專案需復用 C/C++ 既有模組 (加密演算法、硬體驅動等)。三種方案:重寫 (成本高)、COM 封裝 (註冊麻煩)、P/Invoke 呼叫原生 DLL (最輕量)。本文記錄從簡單到複雜的參數封送驗證過程。
// 統一導出巨集
#define LIBEXPORT extern "C" __declspec(dllexport)
// 1. 簡單整數運算
LIBEXPORT int MySum(int a, int b) { return a + b; }
// 2. 字串輸入/輸出 (呼叫端配置緩衝區)
LIBEXPORT void MyStrCopy(char* input, char** output)
{
*output = _strdup(input); // 分配記憶體,呼叫端需釋放
}
// 3. 整數輸出參數
LIBEXPORT int MySumOut(int a, int b, int* result)
{
*result = a + b;
return *result;
}
[DllImport("MyLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int MySum(int a, int b);
// 測試通過
C++ 回傳 char** (指標的指標),C# 必須用 ref string 或 out string,並設 CharSet = CharSet.Ansi:
[DllImport("MyLib.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void MyStrCopy(string input, out string output);
// 使用
MyStrCopy("Hello", out string result);
Console.WriteLine(result); // Hello
// 注意: C++ 端 _strdup 配置的記憶體需額外匯出 Free 函式釋放,否則洩漏
[DllImport("MyLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int MySumOut(int a, int b, out int result);
// 使用
MySumOut(2, 3, out int sum); // sum = 5
| C++ 型別 | C# 封送型別 | 關鍵屬性 |
|---|---|---|
int, long (4 bytes) | int, int | |
char* (輸入) | string | CharSet.Ansi / Unicode |
char** (輸出) | out string / ref string | CharSet.Ansi, 記憶體由 C++ 配置 |
int* (輸出) | out int / ref int | |
struct* | ref StructType | [StructLayout(LayoutKind.Sequential)] |
| 回呼函式指標 | delegate | 委派實例必須保持存活防 GC |
Cdecl (呼叫端清理堆疊),.NET 預設 StdCall (被呼叫端清理)。必須顯式指定。Ansi 或 Unicode,避免自動查找導致不匹配。FreeString(char*) 供 C# 呼叫釋放。[StructLayout(LayoutKind.Sequential, Pack = 1)] 對應 C++ #pragma pack(1)。IntPtr,long 在 C++ 為 4 位元組,C# 為 8 位元組,勿混用。