C#P / Invoke:Varargs委托回调

时间:2011-07-14 14:13:39

标签: c# pinvoke variadic-functions

我只是想做一些托管/非托管互操作。为了获得扩展的错误信息,我决定注册dll提供的日志回调:


[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void LogCallback(void* arg1,int level,byte* fmt);

这个定义有效,但我得到的字符串如“格式%s探测大小=%d和得分=%d”。 我尝试添加__arglist关键字,但代表不允许这样做。

嗯,这对我来说并不那么引人注目,但我只是很好奇,有人可以在C#中获得varargs参数。 我知道我可以使用c ++进行互操作。 所以:有没有办法纯粹用C#做这个,有合理的efford?

编辑:对于那些仍然没有得到它的人:我不进口一个varargs函数但是将其作为回调导出,然后称为我的本机代码。我一次只能指定一个 - >只有一个重载可能,__ arglist不起作用。

6 个答案:

答案 0 :(得分:4)

没有可能的方法。它是不可能的原因是因为变量参数列表在C中的工作方式。

在C中,变量参数只是作为额外参数被推送到堆栈(在我们的例子中是非托管堆栈)。 C不会记录堆栈上的参数数量,被调用的函数采用其最后的形式参数(varargs之前的最后一个参数)获取其位置并开始从堆栈中弹出参数。

它知道从堆栈弹出多少变量的方式是完全基于约定的 - 一些其他参数指示堆栈上有多少变量参数。对于printf,它通过解析格式字符串并在每次看到格式代码时弹出堆栈来实现。看起来您的回调类似。

对于CLR来处理它,它必须能够知道正确的约定来确定拾取所需的参数数量。您无法编写自己的处理程序,因为它需要访问您无权访问的非托管堆栈。所以你无法用C#做到这一点。

有关这方面的更多信息,您需要阅读C调用约定。

答案 1 :(得分:2)

以下是处理它的方法。它可能适用于您的情况,也可能不适用,具体取决于您的回调参数是否与printf函数族一起使用。

首先,从vsprintf导入_vscprintfmsvcrt

[DllImport("msvcrt.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int vsprintf(
    StringBuilder buffer,
    string format,
    IntPtr args);

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int _vscprintf(
    string format,
       IntPtr ptr);

接下来,使用IntPtr args指针声明您的委托:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void LogCallback(
    void* arg1,
    int level, 
    [In][MarshalAs(UnmanagedType.LPStr)] string fmt,
    IntPtr args);

现在,当通过本机代码调用您的委托时,只需使用vsprintf正确格式化消息:

private void LogCallback(void* data, int level, string fmt, IntPtr args)
{
    var sb = new StringBuilder(_vscprintf(fmt, args) + 1);
    vsprintf(sb, fmt, args);

    //here formattedMessage has the value your are looking for
    var formattedMessage = sb.ToString();

    ...
}

答案 2 :(得分:1)

实际上可以在CIL中使用:

.class public auto ansi sealed MSIL.TestDelegate
       extends [mscorlib]System.MulticastDelegate
{
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor(object 'object',
                                 native int 'method') runtime managed
    {
    }
    .method public hidebysig newslot virtual 
            instance vararg void  Invoke() runtime managed
    {
    }
}

答案 3 :(得分:0)

以下文章介绍了略有不同的情况,可能会有所帮助:

How to P/Invoke VarArgs (variable arguments) in C#

答案 4 :(得分:0)

你需要P / invoke marshaller的支持才能做到这一点。编组人员不提供此类支持。因此无法完成。

答案 5 :(得分:0)

我不同意@ shf301,有可能。

对于PInvoke,您可以使用__arglist,如下所示:

[DllImport("msvcrt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "printf")]
public static extern int PrintFormat([MarshalAs(UnmanagedType.LPStr)] string format, __arglist);

呼叫:PrintFormat("Hello %d", __arglist(2019));

对于委托和回调:

  1. 定义以下结构:

    public unsafe struct VariableArgumentBuffer
    {
        public const int BufferLength = 64; // you can increase it if needed
    
        public fixed byte Buffer[BufferLength];
    
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static VariableArgumentBuffer Create(params object[] args)
        {
            VariableArgumentBuffer buffer = new VariableArgumentBuffer();
            Write(ref buffer, args);
            return buffer;
        }
    
        public static void Write(ref VariableArgumentBuffer buffer, params object[] args)
        {
            if (args == null)
            return;
    
            fixed (byte* ptr = buffer.Buffer)
            {
                int offset = 0;
    
                for (int i = 0; i < args.Length; i++)
                {
                    var arg = args[i];
    
                    if (offset + Marshal.SizeOf(arg) > BufferLength)
                        throw new ArgumentOutOfRangeException();
    
                    switch (arg)
                    {
                    case byte value:
                         *(ptr + offset++) = value;
                         break;
    
                    case short value:
                         *(short*)(ptr + offset) = value;
                         offset += sizeof(short);
                         break;
    
                    case int value:
                        *(int*)(ptr + offset) = value;
                        offset += sizeof(int);
                        break;
    
                    case long value:
                        *(long*)(ptr + offset) = value;
                        offset += sizeof(long);
                        break;
    
                    case IntPtr value:
                        *(IntPtr*)(ptr + offset) = value;
                        offset += IntPtr.Size;
                        break;
    
                    default: // TODO: Add more types
                        throw new NotImplementedException();
                  }
              }
           }
        }
     }
    
  2. 定义您的代表

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int PrintFormatDelegate([MarshalAs(UnmanagedType.LPStr)] string format, VariableArgumentBuffer arglist);
    
  3. 通话

    callback("Hello %d %s", VariableArgumentBuffer.Create(2019, Marshal.StringToHGlobalAnsi("Merry christmas")));
    
  4. 用于实施

    public static int MyPrintFormat(string format, VariableArgumentBuffer arglist)
    {
        var stream = new UnmanagedMemoryStream(arglist.Buffer, VariableArgumentBuffer.BufferLength);
        var binary = new BinaryReader(stream);
    
        ....
    }
    
    • 您必须解析format才能知道被压入堆栈的内容,然后使用binary读取参数。例如,如果您知道要推送一个int32,则可以使用binary.ReadInt32()来读取它。如果您不了解此部分,请在评论中告诉我,以便我为您提供更多信息。