C# 委托中的封送va_list
Marshal va_list in C# delegate
我正在尝试从 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。我期望一个字符串和一个整数。有没有办法从那个 Intptr 中取出这 2 个?
以防万一它对某人有帮助,这里有一个封送参数的解决方案。委托声明为:
[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}")
说实话,我不确定这是如何工作的。它似乎只是获得了正确的数据。不过,我并不是说这是正确的。
另一种方法是将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);
在回调中,您可以处理不同平台的特定情况。您需要了解它在每个平台上的工作方式。根据我的测试和理解,您可以在Windows(x86,x64(和Linux x86上按原样将IntPtr传递给vprintf*系列函数,但是在Linux x64上,您需要复制一个结构才能使其工作。
有关更多解释,请参阅我的演示。
编辑:不久前我们在.net运行时的存储库上发布了一个问题,您可以在此处看到它 https://github.com/dotnet/runtime/issues/9316。不幸的是,它并没有走多远,因为我们缺乏正式的提案。
我知道 C# 中还有一个"__arglist"关键字:
-
http://www.dotnetinterop.com/faq/?q=Vararg
-
http://bartdesmet.net/blogs/bart/archive/2006/09/28/4473.aspx
.NET可以(在某种程度上(在va_list
和ArgIterator
之间封送。你可以试试这个:
[UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)]
internal delegate void LogFunc(string format, ArgIterator args);
我不确定参数将如何传递(字符串作为指针,可能(。你可能有一些运气 ArgIterator.GetNextArgType
.最终,您可能必须分析格式字符串中的占位符以获取参数类型。
- 使用std::multimap迭代器创建std::list
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- 如何在 C 中正确使用 libiconv 使其不会报告"Arg list too long"?
- C++中带有List类的迭代器Segfault
- 使用"std::unordereded_map"映射到"std::list"对象
- GCC对可能有效的代码抛出init list生存期警告
- 使用std::list创建循环链表
- 重载Singly Linked List中的赋值运算符
- '[](std::list& list)<int>{return std::move(list)}(list)' 是否保证将 'list' 留空?
- 如果 KEY 是 std::list 或 std::vector 而不是值,那么 std::map 的默认行为是什么?
- 为什么这个 std::queue/指向结构的指针列表直到 List.Size() == 0 才释放内存?
- "std::list::splice(std::const_iterator pos, std::list&& other)"是否保证将"其他"留空?
- 从嵌套循环中的 std::list 中删除将返回访问冲突
- QStringList vs list<shared_ptr<QString>> 性能比较C++
- 包含 std::list 的结构体的 C++ 初始化
- 在C++中使用 Catch 测试框架编译错误"error: expected ';' at end of declaration list"
- C++ assigment std::list:<typename>:itrator 在 main 中工作,但在方法中它不起作用
- 在基于范围的 for 循环期间插入 std::list 的后面
- 循环挂起迭代的 std::擦除 on std::list
- 如何增加以前由新运算符分配的 C++ std::list 数组的大小?