va_list的元帅在C#中的委托(Marshal va_list in C# delegate)

2019-06-24 01:16发布

我试图让从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的?

Answer 1:

万一它可以帮助别人,这里的封送参数的解决方案。 委托声明为:

[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}")

说实话,我不知道这是如何工作。 它只是似乎得到正确的数据。 我不是说这是正确的,但。



Answer 2:

我的理解也有在C#中的“__arglist”关键字可用:

  • http://www.dotnetinterop.com/faq/?q=Vararg

  • http://bartdesmet.net/blogs/bart/archive/2006/09/28/4473.aspx



Answer 3:

.NET可以(在一定程度上)之间编组va_listArgIterator 。 你可以试试这个:

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

我不知道该参数将如何被传递(字符串作为指针,可能)。 你可能有一些运气ArgIterator.GetNextArgType 。 最终,你可能会解析格式字符串中的占位符来获得参数类型。



Answer 4:

另一种方法是通过所使用的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上,你将需要复制的结构为工作。

见我的演示更多的解释。



文章来源: Marshal va_list in C# delegate