我试图让从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
和非托管代码作为参数传递数据。 这适用于其中的参数都是字符串大多数情况下。 然而,在其上的格式是呼叫:
加载插件%s的版本%d。
和ARGS只包含一个字符串(插件名称)。 它们不包含版本值,因为我用这是有道理string[]
在委托声明。 问题是,我应该怎么写委托获得了字符串和诠释?
我尝试使用object[] args
并得到此异常: 从非托管VARIANT到被管理对象在转换期间检测到无效VARIANT。 传递无效的变体的CLR会导致意外的异常,损坏或数据丢失。
编辑:我可以委托签名改成这样:
internal delegate void LogFunc(string format, IntPtr args);
我可以解析的格式,并找出多少个参数的期望和什么类型的。 例如,对于加载插件%s的版本%d。 我希望一个字符串和一个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 // 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}")
说实话,我不知道这是如何工作。 它只是似乎得到正确的数据。 我不是说这是正确的,但。
我的理解也有在C#中的“__arglist”关键字可用:
.NET可以(在一定程度上)之间编组va_list
和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的作为,是对vprintf *家庭的功能在Windows(X86,X64)和Linux的x86,但在Linux x64上,你将需要复制的结构为工作。
见我的演示更多的解释。