C#調用C++代碼遇到的問題總結

来源:http://www.cnblogs.com/neverstop/archive/2016/09/26/5901652.html
-Advertisement-
Play Games

最近在開發服務後臺的時候,使用c#調用了多個c++編寫的dll,期間遇到了一系列的問題,經過一番努力最後都一一解決了,在此做個總結,方便以後參考。主要有:類型對照問題、記憶體釋放問題、版本問題、編譯問題、資源載入問題、異常捕獲與問題定位、vs實時調試問題等。 ...


最近在開發服務後臺的時候,使用c#調用了多個c++編寫的dll,期間遇到了一系列的問題,經過一番努力最後都一一解決了,在此做個總結,方便以後參考,畢竟這些問題也都是很常見的,主要有以下問題:

類型對照問題

c#調用c++方法時,首先要在類中定義一個與c++方法對應的外部方法,因為該方法是用C#語言定義的,那麼肯定要弄清楚C#類型與c++類型如何對應,否則會導致調用失敗,關於這個問題其實不算什麼問題,網上有很多類型對照的文章,都有很詳細的對應列表,用的時候參考一下就可以了。還可以使用工具,自動根據c++方法簽名生成對應的C# import方法簽名,參考P/Invoke Interop Assistant。不過有一個問題還是要註意的,在x86模式下c#中的int對應c++中的int,而在x64模式下C#中的int是對應c++中的long,就這麼一個小小的變數類型,在不經意間可能就會導致c++代碼出錯。

還有一個問題是:托管的 PInvoke 簽名與非托管的目標簽名不匹配,可以在C#代碼的方法特性上加上CallingConvention.Cdecl。如下所示:

[DllImport("dllname.dll", CharSet = CharSet.Ansi, EntryPoint = "methodname", CallingConvention = CallingConvention.Cdecl)]

記憶體釋放問題

由於這個問題經常遇到,並且如果不能解決的話肯定不會再考慮使用該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為傳出參數

有的時候記憶體問題是純粹由於c++代碼導致的,一般遇到記憶體問題,我會用c++的測試工程再跑一遍,看看是否仍有該問題,如果是說明真是c++的bug了,可以通知同事去修改bug了。

記憶體問題有時候並不會體現的十分明顯,這需要我們更加細心的觀察日誌併發現導致問題的真正原因。我之前遇到該方面的一個問題,剛開始記憶體漲幅非常明顯,經過多次與開發該dll的同事溝通後,問題已經解決的差不多了,但是大量測試後發現記憶體還是會有一點上漲,雖然幅度很小,但第六感告訴我此中必有蹊蹺,這要是上線跑個幾天豈不是還得爆,後來我把每一次調用c++方法後當前進程占用的記憶體輸出到文件中,經過仔細觀察,發現絕大部分文件(文件內容要傳入c++方法中進行處理)都沒問題,記憶體都很平穩,但是有極小一部分文件在傳入c++方法後,會導致記憶體相比其他文件有一個明顯的增長,看來問題是出現在這些文件中,隨後把這些文件單獨放在一起進行迴圈調用,記憶體一下子就大幅增長了,後面就不用說了,問題當然解決了。因此,要保持記日誌的良好習慣,哪怕是在測試工程中

版本問題(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,紅框那裡會顯示

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

這個問題在運行時有時候會提示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中的方法。異常捕獲+事件查看器+日誌可以幫助開發者發現程式的大部分問題與原因。

其他問題

1、0X1A截斷全文的問題

這個是c++代碼讀取文件時可能會遇到的一個問題,雖然在調試某個問題的過程中發現了這個情況,但後來經開發dll的同事說問題的原因不是這個,這裡就僅此記錄一下吧,ifstream in("test.txt",'b');這樣加上第二個參數就不會截斷了。

2、vs實時調試造成iis進程一直等待的問題

兩次遇到這個問題都是在下班後出現的,當時也不知道什麼原因,後來通過windbg看了一下測試程式和w3wp進程的轉儲文件,通過!gle -all命令發現每個線程都在等待狀態,如下圖所示:

iis進程也是如此,本以為是代碼死鎖了,但是通過!locks命令也沒發現有任何異常(關於這個問題,可以參考 應用死鎖分析,當時有點懵,不知道是什麼造成了這種情況,後來發生一件事情讓我弄明白了為什麼,那是在快下班的時候,程式正好出現了一個異常(雖是異常,其實不會導致程式崩潰退出),這時伺服器上彈出了一個vs實時調試的提示視窗,我註意到iis的cpu使用率突然就降為0,測試程式的控制台也輸出了線程等待的消息,聯想到之前那些STATUS_WAIT_0的錯誤信息以及貌似死鎖的情況,我感覺到可能是iis終止了所有線程,在等待vs實時調試這個交互視窗的結束,由於平時都是在下班後才會開啟測試程式來驗證程式的穩定性,所以當彈出這個交互視窗時,一直不會有人去處理,線程不會一直這麼等下去,最後測試程式就退出了,iis也無法再繼續處理請求了,這個交互視窗也貌似消失了(為什麼用貌似,因為我沒有專門去留意,只是憑印象覺得之前沒見過),想到這我點了一下“取消調試”,程式繼續往下運行了,也不再阻塞了,所有在程式運行的時候,最好關閉VS的實時調試功能,以免造成不必要的問題。進入visual studio中,選擇【工具】->【選項】,點擊【調式】,在【實時】選項卡中把【本機】【腳本】【托管】三個對勾取消掉就可以了。

其實就算實時調試視窗不見了,我們也可以通過系統事件來找到一些蛛絲馬跡,如下圖所示,只不過很難僅憑這個事件就斷定問題的原因,因為伺服器上運行了多個w3wp實例,只能說通過這個情況增長一些經驗了。

其實還有一些問題,到現在有點記不清了,就不敢貿然憑殘存的那點記憶來描述了,以便造成不必要的誤解。對於遇到的問題,有些很明顯,有些很隱蔽,有些需要仔細分析,有些需要在大量測試的情況下才會發現,這裡只想說一句:測試很重要,工作需用心。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 每日指標的計算: 每日指標計算腳本是本系統大部分指標的基礎,設計十多個計算指標,以及三十多個取數指標,所以計算過程比較複雜,整個過程如下圖所示,下麵挑比較典型的幾種指標的計算詳細介紹。 1)日指標計算——數據輸入(系統計算參數的輸入): 日指標計算主要是從2個表控制項進行數據輸入,如下圖所示: 因為每 ...
  • 在本系統中,針對電量監測系統的數據計算是最複雜的,其中需要採用多種轉換以及公式計算,並且需要結合系統預置的一些計算參數以及每個台區的一些補充技術參數進行計算,計算的過程分為5大步驟(轉換): 1、 根據系統時間獲取到電量監測系統瞬時數據的表名(電量監測系統每天都會生成一個以日期命名的新表)。 2、 ...
  • 1. select count(*) from table; //統計元組個數 2. select count(列名) from table; //統計一列中值的個數 3. select count(*) from table where 欄位 = ""; //符合該條件的記錄總數 4. sql_c ...
  • 三、組織機構轉換(異構抽取) 上述講到的都是一些通用的表抽取作業程式,中間不做任何轉換,所以過程非常簡單和通用,下麵要開始進入需要數據轉換的部分,首先是從設備信息系統獲取組織機構部分,並轉換到mp平臺上,整個過程與之前的大致相同。 在最後一步導入數據之前會做組織機構類型對應,進入第3個轉換程式。 該 ...
  • 主要涉及:JOIN 、JOIN 更新、GROUP BY HAVING 數據查重/去重 ...
  • http://www.cnblogs.com/lyhabc/p/3533027.html 一般的交易系統裡面我們都會以自增列或交易時間列作為聚集索引列,因為一般這些系統都是寫多讀少 每天的交易數據會不停的插入到資料庫,但是讀取數據就沒有數據插入那麼頻繁 因為這些系統一般是寫多讀少,所以我們會選擇在自 ...
  • 本案例是一個小型數據抽取分析類系統,通過抽取數據共用中心中的配網台區(一個台區一個配變)的相關數據進行整合,完成有關台區的50多個欄位按照日、月、多月等維度的集中計算展示。新使用的控制項有:作業、轉換、檢驗欄位的值、使用javascript腳本驗證、等待、設置變數、表輸入、資料庫連接、表輸出(同構)、... ...
  • 瞭解: Yum:Yellowdog Updater,Modified的簡稱,起初由yellow dog發行版的開發者Terra Soft研發,用Python編寫,後經杜克大學的Linux@Duke開發團隊進行改進,遂有此名。Yum是一個shell前端軟體包管理器,基於RPM包管理,能夠從指定的伺服器 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...