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。如下图所示。

时间: 2024-10-27 01:35:02

C++如何调用C#开发的dll的相关文章

通过c#去调用c++开发的dll com 组件 -Import com

这个问题缠了我2个小时才弄出来,其实很简单.当对方提供一个dll给你使用时,你需要去了解这个dll 是由什么语言写的,怎么编译的,看它的编译类型.这样即使在没有头绪时,你可以先尝使用一些比较热门的编译工具去解析它.比如gcc,reflector,ILSpy 都行. 关于C++写出来的dll,理论上也是一个dll,但更准确定义应该称为是一个特殊的dll.普通的dll,比如NOPI.DLL,我们只需要在项目中去引用它再加个namespace就可以去使用它内部提供的方法.而c++编译出来的dll 则需

C#使用CLR/C++的DLL间接调用Native C++的DLL

C#使用CLR/C++的DLL间接调用Native C++的DLL 开发环境:win 7  VS2010 简介:C#的exe使用CLR/C++间接调用Native C++的DLL. 第一步:创建一个C#的Console Application工程-->命名“ConsoleApplication1”. 第二步:创建一个CLR/C++的工程,右击“ConsoleApplication1”上面的“Solution 'ConsoleApplication1'”-->Add-->NewProjec

PB调用C#编写的DLL

C#以其简单易用,功能强大深受大家喜爱.PowerBuilder作为C/S的MIS开发工具,十分简单灵活,开发时间短,开发及维护成本低,一直是中小企业信息管理系统的首选开发工具.但是PB的局限性限制了它进一步的发展,这个就不多说了,玩PB的朋友都清楚.PB如何调用C#写的DLL,这个兴趣一上来,就忍不住要解决它.经过多方查找资料加上自己写代码测试,算是解决这个难题.下面列出开发步骤及各种设置选项(开发工具VS2008SP1+PB9.0-8836) 首先我们打开VS2008,新建一个项目,如图 接

用C#.NET调用Java开发的WebService传递int,double问题,出现java无法获得值!

用C#.NET调用Java开发的WebService时,先在客户端封装的带有int属性的对象,当将该对象传到服务器端时,服务器端可以得到string类型的属性值,却不能得到int类型.double和DateTime类型的值(在服务端得到的均为null) 解决办法: VS2005封装WebService引用 用C#.NET调用Java开发的WebService时,先在客户端封装的带有int属性的对象,当将该对象传到服务器端时,服务器端可以得到string类型的属性值,却不能得到int类型.doub

用C#.NET调用Java开发的WebService传递int,double问题,出现java无法获

用C#.NET调用Java开发的WebService传递int,double问题,出现java无法获得值! 用C#.NET调用Java开发的WebService时,先在客户端封装的带有int属性的对象,当将该对象传到服务器端时,服务器端可以得到string类型的属性值,却不能得到int类型.double和DateTime类型的值(在服务端得到的均为null) 解决办法: VS2005封装WebService引用 用C#.NET调用Java开发的WebService时,先在客户端封装的带有int属

C#引用C++开发的DLL

.Net 开发中如果使用外部DLL,一般引用进来就可以了,最多引用前将DLL注册一下.最近做的项目中需要使用硬件厂家用C++开发的DLL,我还照原来的方式引用却报错,一步步用下来却发现原来还有这么多技巧需注意.下面是我所遇到的问题及解决的方法,希望能对大家有用. 1.在Visual Studio中引用C++写的DLL时报以下错误: 未能添加引用,请确保此文件可访问并且是一个有效的程序集或COM组件. 手工注册该DLL也报错:模块已加载,但找不到入口点DLLRegisterServer, 请确保X

vb做界面调用c编写的dll

没有真正的做过C++项目,如何在短时间内完成模型软件的方法,成为前段时间需要考虑的问题,通过vbs脚本到vb到gis一直到如今的建模软件,我想到用比较容易上手的吧vb来做界面,(网上有的一些前辈也是这么应用采纳的,极大的肯定了我的方向),核心计算部分用的是c编写的dll,计算引擎直接利用epanet,数据库上打算先放置一边,留着后续升级的时候进行采用,因为定位的是一种辅助调度分析的工具,因此想着先运行起来. vb环境:VB6.0(企业版) C开发环境:DEV C++ 数据库:SQL2008 vb

lua调用C++写的DLL实现“热更新”

原创作品,转载请注明来源是CSDN:http://blog.csdn.net/relar/article/details/38084689 开发游戏服务器往往有"热更新"的需求,就是在不停止服务程序的情况下,对服务程序进行升级.这里采用lua脚本桥接C++的模式.程序主框架用C++,程序的业务逻辑也是C++(具体的是C++写的DLL),这两者之间用LUA脚本语言进行桥接.当程序运行时,只要改变LUA脚本,即可以选择使用不同的DLL,以实现业务逻辑的升级更新. 上演示代码,代码分为三部分

C++调用C#生成的DLL文件的各种问题

C++调用C#生成的DLL文件: 首先选择建立一个C#的类库,然后再按照需求编写需要的函数 之后,对于C++调用过程需要注意的几点: 1.使用#using <....some.dll>指出DLL文件的位置来调用DLL 2.using namespace some 方便之后调用函数 3.Class1 ^c = gcnew Class1(); gcnew和new的区别(引自百度知道): gcnew返回的是一个句柄(Handle),而new返回的是实际的内存地址. gcnew创建的对象由虚拟机托管,