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


序言

本文介紹一個C++如何調用C#開發的dll實例。

前言

C++編寫的程序為非托管代碼,C#編寫的程序為托管代碼。托管代碼雖然提供了其他開發平台沒有的許多優勢,但由於前期系統及歷史版本很多使用的是非托管代碼編寫的程序,所以CLR提供了一些機制,允許在應用程序中同時包含托管和非托管代碼。具體說分為以下三種:

  1. 托管代碼能調用DLL中的非托管函數。通過P/Invoke(Platform Invoke)機制調用DLL中的函數,如Kernel32.dll等。
  2. 托管代碼可以使用現有COM組件(服務器)。許多公司都已經實現了大量非托管COM組件。利用來自這些組件的類型庫,可創建一個托管程序集來描述COM組件。托管代碼可像訪問其他任何類型一樣訪問托管程序集中的類型。
  3. 非托管代碼可以使用托管類型(服務器)。許多現有的非托管代碼要求提供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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM