[英]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[]
。 問題是,我應該如何編寫委托來獲取字符串和整數?
我嘗試使用object[] args
並得到此異常:在從非托管 VARIANT 到托管對象的轉換期間檢測到無效的 VARIANT。 將無效的 VARIANT 傳遞給 CLR 會導致意外異常、損壞或數據丟失。
編輯:我可以將委托簽名更改為:
internal delegate void LogFunc(string format, IntPtr args);
我可以解析格式並找出期望的參數數量和類型。 例如,對於版本 %d 的已加載插件 %s。 我希望一個字符串和一個整數。 有沒有辦法從 IntPtr 中獲取這兩個?
以防萬一它對某人有幫助,這里有一個整理參數的解決方案。 委托聲明為:
[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);
在回調中,您處理不同平台的特定情況。 您需要了解它在每個平台上的工作方式。 根據我的測試和理解,您可以將 IntPtr 原樣傳遞給 Windows (x86,x64) 和 Linux x86 上的 vprintf* 系列函數,但在 Linux x64 上,您需要復制一個結構才能工作。
有關更多解釋,請參閱我的演示。
編輯:我們不久前在 .net 運行時的存儲庫上發布了一個問題,您可以在此處查看https://github.com/dotnet/runtime/issues/9316 。 不幸的是,它並沒有走多遠,因為我們缺乏正式的提案。
我知道 C# 中還有一個“__arglist”關鍵字可用:
.NET 可以(在某種程度上)在va_list
和ArgIterator
之間進行ArgIterator
。 你可以試試這個:
[UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)]
internal delegate void LogFunc(string format, ArgIterator args);
我不確定將如何傳遞參數(可能是字符串作為指針)。 使用ArgIterator.GetNextArgType
可能會有些運氣。 最終,您可能必須解析格式字符串中的占位符才能獲取參數類型。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.