我正在尝试从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
并获得此异常:
在从非托管VARIANT到托管对象的转换过程中检测到无效的VARIANT。将无效的VARIANT传递给CLR可能会导致意外的异常,损坏或数据丢失。
编辑: 我可以将委托签名更改为:
internal delegate void LogFunc(string format, IntPtr args);
我可以解析格式并找出期望的参数和类型。例如。对于版本%d的加载插件%s。我希望有一个字符串和一个int。有没有办法从IntPtr中获取这两个?
答案 0 :(得分:3)
以防它有助于某人,这是一个解决问题的解决方案。委托声明为:
[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 // 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}")
说实话,我不确定这是怎么回事。它似乎只是得到了正确的数据。我并没有声称它是正确的。
答案 1 :(得分:1)
我知道C#中还有一个“__arglist”关键字:
答案 2 :(得分:0)
.NET可以(在某种程度上)在va_list
和ArgIterator
之间进行编组。你可以试试这个:
[UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)]
internal delegate void LogFunc(string format, ArgIterator args);
我不确定如何传递参数(字符串作为指针,可能)。你可能会对ArgIterator.GetNextArgType
感到幸运。最终,您可能必须解析格式字符串中的占位符以获取参数类型。
答案 3 :(得分:0)
另一种方法是将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上,您需要复制一个结构才能工作。< / p>
有关更多解释,请参阅我的演示。