首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

CLR 互操作核心:托管與非托管代碼封送處理完整指南

     編輯:關於.NET

封送處理:跨越托管/非托管邊橋樑

當托管代碼 (C#) 呼叫非托管代碼 (C++ DLL、Win32 API、COM) 時,**資料型別、記憶體佈局、呼叫慣例、例外處理** 皆不相容。CLR **封送處理器** 負責在邊界自動轉換,核心決策點在於:**複製 vs 釘住**。

方向屬性:InAttribute / OutAttribute

C# 簽名套用屬性封送方向非托管對應
void M(int x)預設[In]int x
void M(out int x)[Out][Out]int *x
void M(ref int x)[In, Out][In, Out]int *x
void M([In] ref int x)[In] 覆蓋預設[In]int *x (不可寫回)
  • StringBuilder:預設視為 [In, Out],CLR 會**釘住內部緩衝區**直接傳遞指標,效能最佳。**嚴禁使用 ref/out**,否則變雙重指標失去釘住優勢。
  • string:不可變,僅支援 [In];若需寫回請用 StringBuilder。
  • class (參考型別):預設 [In];out/ref 產生雙重指標 **。

複製 vs 釘住:效能關鍵

條件行為範例
可直接複製型別釘住 (無複製)int, byte, struct (僅含基本型別), string (CharSet=Unicode)
需轉換型別複製 (配置非托管記憶體、轉換、呼叫、複製回、釋放)string (CharSet=Ansi), bool[], 含非基本型別的 struct
ref/out 參數永遠複製 (雙重指標)ref int, out string
反向 P/Invoke (非托管呼叫托管)永遠複製委派轉函數指標回呼

最佳化原則:優先使用 CharSet=Unicode、StringBuilder、value type struct、避免 ref/out 參考型別,讓 CLR 走釘住路徑。

記憶體所有權與釋放慣例 (COM 風格)

  • [In]:呼叫端配置、呼叫端釋放。被呼叫端**不可**釋放或修改指標指向。
  • [Out]:被呼叫端配置 (CoTaskMemAlloc/SysAllocString)、**呼叫端釋放** (CoTaskMemFree/SysFreeString)。
  • [In, Out]:呼叫端配置 → 被呼叫端可 CoTaskMemFree 舊記憶體 → CoTaskMemAlloc 新記憶體 → 覆蓋指標 → 呼叫端最終釋放新記憶體。

鐵律:非托管代碼**必須**使用 CoTaskMemAlloc/Free 或 SysAllocString/Free,嚴禁 malloc/free、new/delete、HeapAlloc/Free,否則跨版本 (XP/Vista/Win10) 會崩潰或洩漏。

反向 P/Invoke:委派生命週期管理

public delegate void Callback(int value);

[DllImport("NativeLib.dll")]
public static extern void RegisterCallback(Callback cb);

public static void Main()
{
    Callback managedCb = OnNativeEvent;
    // 關鍵:防止 GC 回收委派
    GCHandle handle = GCHandle.Alloc(managedCb);
    try
    {
        RegisterCallback(managedCb); // 內部建立 thunk 持有委派參考
        Console.ReadLine();
    }
    finally { handle.Free(); }
}
private static void OnNativeEvent(int v) => Console.WriteLine($"Native callback: {v}");
  • Marshal.GetFunctionPointerForDelegate 僅取得函數指標,**不延長委派壽命**。
  • 若非托管端**長期持有**函數指標 (非即時回呼),**必須** GCHandle.Alloc(delegate) 防止 GC 回收,否則觸發 CallbackOnCollectedDelegate MDA 錯誤。
  • 短暫回呼 (如 EnumWindows) 通常無需手動 GCHandle,CLR 內部 thunk 會暫時固定。

P/Invoke Interop Assistant:自動生成簽名神器

微軟官方工具,支援:

  • SigImp Search:從 Windows SDK 標頭檔搜尋 API 自動生成 C# 簽名。
  • SigImp Translate Snippet:貼上 C++ 宣告自動轉 C#。
  • SigExp:反向輸出非托管簽名供驗證。

下載:GitHub Repo 或隨 Visual Studio SDK 安裝。

實戰檢查清單

  1. 確認 CharSet (Unicode/Ansi) 與非托管端一致。
  2. 字串輸出**務必用 StringBuilder** + Capacity。
  3. 結構體加 [StructLayout(LayoutKind.Sequential)],欄位序與非托管一致。
  4. 陣列參數用 [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=n)] 指定長度。
  5. 非托管配置記憶體回傳托管 → 托管端 Marshal.FreeCoTaskMem / Marshal.FreeBSTR 釋放。
  6. 啟用 DllImport(SetLastError=true) + Marshal.GetLastWin32Error() 捕獲 Win32 錯誤碼。

總結

封送處理看似複雜,核心只有一條:**讓 CLR 走「釘住」而非「複製」路徑**。掌握 In/Out、StringBuilder、CharSet=Unicode、StructLayout、CoTaskMemAlloc 五大關鍵,即可寫出高效能、零洩漏、跨平台穩定的互操作代碼。複雜案例優先用 **P/Invoke Interop Assistant** 生成骨架,再人工微調。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved