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

C# 載入自訂動態連結庫 (DLL):P/Invoke 參數編組、字串/指標/結構體實戰遷移指南

     編輯:關於C語言

遷移策略:為何選擇 DLL Interop 而非重寫/COM 包裝

  • 重寫風險高:600+ 行加密演算法、大量指標/記憶體操作、不熟悉演算法細節 → 相容性極難保證
  • COM 包裝麻煩:需處理型別對應、註冊部署、版本衝突
  • DLL 直接呼叫:封裝成本低、保持原始邏輯、效能最佳 → 採用此方案

C++ DLL 匯出定義標準化


// 標頭檔
#define LIBEXPORT_API extern "C" __declspec(dllexport)

// 範例函數
LIBEXPORT_API int MySum(int a, int b) { return a + b; }
LIBEXPORT_API char* MyStrCopy(char* dest, const char* src) { strcpy(dest, src); return dest; }
LIBEXPORT_API int MyAdd(int a, int b, int* result) { *result = a + b; return *result; }

關鍵:extern "C" 禁用 C++ Name Mangling,確保匯出符號為純函數名。

C# P/Invoke 簽章對照表 (遷移實驗總結)

C++ 宣告C# 簽章關鍵點
int MySum(int, int)[DllImport] static extern int MySum(int a, int b);基本型別直接對應
char* MyStrCopy(char* dest, const char* src)[DllImport(CharSet=CharSet.Ansi)] static extern string MyStrCopy(StringBuilder dest, string src);CharSet.Ansi 對應 char*;StringBuilder 作為可變輸出緩衝區;避免 ref string (不可變導致編組錯誤)
int MyAdd(int, int, int* result)[DllImport] static extern int MyAdd(int a, int b, ref int result);ref int 對應輸出指標;不可用 out (需初始化)
void ComplexFunc(MyStruct* p)[DllImport] static extern void ComplexFunc(ref MyStruct s);結構體 [StructLayout(LayoutKind.Sequential, Pack=4)] + ref 傳址

關鍵遷移經驗總結

  1. 字串輸出參數必須用 StringBuilder,配合 CharSet.Ansi 或 Unicode (對應 C++ char*/wchar_t*),不可用 ref string
  2. 輸出指標參數用 ref/out:int* → ref int (需預設值);雙重指標 int** → ref IntPtr + Marshal.AllocHGlobal
  3. CallingConvention 必須一致:Win32 API 為 StdCall (預設);C++ 預設 Cdecl → 明確指定 CallingConvention.Cdecl
  4. CharSet 決定字串編組與 A/W 後綴搜尋:Ansi→FuncA,Unicode→FuncW,Auto→平台相關
  5. DLL 部署:置於應用程式目錄 (bin\Debug) 或系統 PATH 目錄;32/64 位元必須匹配 (C# 專案勾選 Prefer 32-bit 或編譯 64 位元 DLL)
  6. 結構體記憶體配置:[StructLayout(LayoutKind.Sequential, Pack=4)] 與 C++ #pragma pack(4) 對應;bool 在 C++ 為 1 位元組,C# 預設 4 位元組 → 需 [MarshalAs(UnmanagedType.U1)]

完整遷移範例:加密函數呼叫


// C++ 原型: int EncryptData(const char* input, int inLen, char* output, int* outLen);
[DllImport("CryptoLib.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern int EncryptData(string input, int inLen, StringBuilder output, ref int outLen);

public string Encrypt(string plainText)
{
    byte[] inputBytes = Encoding.ASCII.GetBytes(plainText);
    int maxOut = inputBytes.Length * 2 + 16; // 預估緩衝區
    var sb = new StringBuilder(maxOut);
    int outLen = maxOut;
    
    int ret = EncryptData(plainText, plainText.Length, sb, ref outLen);
    if (ret != 0) throw new Exception($"加密失敗,錯誤碼: {ret}");
    
    return sb.ToString(0, outLen);
}

現代化替代方案 (.NET 5+)

  • LibraryImport (Source Generator):編譯期生成 P/Invoke 存根,效能更好、支援 MarshalUsing 自訂編組
  • NativeAOT:原生編譯,直接連結靜態庫,無 P/Invoke 開銷
  • C++/CLI 包裝層:複雜 C++ 類別庫建議寫 Managed Wrapper,再供 C# 參考
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved