C# P/Invoke: Varargs delegate callback

Viewed 3619

I was just trying to do some managed/unmanaged interop. To get extended error information I decided to register a log callback offered by the dll:


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

This definition works, but i get strings like "Format %s probed with size=%d and score=%d". I tryed to add the __arglist keyword, but it is not allowed for delegates.

Well, it is not so dramatic for me, but I am just curious wether one could get the varargs parameters in C#. I know that I could use c++ for interop. So: Is there a way to do this purely in C#, with reasonable efford?

EDIT: For those who still did not get it: I am NOT IMPORTING a varargs function BUT EXPORTING it as a callback, which is then called my native code. I can specify only one at a time -> only one overload possible and __arglist does NOT work.

6 Answers

Actually it is possible in 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
    {
    }
}

I disagree with @shf301, It's possible.

You can use __arglist in case of PInvoke, like this:

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

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

In the case of delegates and callbacks:

  1. Define the following struct:

    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. Define your delegate

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

    callback("Hello %d %s", VariableArgumentBuffer.Create(2019, Marshal.StringToHGlobalAnsi("Merry christmas")));
    
  4. For implementing

    public static int MyPrintFormat(string format, VariableArgumentBuffer arglist)
    {
        var stream = new UnmanagedMemoryStream(arglist.Buffer, VariableArgumentBuffer.BufferLength);
        var binary = new BinaryReader(stream);
    
        ....
    }
    
    • You have to parse format to know what is pushed into the stack, and then read arguments using binary. For example, if you know an int32 is pushed, you can read it using binary.ReadInt32(). If you don't understand this part, please tell me in comments so I can provide you more info.
Related