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

C# 調用 C/C++ DLL 實戰踩坑總結

     編輯:關於C++

C# 調用 C/C++ DLL 實戰踩坑總結

專案中需在 WinForm 程式中透過 P/Invoke 調用 C 語言編寫的 DLL 讀取 IC 卡號,過程中遇到 DLL 路徑解析、結構體封送、參數傳遞約定等典型問題,現整理如下供參考。

一、DLL 路徑解析問題

CLR 尋找非託管 DLL 的順序:

  1. 應用程式目錄(bin\Debug 或 bin\Release)。
  2. 系統目錄(C:\Windows\System32)。
  3. 環境變量 PATH 指定的目錄。
  4. 硬編碼絕對路徑(DllImport(@"E:\Source\GPTagReaderDll")),不推薦(發布時路徑不固定,且若該 DLL 還依賴其他 DLL,硬編碼無法解決依賴鏈)。

最佳實踐:將所有非託管 DLL(含依賴項)放在與 .exe 同級目錄,或在專案屬性 → Build Events → Post-build event 中添加複製命令(xcopy /y "$(SolutionDir)NativeDlls\*" "$(TargetDir)")。

二、結構體封送

C 端定義

typedef struct {
    unsigned char DeviceIdentify[30]; // 設備標識字符串
    unsigned short res;               // 保留字段
} FindDeviceAck_Struct;

C# 端對應定義(關鍵屬性)

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct FindDeviceAck_Struct {
    // 固定長度字符串 → ByValTStr(注意編碼)
    // [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)]
    // public string DeviceIdentify;   // 易產生亂碼

    // 建議用 byte[] 對應 unsigned char[],再手動解碼
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 30)]
    public byte[] DeviceIdentify;

    public ushort res;
}
  • LayoutKind.Sequential:按聲明順序緊湊排列。
  • Pack = 1:1 字節對齊,對應 C 端 #pragma pack(1) 或預設無填充。
  • CharSet.Ansi:字符串以 ANSI 編碼封送(C 端為 char)。
  • 字符串陷阱:ByValTStr 封送為定長 ANSI 字符串,若 C 端數據含非 ASCII 或非空終止,極易亂碼;改用 byte[] + Encoding.Default.GetString() 手動轉換最穩健。

三、函數簽名與調用約定

C 端原型

BOOL FindDevice(FindDeviceAck_Struct *finddeviceack); // 輸出參數,指針傳遞

C# 端正確聲明

[DllImport("GPTagReaderDll", EntryPoint = "FindDevice", CallingConvention = CallingConvention.Cdecl)]
public static extern bool FindDevice(ref FindDeviceAck_Struct finddeviceack);
  • 關鍵點:C 端參數為指針(*)且為輸出參數,C# 必須用 ref(或 out)標記,否則發生 Attempted to read or write protected memory 錯誤。
  • CallingConvention.Cdecl:C/C++ 預設調用約定(調用者清棧),若 DLL 為 __stdcall 則用 StdCall(預設)。
  • EntryPoint:若 C++ 函數經歷 Name Mangling,需用 extern "C" 導出或用 Dependency Walker 確認實際導出名。

四、調用示例

var ack = new FindDeviceAck_Struct();
bool ok = NativeMethods.FindDevice(ref ack);
if (ok) {
    string devId = Encoding.Default.GetString(ack.DeviceIdentify).TrimEnd('\0');
    Console.WriteLine($"Device: {devId}, Res: {ack.res}");
}

五、避坑清單

  1. DLL 與依賴項放在 exe 同目錄,勿硬編碼路徑。
  2. 結構體 Pack、CharSet、LayoutKind 嚴格對應 C 端定義。
  3. 字符串字段優先用 byte[] 封送,應用層自行解碼。
  4. 輸出指針參數務必加 ref/out。
  5. 調用約定(Cdecl/StdCall)與導出名(EntryPoint)雙重確認。
  6. 若 DLL 為 C++ 類導出,需編寫 C 風格封裝層(extern "C" 工廠函數)供 P/Invoke 調用。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved