原文鏈接:https://blog.csdn.net/weixin_42420155/article/details/81060945
最近在使用Qt整合以前的一個工具時,發現有幾個dll是采用C#寫的,當然可以把C#寫的dll重新在Qt中用C++寫,雖然這是幾個C#寫的dll的函數接口使用的參數都比較簡單,只用了int和string類型等,但在函數內部卻還使用了C#的專用一些類,如果重寫還是很麻煩,就查找了一些文章,多數都是說采用把C#寫的dll采用COM注冊方式讓Qt調用,但是這樣又要重新編譯以前的C#項目,實在是很麻煩,所以是否有種方式可以直接利用以前C#寫的dll呢?
C#寫的dll是沒有dllMain入口函數的,是一種中間語言,需要.Net運行時進行做本地化工作,因此如果要調用C#寫的dll,需要依賴.Net運行時,然而Qt中還無法直接調用.Net運行時,最好的方式是能夠在Qt中直接調用C#dll的函數,但是Qt明顯只能調用C++寫的dll,所以就只能通過編寫一個C++的dll導出接口供Qt調用,這個C++編寫的dll對C#寫的dll進行封裝,這個C++的dll可以采用/CLR方式對C#編寫的dll進行引用的,即將C++編寫的dll中生成的.lib文件供Qt進行鏈接,由於該接口符合C++規范,所以Qt可以鏈接到對應的C++編寫的dll。為此,寫一篇Demo,希望對查找該資料的人有幫助吧。demo的過程如下:
編寫環境:Win7,Qt5.7.1(MSVC2013_x86),VS2013
1.打開VS2013,新建一個C#的Class Library項目(這里選擇的是.Net Framework 4),項目名為CSharpDll
2.由於默認沒有引入Forms等UI庫,先在reference中添加引用System.Windows.Forms以便可以在測試中使用MessageBox等


3.最終C#編寫的dll如下圖,命名空間為CSharpDll,公共類為CSharpClass
里面包含一個加法add,一個減法substract(為了測試指針,所以在減法的返回類型是void,而把計算結果通過ref參數c給返回),一個showBox方法(里面采用C#的MessageBox對話框顯示用戶輸入的參數字串)
4.對project進行release build,在release目錄下生成了CSharpDll.dll(待會用到)

5.關閉CSharpDll項目,另外新建一個C++ CLR類型的Class Library項目(選擇與C#項目相同的.Net Framework 4),項目名稱為CppDll

6.選擇Project->CppDll Properties...,在彈出的屬性頁面選擇“Add New Reference..”,點擊“browsing.”后選擇CSharpDll項目中release目錄下的CSharpDll.dll


7.選擇CSharpDll.dll后,可以看到在項目屬性的References中出現了CSharpDll這個Library

8.在CppDll項目中的CppDll.h中利用__declspec(dllexport)導出對應的3個接口函數add,substract,showBox,這里為便於區分,前面加上api_前綴對這3個函數封裝。需要using namespace System::Reflection,對於這里的api_showBox方法,其參數不能采用CSharpDll里面的showBox參數的string類型,而是使用const char* 類型
9.選擇release方式build CppDll項目,在release文件夾中生成了CppDll.lib和CppDll.dll文件,可以看到同時其也將引用的CSharpDll.dll也給拷貝到release文件夾中了

10.接下來在Qt中進行調用,在QtCreator中新建一個TestCSharpDll項目(MSVC2013 32bit)

11.將CppDll.lib拷貝到Qt的TestCSharpDll項目文件夾內

12.在mainwindow.ui中,設計調用api_add函數,api_substract函數和api_showBox函數的界面

13.分別對add,sub和showBox按鈕的clicked信號進行槽函數編寫,其調用對應的api_add,api_substract和api_showBox函數,需要注意兩點:
第一點是需要在mainwindow.cpp文件前通過#pragma comment(lib,“lib文件的絕對路徑”),這里的lib文件的絕對路徑需根據CppDll.lib的實際位置進行設置(在這里本機上為'E:/SourceCode/Qt/TestCSharpDll/CppDll.lib')(絕對路徑的分隔符最好采用'/',而不是'\')
第二點是需要在開始處通過 __declspec(dllimport)的方式將導出函數進行聲明,鏈接器會在CppDll.lib中去查找這3個函數的動態鏈接庫dll的入口 
14.選擇Build對Qt中的TestCSharpDll項目進行Debug構建,將CSharpDll.dll和CppDll.dll拷貝到項目生成的Debug目錄中
15.執行TestCSharpDll.exe,輸入add函數的兩個加法參數,substract函數的兩個減法參數,以及輸入showBox的QString參數(這里是QString,在槽函數里面會通過QString的toLocal8Bit().data()函數轉換為char*參數)進行測試,可看出調用成功,並且在showBox中,彈出的對話框標題是CSharpDll中寫的“這是C#的MessageBox”,而在內容中則是Qt中輸入的“這個字串是Qt界面輸入的”


16.后記:Qt中實際上是通過鏈接中間層C++ CLR編寫的.lib,而C++ CLR可以直接調用C#的dll,因此需要在Qt與C# dll中構建一個中間層,這個中間層既能被Qt中的C++代碼進行鏈接,同時該中間層又能訪問C# dll中的函數。所以最終Qt生成的項目中同一目錄下需要CppDll.dll和CSharpDll.dll。
自然會有人想到如果將CppDll項目采用生成靜態庫static library(.lib)形式,通過在Qt中鏈接該靜態庫,最終運行Qt程序時就可以不用在同一目錄下放置CppDll.dll,而只需要放置CSharp.dll即可。理論上是這樣沒錯,但是經過嘗試發現Qt還是無法通過靜態鏈接CppDll的方式。
具體實際原因不太確定,不過我猜原因可能是,Qt程序是純native c++代碼(unmanaged代碼),而CppDll.dll和CSharpDll.dll是managed代碼,因此Qt程序通過動態鏈接到CppDll.dll時,Qt程序本身是以native c++代碼執行,而在調用CppDll.dll中的代碼時,CppDll.dll中代碼的加載是以.Net運行時進行托管,也即CppDll.dll被加載時,是被.Net Framework進行host啟動的,然后CppDll.dll中的代碼可以進一步訪問CSharpDll.dll(此時CSharpDll.dll也是被.Net Framework進行host啟動的)。所以如果在Qt程序中將CppDll進行靜態鏈接,其生成的Qt程序中最后必然包括maneged代碼(即Qt程序中既有native又有managed代碼),最終就會造成Qt程序在運行中無法明確其啟動方式(按navtive啟動,里面的執行都是navtive方式,遇到managed代碼,無法正常調用;如果按.Net進行host啟動,則其native代碼無法被.Net host識別),從而導致鏈接失敗,當然,至於是否有其他方式繞過managed和unmanaged的限制並能以這種靜態鏈接方式進行構建,還需進一步查閱了。同時在將managed代碼編譯時,編譯器選項需要指定'/clr',然而這個'/clr'與qt需要的編譯指令'/EH'沖突,無法兼容,所以也無法靜態鏈接。
不過,估計這種靜態鏈接的做法也應該沒多少必要性了,無論是從維護角度還是運行角度。
相關源碼下載:鏈接: https://pan.baidu.com/s/1vuns3yKiqD-CFj4ZXM116w 密碼: i6p2
————————————————
版權聲明:本文為CSDN博主「CoastFF」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_42420155/article/details/81060945
