最近做項目用到了一個二維碼識別的SDK,想在C#裡做個介面去調用該庫產生和解析二維碼,原本之前做過在C#裡調用DLL的研究,沒想到這次的嘗試會異常的艱難,聽我慢慢說來。
先用google搜了一通,總結了C#下調用DLL的幾種方法:
1、C#下Invoke。通過DLLImport動態匯入DLL中的函數,然後直接調用之。這種方法比較適合WinAPI和參數比較簡單的函數(最好還是通過純C的方式,即extern "C"的方式匯出的函數),否則要在C#裡構造出各種自訂的struct和指標,很麻煩而且調試起來很困難;
2、做個基於COM的DLL Warpper。除非你很懂COM,不然還是用方法1吧;
3、用C++/CLI做Warpper。以前忽視了C++/CLI的存在,覺得這就是個怪胎,而且代碼看起來很醜陋。不過通過這次的項目,我發現原來是我太膚淺了。這是本人強烈推薦的方法,如果你有很多原來做的模組,想在轉向.net後還能接著用,那就聽我的,抽點時間來學學C++/CLI吧!
由於在C++/CLI可以同時編寫託管和非託管平台的代碼,因此用來做Warpper再合適不過了。關於C++/CLI的內容,我會另外開博來談,本篇文章重點還是說說在項目中的運用。
1、要想在C#裡識別你寫的類和方法,那麼它的參數就必須是託管平台能夠識別的類型。例如:
System::Int32 QRCodeWapperCLI::QRCodeWapper::DeCodeQRFromFile(String^ filename,[System::Runtime::InteropServices::OutAttribute] String^% outQRInfo)
這個聲明中託管類型有個^符號,實值型別可以不用區分;%符表示ref,再加上屬性[System::Runtime::InteropServices::OutAttribute] 就表示 out類型的參數。
2、託管字串String與非託管字串char *的轉換方法,可以參考以下代碼:
IntPtr iFilename=Marshal::StringToHGlobalAnsi(filename);
char *strFile= reinterpret_cast<char*>(static_cast<void*>(iFilename));
Marshal類是聯絡託管和非託管平台之間巨牛無比的類,有很多方法,大家可以看看MSDN,上面的資料很好。
反過來可以這樣:
char* s1 = "native string1";
wchar_t* s2 = L"native string2";
String^ str1 = gcnew String( s1 );
String^ str2 = gcnew String( s2 );
3、在託管和非託管的記憶體緩衝間複製資料,可以參考以下代碼:
cli::array<BYTE>^ bmpBytes2 =gcnew array<BYTE>(bfSize); //託管記憶體
pin_ptr<BYTE> p2 = &bmpBytes2[0];//非託管記憶體
::memcpy(p2, pMyBmp,Length * sizeof(BYTE) );
你確實沒有看錯,就是memcpy!
4、除了SYSTEM命名空間外,其他的名稱空間除了要使用using namespace語句聲明外,還要用using包含它的dll檔案!如要用上Drawing名稱空間,不僅要using namespace System::Drawing; ,還需要#using <system.drawing.dll>,不然編譯時間就會報錯。
5、調試時開啟C#的混合調試和C++的混合調試選項,就可以在同時對託管和非託管平台的代碼進行調試了。