1. 程式人生 > >C++如何調用C#開發的dll

C++如何調用C#開發的dll

framework html 優勢 顛覆 應用程序 通過 對話框 nbt code

轉載  http://www.cnblogs.com/huangmianwu/p/6145044.html

前言 C
++編寫的程序為非托管代碼,C#編寫的程序為托管代碼。托管代碼雖然提供了其他開發平臺沒有的許多優勢,但由於前期系統及歷史版本很多使用的是非托管代碼編寫的程序,所以CLR提供了一些機制,允許在應用程序中同時包含托管和非托管代碼。具體說分為以下三種: 托管代碼能調用DLL中的非托管函數。通過P/Invoke(Platform Invoke)機制調用DLL中的函數,如Kernel32.dll等。 托管代碼可以使用現有COM組件(服務器)。許多公司都已經實現了大量非托管COM組件。利用來自這些組件的類型庫,可創建一個托管程序集來描述COM組件。托管代碼可像訪問其他任何類型一樣訪問托管程序集中的類型。 非托管代碼可以使用托管類型(服務器)。許多現有的非托管代碼要求提供COM組件來確保代碼正確工作。使用托管代碼可以更簡單地實現這些組件,避免所有代碼都不得不和引用計數和接口打交道。比如C
++調用C#開發的dll。 以上部分文字摘自《CLR via C#》,會比較難懂點。剛好工作中有通過C++調用C#開發的dll的經驗,也就是上述第3點。所以想借此文記錄下開發的步驟和思路。後續有時間再把上述的1、2點補上,形成一個系列文章。 正文 1、用C#編寫dll 該dll只簡單實現兩個功能:字符串拼接和兩個數相加。先創建方法接口:Add和Join。代碼如下: 復制代碼 [Guid("254D1FBC-416B-422F-AE39-C923E8803396")] public interface ICalc { [DispId(1)]
bool Add(string a, string b, out int c); [DispId(2)] void Join(string a, string b, out string c); } 復制代碼 為了更全面地介紹調用的方法類型,在這裏專門把Add方法返回值定義為bool類型,結果通過輸出參數輸出,為int類型; Join方法無返回值(void類型),結果通過輸出參數輸出,為string類型。 其中DispId特性和GUID特性是必須的。DispId按順序編號即可。GUID的創建步驟為工具-->創建GUID-->選擇第5項,復制(針對VS2013)如下圖所示: 接下來創建繼承ICalc接口的Calc類,實現Add和Join方法,代碼如下。也需要創建GUID,步驟同上。 復制代碼 [Guid(
"F963B111-39FA-499D-9172-6102C79BB6E5")] [ClassInterface(ClassInterfaceType.None)] public class Calc : ICalc { public bool Add(string a, string b, out int c) { int int_a; int int_b; if (!Int32.TryParse(a, out int_a)) { c = 0; return false; } if (!Int32.TryParse(b, out int_b)) { c = 0; return false; } c = int_a + int_b; return true; } public void Join(string a, string b, out string c) { c = a + b; return ; } } 復制代碼 此外還需要設置“使程序集COM可見”和“為COM互操作註冊” “使程序集COM可見”步驟為:項目屬性-->“應用程序”項-->"程序集信息"-->勾選“使程序集COM可見”,如下圖所示: “為COM互操作註冊”設置步驟為:項目屬性-->“生成”項-->勾選“為COM互操作註冊”,如下圖所示: 註意:此項操作需要提供系統管理員權限,啟動VS時請以“管理員身份運行”,否則生成解決方案時會出現對註冊表項XXX的訪問被拒絕的錯誤。 生成解決方案後,會生成dll和tlb兩個文件。到此則已經完成C#端的工作了。 接下來介紹通過regasm.exe生成註冊表文件供使用者將dll註冊為COM組件。 2、註冊dll為COM組件 在本機開發時因為勾選了勾選“為COM互操作註冊”選項,所以生成解決方案時已經在本機將該dll註冊為COM組件,所以運行時不需再註冊, 但如果是在其他機器上運行時,需要將dll註冊為COM組件後才可使用。在這裏我們通過regasm.exe生成註冊表文件供使用者將dll註冊為COM組件(其實就是把GUID導入註冊表)。 腳本文件如下: regasm E:\博客園\UnmanagecodeCallManagecode\CalcClass\CalcClass\bin\Debug\CalcClass.dll regasm E:\博客園\UnmanagecodeCallManagecode\CalcClass\CalcClass\bin\Debug\CalcClass.dll /tlb: CalcClass.tlb regasm E:\博客園\UnmanagecodeCallManagecode\CalcClass\CalcClass\bin\Debug\CalcClass.dll /regfile: CalcClass.reg 註意使用的regasm.exe版本與開發dll所使用的.NET Framework版本最好保持一致。 運行該腳本生成CalcClass.reg文件。在其他機器上運行該文件,即可註冊該COM組件,才能正常使用。 接下來是如何將其封裝成COM組件的問題了。 3、將dll封裝成COM組件 新建工作空間,選擇Win32 Dynamic-Link Library,類型為簡單DLL工程。 將上述生成的dll和tlb兩個文件拷貝至工作空間文件路徑下。 在StdAfx.h頭文件下增加以下兩行代碼導入dll:(內容需要根據tlb文件名和命名空間做更改) #import "CalcClass.tlb" using namespace CalcClass; 在cpp文件中添加以下方法聲明(聲明為C編譯連接方式的外部函數),也可創建頭文件後包含進來。 extern "C"_declspec(dllexport)BOOL Add(char* a,char* b,long* c); extern "C"_declspec(dllexport)void Join(char* a,char* b,char* c); 實現聲明的兩個方法: 復制代碼 BOOL Add (char* a,char* b,long* c) { CoInitialize(NULL); CalcClass::ICalcPtr CalcPtr(__uuidof(Calc));//獲取Calc所關聯的GUID VARIANT_BOOL ret = CalcPtr->Add(_bstr_t(a),_bstr_t(b),c); CalcPtr->Release(); CoUninitialize(); if( ret == -1 ) return 1; else return ret; } void Join (char* a,char* b,char* c){ CoInitialize(NULL); CalcClass::ICalcPtr CalcPtr(__uuidof(Calc));//獲取Calc所關聯的GUID BSTR temp; CalcPtr->Join(_bstr_t(a),_bstr_t(b),&temp); strcpy(c , _com_util::ConvertBSTRToString(temp)); CalcPtr->Release(); CoUninitialize(); } 復制代碼 這裏做兩點說明: 1、對於VARIANT_BOOL類型做個簡單介紹:-1表示true,0表示false。(這點確實顛覆了我們對bool值的常規理解) 2、C#的out參數轉換為C++時必須傳指針變量,也就是說傳參時須對變量進行取指操作,這也是輸出參數的本質。(可以通過tlb文件參考調用,或者生成後參考查看tli或tlh文件) 編譯成功後則完成了dll封裝為COM組件的任務。至此,C++即可調用C#編寫的dll了。下面將展示一個調用的DEMO示例。 4、調用DEMO示例 新建工作空間,選擇Win32 exe,類型為對話框。設計界面如下所示,添加按鈕事件OnAddbtn和OnJoinbtn 聲明方法,代碼如下: typedef BOOL (* Add)(char* a,char* b,long* c); typedef void (* Join)(char* a,char* b,char* c); OnAddbtn事件響應代碼如下: 復制代碼 void CCalcComDemoDlg::OnAddbtn() { // TODO: Add your control notification handler code here BOOL ret; long result; char A[255]; char B[255]; CString str_A; CString str_B; GetDlgItem(IDC_EDIT1)->GetWindowText(str_A); GetDlgItem(IDC_EDIT2)->GetWindowText(str_B); strcpy(A,str_A); strcpy(B,str_B); HINSTANCE calc; calc = LoadLibrary(TEXT("CalcCom.dll")); if (NULL == calc) { MessageBox("cant‘t find dll"); return; } Add _Add=(Add)::GetProcAddress(calc,"Add"); if (NULL == _Add) { MessageBox("cant‘t find function"); return; } else { ret = _Add(A,B,&result); CString boxMsg; boxMsg.Format("Reslut: %d\nMessage:%ld\n",ret,result); MessageBox(boxMsg); } } 復制代碼 OnJoinbtn事件響應代碼如下: 復制代碼 void CCalcComDemoDlg::OnJoinbtn() { // TODO: Add your control notification handler code here char A[255]; char B[255]; CString str_A; CString str_B; GetDlgItem(IDC_EDIT1)->GetWindowText(str_A); GetDlgItem(IDC_EDIT2)->GetWindowText(str_B); strcpy(A,str_A); strcpy(B,str_B); char result[255]; HINSTANCE calc; calc = LoadLibrary(TEXT("CalcCom.dll")); if (NULL == calc) { MessageBox("cant‘t find dll"); return; } Join _Join=(Join)::GetProcAddress(calc,"Join"); if (NULL == _Join) { MessageBox("cant‘t find function"); return; } else { _Join(A,B,result); CString boxMsg; boxMsg.Format("Message:%s\n",result); MessageBox(boxMsg); } } 復制代碼 這裏用的是LoadLibrary(TEXT("CalcCom.dll")),默認為exe執行路徑下的dll。所以編譯完成後將上述生成的COM組件dll拷貝到exe執行路徑下。當然也可直接指定dll的路徑。 運行程序即可驗證是否成功調用C#編寫的dll。如下圖所示。 Add方法調用結果 Join方法調用結果 附件為程序源代碼。僅供參考。 http://files.cnblogs.com/files/huangmianwu/UnmanagecodeCallManagecode.rar

C++如何調用C#開發的dll