簡體   English   中英

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[] 問題是,我應該如何編寫委托來獲取字符串和整數?

我嘗試使用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 不幸的是,它並沒有走多遠,因為我們缺乏正式的提案。

.NET 可以(在某種程度上)在va_listArgIterator之間進行ArgIterator 你可以試試這個:

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

我不確定將如何傳遞參數(可能是字符串作為指針)。 使用ArgIterator.GetNextArgType可能會有些運氣。 最終,您可能必須解析格式字符串中的占位符才能獲取參數類型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM