Marshal va_list in C# delegate
c#, c++, marshalling, pinvoke
Solution
Just in case it helps someone, here's a solution for marshaling the arguments. The delegate is declared as:
[UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)] // Cdecl is a must
internal delegate void LogFunc(string format, IntPtr argsAddress);
The `argsAddress` is the unmanaged memory address where the array starts (I think). The `format` gives the size of the array. Knowing this I can create the managed array and fill it. Pseuso-code:
size <- get size from format
if size = 0 then return
array <- new IntPtr[size]
Marshal.Copy(argsAddress, array, 0, size);
args <- new string[size]
for i = 0 to size-1 do
placeholder <- get the i-th placeholder from format // e.g. "%s"
switch (placeholder)
case "%s": args[i] <- Marshal.PtrToStringAnsi(array[i])
case "%d": args[i] <- array[i].ToString() // i can't explain why the array contains the value, but it does
default: throw exception("todo: handle {placeholder}")
To tell the truth, I'm not sure how this works. It just seems to get the right data. I'm not claiming it is correct though.
Problem
I'm trying to make this work from c#: C header: ``` typedef void (LogFunc) (const char *format, va_list args); bool Init(uint32 version, LogFunc *log) ``` C# implementation: ``` static class NativeMethods { [DllImport("My.dll", SetLastError = true)] internal static extern bool Init(uint version, LogFunc log); [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)] internal delegate void LogFunc(string format, string[] args); } class Program { public static void Main(string[] args) { NativeMethods.Init(5, LogMessage); Console.ReadLine(); } private static void LogMessage(string format, string[] args) { Console.WriteLine("Format: {0}, args: {1}", format, DisplayArgs(args)); } } ``` What happens here is that the call to `NativeMethods.Init` calls back `LogMessage` and passes data from unmanaged code as parameters. This works for most cases in which the arguments are strings. However, there is a call on which the format is: Loaded plugin %s for version %d. and the args contains only a string (the plugin name). They do not contain the version value, which makes sense since I used `string[]` in the delegate declaration. Question is, how should I write the delegate to get both the string and the int? I tried using `object[] args` and got this exception: An invalid VARIANT was detected during a conversion from an unmanaged VARIANT to a managed object. Passing invalid VARIANTs to the CLR can cause unexpected exceptions, corruption or data loss. EDIT: I could change the delegate signature to this: ``` internal delegate void LogFunc(string format, IntPtr args); ``` I could parse the format and find out how many arguments to expect and of what type. E.g. for Loaded plugin %s for version %d. I would expect a string and an int. Is there a way to get these 2 out of that IntPtr?