C#委托中的Marshal va_list

我正在尝试从c#开始这项工作:

C头:

typedef void (LogFunc) (const char *format, va_list args); bool Init(uint32 version, LogFunc *log) 

C#实现:

 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)); } } 

这里发生的是对NativeMethods.Init调用回调LogMessage并将来自非托管代码的数据作为参数传递。 这适用于大多数情况,其中参数是字符串。 但是,有一种调用格式为:

版本%d的已加载插件%s。

并且args只包含一个字符串(插件名称)。 它们不包含版本值,这是有道理的,因为我在委托声明中使用了string[] 。 问题是,如何编写委托来获取字符串和int?

我尝试使用object[] args并得到此exception: 在从非托管VARIANT到托管对象的转换过程中检测到无效的VARIANT。 将无效的VARIANT传递给CLR可能会导致意外的exception,损坏或数据丢失。

编辑:我可以将委托签名更改为:

 internal delegate void LogFunc(string format, IntPtr args); 

我可以解析格式并找出期望的参数和类型。 例如, 版本%d的已加载插件%s。 我期待一个字符串和一个int。 有没有办法从IntPtr中获取这两个?

万一它可以帮助某人,这里是一个解决问题的解决方案。 代表被宣布为:

 [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)] // Cdecl is a must internal delegate void LogFunc(string format, IntPtr argsAddress); 

argsAddress是数组启动的非托管内存地址(我认为)。 format给出了数组的大小。 知道了这一点,我可以创建托管数组并填充它。 Pseuso代码:

 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 // eg "%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}") 

说实话,我不确定这是怎么回事。 它似乎只是得到了正确的数据。 我不是说它是正确的。

.NET可以(在某种程度上)在va_listArgIterator之间ArgIterator 。 你可以试试这个:

 [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)] internal delegate void LogFunc(string format, ArgIterator args); 

我不确定如何传递参数(字符串作为指针,可能)。 你可能会对ArgIterator.GetNextArgType幸运。 最终,您可能必须解析格式字符串中的占位符以获取参数类型。

另一种方法是将va_list传递回本机代码,比如在.net中调用vprintf。 我有同样的问题,我希望它跨平台。 所以我编写了一个示例项目来演示它如何在多个平台上运行。

请参阅https://github.com/jeremyVignelles/va-list-interop-demo

基本思路是:

您声明了您的回调委托:

 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void LogFunc(string format, IntPtr args); 

你像往常一样传递你的回调:

 NativeMethods.Init(5, LogMessage); 

在回调中,您可以处理不同平台的特定情况。 您需要了解它在每个平台上的工作原理。 根据我的测试和理解,您可以按原样将IntPtr传递给Windows(x86,x64)和Linux x86上的vprintf *系列函数,但是在Linux x64上,您需要复制一个结构才能工作。

有关更多说明,请参阅我的演示。