為了記錄開發過程中踩過的坑,避免日後重蹈覆轍,我決定將這段跨語言互操作的經驗完整記錄下來,也希望能幫助有類似需求的開發者少走彎路。
近期需要開發一個介面系統,包裝先前完成的人臉屬性識別模型。使用者可隨機選取圖片(未來將整合攝影機即時拍攝),系統自動分析圖中人臉屬性(如是否戴眼鏡、帽子等)。整個流程包含人臉偵測、人臉對齊、屬性預測三個階段:
為什麼不直接混合編譯?雖然可行,但程式碼可讀性極差、除錯困難,我嘗試過最終仍選擇放棄。採用 DLL 封裝雖然初期包裝費時數天,但後續使用如同黑盒子般簡單:依規定參數型別傳入,即可獲得預期輸出,幾行程式碼搞定,基本免配置環境。
本機環境:Windows 7 64位 + Visual Studio 2013 + MATLAB 2015
請務必確認你的環境與上述一致,否則我的成功路徑不一定適用。我當初搜尋資料時找不到完全相同環境的教學,全靠摸索才通過,關鍵在於不放棄。
C++ 屬於非托管代碼,直接由作業系統執行;C# 屬於托管代碼,在 CLR (Common Language Runtime) 環境下執行,享有自動垃圾回收、型別檢查等服務。若 C# 要呼叫 C++,必須將 C++ 代碼包裝成托管代碼並編譯為 DLL。
假設有一個人臉偵測+5點定位函數,輸入兩個字串(圖片路徑、特徵點儲存路徑)。首先將此函數封裝為類的成員函數,稱為 NativeClass:
關鍵提示:C++ 的 std::string 封裝後在 C# 端型別極其複雜,強烈建議使用 char* 作為參數,對應 C# 的 sbyte*,轉換極為方便。
需修改兩處紅框標示:
小插曲:設定屬性後有時關鍵字高亮會消失(編譯器識別異常),多試幾次保存直到高亮恢復,不影響最終編譯但寫碼體驗大打折扣。
核心要點:
public ref class 宣告托管類,供 C# 可見
兩處紅框關鍵:
若 C++ 專案依賴額外 DLL/LIB,必須一併複製到 C# 專案輸出目錄,否則執行時會報「DLL 依賴缺失」。
將 DLL 複製到 C# 專案 bin\Release 目錄,並在「參考」中加入該 DLL。
C++ char* 對應 C# sbyte*。字串轉換範例:
需在方法標記 unsafe,且專案屬性 → Build → 勾選 Allow unsafe code:
關鍵坑點:若 C++ DLL 以 x64 編譯,C# 專案 必須取消勾選 "Prefer 32-bit",否則執行會報錯。
var wrapper = new ManagedWrapper(); // 托管類名稱
wrapper.DetectFace(imgPath, ptsPath); // 呼叫函數
string result = Marshal.PtrToStringAnsi((IntPtr)ptr); // 結果轉回 string
至此 C# 呼叫 C++ DLL 大功告成!MATLAB DLL 呼叫請見下篇。