【问题标题】:Marshal va_list in C# delegate在 C# 委托中元帅 va_list
【发布时间】:2012-05-08 20:03:10
【问题描述】:

我正在尝试通过 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 中取出这两个?

【问题讨论】:

  • 封送参数只是问题的一部分,您只能通过调用 vsprintf() 正确格式化字符串。您需要用 C++/CLI 语言编写一个小适配器。

标签: c# c++ pinvoke marshalling


【解决方案1】:

以防万一它对某人有所帮助,这里有一个整理参数的解决方案。委托声明为:

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

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

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

说实话,我不确定它是如何工作的。它似乎得到了正确的数据。不过我并不是说它是正确的。

【讨论】:

  • 非常感谢,我正在尝试完全相同的事情,使用相同的非托管 API(我从您的消息中猜到了)。
【解决方案2】:

另一种方法是将 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 上,您需要复制一个结构才能使其工作。

查看我的演示以获得更多解释。

编辑:我们不久前在 .net 运行时的存储库上发布了一个问题,您可以在此处查看 https://github.com/dotnet/runtime/issues/9316。不幸的是,由于我们缺乏正式的提案,它并没有走得太远。

【讨论】:

    【解决方案3】:

    【讨论】:

    • 它是,但它不适用于代表。我试着这样使用它:internal delegate void LogFunc(string format, __arglist);
    【解决方案4】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      相关资源
      最近更新 更多