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

C# DllImport 進階實戰:呼叫非受控 DLL 參數編組、入口點修飾、INI 操作完整指南

     編輯:關於C語言

DllImport 基礎:平台呼叫 (P/Invoke) 核心屬性


[DllImport("Kernel32.dll", 
    EntryPoint = "MoveFileW",      // 實際匯出函數名 (支援重載/修飾名)
    CharSet = CharSet.Unicode,     // 字串編組:Unicode (W) / Ansi (A) / Auto
    SetLastError = true,           // 保留 Win32 錯誤碼 (Marshal.GetLastWin32Error)
    CallingConvention = CallingConvention.StdCall, // 呼叫慣例:Win32 API 為 StdCall
    ExactSpelling = true           // 禁止自動尋找 A/W 後綴
)]
public static extern bool MoveFile(string src, string dst);

實戰案例:C++ DLL 名稱修飾問題

C++ 編譯器對函數名進行 Name Mangling (修飾),產生如 ?OpenSerialPort1@@YA_NXZ。解決方案:

  1. C++ 端加上 extern "C" 禁用修飾:
    // 輸出標準 C 風格符號
    extern "C" __declspec(dllexport) bool OpenSerialPort1();
    extern "C" __declspec(dllexport) bool fnGetIO(unsigned char* p1, unsigned char* p2, unsigned char* p3);
    extern "C" __declspec(dllexport) bool CloseSerialPort1();
  2. 或使用 .def 模組定義檔匯出別名
  3. C# 端 EntryPoint 直接寫原始函數名,CallingConvention = Cdecl (若 C++ 為 cdecl)

參數編組進階:指標、陣列、輸出參數

C/C++ 宣告C# 簽章關鍵屬性
void GetData(byte* buf, int len)void GetData(byte[] buf, int len)陣列自動編組為指標 (In/Out)
void GetData(byte** buf)void GetData(ref IntPtr buf)雙重指標用 ref IntPtr + Marshal.AllocHGlobal
int GetValue(byte* pBuffer) (輸出陣列)[DllImport] static extern int GetValue([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] byte[] buffer, int size);Out + LPArray + SizeParamIndex 指定長度參數索引
void Func(char* str) (字串輸出)void Func(StringBuilder sb, int capacity)StringBuilder 為可變字串緩衝區
void Func(const char* str) (字串輸入)void Func(string str)字串自動編組為 LPSTR/LPWSTR

實用工具:INI 檔讀寫封裝


public class IniFile
{
    private readonly string _path;

    [DllImport("kernel32", CharSet = CharSet.Auto)]
    private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);

    [DllImport("kernel32", CharSet = CharSet.Auto)]
    private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);

    public IniFile(string path) => _path = path;

    public void Write(string section, string key, string value)
        => WritePrivateProfileString(section, key, value, _path);

    public string Read(string section, string key, string defaultValue = "")
    {
        var sb = new StringBuilder(255);
        GetPrivateProfileString(section, key, defaultValue, sb, 255, _path);
        return sb.ToString();
    }
}

常見坑與除錯技巧

  • DLL 尋找順序:應用程式目錄 → 系統目錄 → PATH 環境變數。建議將非系統 DLL 放在輸出目錄
  • 32/64 位元不匹配:C# Any CPU 在 64 位元 OS 執行為 64 位元進程,無法載入 32 位元 DLL。解法:專案屬性 → Build → 勾選 Prefer 32-bit 或編譯 64 位元版 DLL
  • 字串編組不一致:C++ char* (Ansi) 對應 CharSet.Ansi,wchar_t* 對應 CharSet.Unicode
  • 結構體記憶體對齊:[StructLayout(LayoutKind.Sequential, Pack=4)] 控制封裝對齊,需與 C++ #pragma pack 一致
  • 委派回呼:跨非受控/受控邊界的委派必須防止被 GC 回收,需保存委派實例參考 (如存為靜態欄位)

現代替代方案

  • NativeAOT / LibraryImport (.NET 7+):原生 AOT 編譯、原始碼產生器自動產生 P/Invoke 簽章,效能更好
  • C++/CLI 包裝層:複雜 C++ 類別庫建議寫 Managed C++ 包裝,再供 C# 參考
  • ComInterface / SWIG:大型 C++ 專案自動生成互通層
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved