1. 程式人生 > >[轉]C#呼叫C++ DLL

[轉]C#呼叫C++ DLL

在開發過程中經常需要在C#中呼叫C++編寫的DLL,中間碰到過一些問題,這裡做個總結,方便以後參考。

  • 型別對照問題
  • 記憶體釋放問題
  • 版本問題(x86與x64)
  • 編譯問題(靜態與動態)
  • 資源載入問題
  • 異常捕獲與問題定位

型別對照問題

  c#呼叫c++方法時,首先要在類中定義一個與c++方法對應的外部方法,因為該方法是用C#語言定義的,那麼肯定要弄清楚C#型別與c++型別如何對應,否則會導致呼叫失敗,關於這個問題其實不算什麼問題,網上有很多型別對照的文章,都有很詳細的對應列表,用的時候參考一下就可以了。還可以使用工具,自動根據c++方法簽名生成對應的C# import方法簽名,參考

P/Invoke Interop Assistant

  這裡有一個呼叫約定的問題:__cdecl是C/C++函式的預設呼叫約定,__stdcall是C#函式的預設呼叫約定,也是所有的Windows API的呼叫方式。

  有一個問題還是要注意的,在x86模式下c#中的int對應c++中的int,而在x64模式下C#中的int是對應c++中的long,就這麼一個小小的變數型別,在不經意間可能就會導致c++程式碼出錯。

  C#呼叫C++(Win32)時常用型別轉換總結

  

記憶體釋放問題

  由於這個問題經常遇到,並且如果不能解決的話肯定不會再考慮使用該dll了,這是一個可用性的問題。所以我在呼叫c++方法的時候,通常都會先批量跑一邊,通過日誌記錄下每呼叫一次方法後,當前程序所佔用的記憶體大小,這樣在執行一段時間以後,就能很清楚的看到記憶體是否持續增長,如果是的話就需要和編寫該dll的同事進行溝通,給他們提供測試資料,確認產生問題的原因。

  有時即使C++中的方法進行了記憶體釋放,並且在c++測試程式碼中已經沒有記憶體增長問題了,但是在C#中呼叫的時候記憶體還是會持續增長,該問題可能跟使用的場景有關,我這裡是因為呼叫了一個返回char *型別的c++方法,我直接用C#中的字串型別的一個變數接收了,結果發現記憶體總是釋放不了,後來讓同事把c++的方法更改了一下引數,然後在C#中用StringBuilder型別的變數作為引數傳入c++方法中來接收該方法的結果,這樣該記憶體問題就解決了。

C#
// 在C#中宣告與C++方法對應的dllimport方法
[DllImport("dllname.dll", CharSet = CharSet.Ansi, EntryPoint = "
Handle", CallingConvention = CallingConvention.Cdecl)] public static extern bool CPPMethod(string content,StringBuilder result); // 該變數用來接收c++方法的處理結果,作為傳出引數傳入c++方法,在構造的時候必須明確指定大小 // 如果不指定或者指定的大小不足,會導致c++方法出現空間分配不夠的異常 StringBuilder resultSB = new StringBuilder(length); string cppParam = "some content"; bool isSuccess = CPPMethod(cppParam,resultSB); // CPPMethod是與C++方法對應的dllimport方法 C++ // C++中的DLL函式原型,即:C#中要呼叫的方法,此處不再返回char *型別的結果,而是將結果放到傳出引數result中 extern "C" __declspec(dllexport) bool Handle(char* content, char* result); // result為傳出引數

版本問題(x86與x64)

  版本不匹配的話,在除錯時會提示正在載入格式不正確的dll。

  如果使用的是32位的c++版dll,需要把C#專案的編譯平臺設定為x86,如果使用的是64位的c++版dll,則設定為any cpu和x64都可以,這個需要自己根據實際情況對應好就可以了。

  如果程式對記憶體的使用比較高,最好將程式編譯為64位,因為32位程式對單程序的記憶體大小有限制,經測試最大不超過2G。因為我的程式剛開始使用的是32位的c++版dll,並且在執行時需要呼叫這些dll載入很多資源,載入完這些資源程序佔用的記憶體就差不多快2G了,所以總會莫名其妙的崩掉,甚至在載入的過程中就直接崩掉了,當時預感到是32位的問題,後來讓同事將dll重新編譯為64位後就沒有這個問題了。可以通過dumpbin命令判斷一個dll是32位還是64位,開啟vs開發人員命令提示,輸入:dumpbin /headers 你的dll路徑,例如:dumpbin /headers d:\test.dll,如下圖所示:

如果是32位dll,紅框那裡會顯示 

這裡有一個地方需要注意,預設asp.net專案在除錯時會執行在32位下的iisexpress程序中,如果你的專案是64位的,那麼需要在VS中將iisexpress配置為64位模式,如下圖所示:

編譯問題(靜態編譯與動態編譯)

  這個問題在執行時有時候會提示dll載入不成功,這個問題在不同的電腦上會有不同的體現,有的存在這個問題,有的就執行正常。而我本機就屬於正常的,部署的伺服器屬於出問題的。出現這個問題後,在確認程式碼無誤後,我用depends.exe這個工具查看了一下導致問題的那個c++版的dll都依賴什麼程式集,在出問題的機器上會提示有一些依賴的dll不存在,而這些dll在執行正常的機器上是存在的。下圖紅色框中的為某些機器上可能會缺少的dll:

  如果缺少相關dll,該條目的左邊會顯示出一個黃色的問號。這個問題可以採用靜態編譯進行解決,關於什麼是靜態編譯可以自行百度,總之就是將程式所依賴的dll編譯到程式集中,這樣即使其他機器不存在這些dll也可以正常運行了,靜態編譯可以在vs的專案屬性中進行設定

  預設是多執行緒 DLL(/MD),即:動態編譯,這裡更改為 多執行緒(/MT),即:靜態編譯。

  剛才的配置只能解決缺少MSVCP120.DLL和MSVCR120.DLL這一類問題,對於缺少MFC相關的dll,還要經過下面的配置:

  預設是使用標準Windows庫,這裡改為在靜態庫中使用MFC

資源載入問題(相對路徑與絕對路徑,dll中又呼叫其他dll載入資源)

  這個問題相對比較隱蔽,出現時不會丟擲異常,只能通過c++方法返回的狀態碼來判斷方法執行是否成功,要不是在這裡放了一個斷點,特意看了一下,可能就遺漏這個問題了。

  場景是這樣的:
  我在webservice中呼叫c++版dll中的一個初始化方法,該方法會載入一些資原始檔,我在vs中除錯執行的時候沒問題,釋出以後居然無法載入資源,貌似是路徑問題,我把資原始檔放到w3wp.exe的根目錄下倒是可以成功載入,放在其他目錄中就不行,遇到這個問題首先想到的可能是資源所在的目錄許可權不夠導致iis無法正常載入,因為之前有個同樣的問題就是這樣,但這次將資源所在的目錄更改為Everyone使用者的完全控制權限還是不行,並且該問題只出現在b/s專案中,c/s專案沒有這個問題。並且該目錄中存放了很多資原始檔,有好幾個c++版的dll都需要從這裡載入,其他幾個都沒問題,就這一個dll不行,看來不是許可權的問題。這時候又想是不是相對路徑的問題,那我改成絕對路徑吧,結果問題依舊,後來在技術群裡有個大牛說試試Directory.SetCurrentDirectory,趕緊修改程式碼,測試了一下確實好使了。程式碼如下:

// 儲存當前工作目錄
string currWorkPath = Directory.GetCurrentDirectory();
// 切換當前工作目錄
Directory.SetCurrentDirectory(resourcePath);
// 初始化進行資源載入
Init(resourcePath);  // 這裡要注意,使用了SetCurrentDirectory方法後,resourcePath要用相對路徑
// 還原當前工作目錄
Directory.SetCurrentDirectory(currWorkPath);

  如註釋所示,使用SetCurrentDirectory切換了當前工作目錄後,方法中所用的路徑要改為相對路徑,一開始我用的是絕對路徑,居然還是無法載入。

  後來發現了該問題的原因,在使用的dll中又呼叫另外一個dll進行資源載入,可能這樣會導致那個間接呼叫的dll出現路徑問題,所以出現資源載入失敗。

異常捕獲與問題定位

  關於異常捕獲,雖然在方法中添加了特性HandleProcessCorruptedStateExceptionsSecurityCritical但還是捕獲不到c++中的異常,原因可能是c++在遇到某些異常時會造成程式直接退出,這樣在C#中就自然捕獲不到了,所以還是儘量保證c++程式碼的健壯性。
如果在c#中呼叫了多個c++版dll中的方法,因為有時捕獲不到異常,很難通過常規方法找到問題的原因,c++方法中一旦出現異常可能會直接導致程序退出了,這時可以藉助作業系統中的事件檢視器來找出異常是來自哪個dll,同時在原有程式碼中註釋掉那段呼叫該c++方法的程式碼,或者mock一個方法呼叫,保證該段程式碼無異常,然後再進行測試,如果無異常,那麼只要解決了那個c++方法的問題即可,如果還有異常那麼就是其他dll的問題,然後可以編寫測試程式碼單獨測試曾經出問題的dll中的方法。異常捕獲+事件檢視器+日誌可以幫助開發者發現程式的大部分問題與原因。

 

  轉自

  https://www.cnblogs.com/neverstop/p/5901652.html#item1

  https://www.cnblogs.com/lidabo/archive/2012/06/05/2536737.html