序言
本文介紹一個C++如何調用C#開發的dll實例。
前言
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