《dll篇》c#生成dll供c++调用(待梳理)

C#生成dll并C++调用

原文链接:https://www.cnblogs.com/huangmianwu/p/6145044.html
其中的GUID和注册方式,可以不用参考。

前言

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

image

image

接下来创建继承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可见”,如下图所示:

image

“为COM互操作注册”设置步骤为:项目属性-->“生成”项-->勾选“为COM互操作注册”,如下图所示:
注意:此项操作需要提供系统管理员权限,启动VS时请以“管理员身份运行”,否则生成解决方案时会出现对注册表项XXX的访问被拒绝的错误。

image

生成解决方案后,会生成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

image

声明方法,代码如下:

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方法调用结果

image

Join方法调用结果
image

附件为程序源代码。仅供参考。
https://files.cnblogs.com/files/huangmianwu/UnmanagecodeCallManagecode.rar

C#生成dll并C#调用

原文链接:https://www.likecs.com/show-308282657.html

一、C#封装成DLL

1、在VS中创建项目选择类库,命名 myDll
image

2.建立好项目后自动生成的代码如下:
image

代码修改如下,添加自己要封装的C#代码,注意修饰符必须为pubic

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace myDLL
{
	public class Class1
	{
		//封装的DLL函数
		public int add(int x, int y)
		{
			return x + y;
		}
	}
}

3.点击项目生成解决方案,然后在项目目录的bin/debug下即可发现封装好的dll文件
image

注意:封装成DLL时程序集名字要跟程序里的 namespace 命名一致,如下图,否则应用DLL时无法引用成功。
image

image

二、C#中调用该DLL

1.新建Windows窗体应用程序
image

2.把DLL放在项目文件夹的bin目录的Debug目录下

3.点击项目里的引用添加DLL
image

image

image

添加完后可以在应用里看到DLL

image

4.使用using包含进去

using myDLL;

5、添加一个测试按钮,按下就调用该DLL的函数
image

跳转到按钮程序部分添加调用代码

Class1 test = new Class1(); //新建类
int a = test.add(1, 2); //调用DLL的函数
MessageBox.Show(" a = " + a);//查看调用结果

image

说明:C#封装的DLL是非标准的DLL(托管类),不可以用 DllImport 调用,DllImport是用来调用标准类(非托管类)的,这类DLL一般是用C++写的

image

生成dll供c++调用

组件对象模型

原文链接:https://www.cnblogs.com/jmllc/p/14979734.html

发展历史

原文链接:https://www.zhihu.com/question/49433640/answer/115952604

COM,组件对象模型,说是将死的技术,比较贴切。
1、COM主要是一套给C/C++的接口,当时微软的野心很大,COM也被推广到了VB等其他一大堆平台。
2、COM是为了C设计的,COM使用dll发布接口,COM导出的基本都是C的函数。从原理上说,将dll加载到内存后,会提供一组函数地址,call进去就可以调用相应函数。
3、但对于C++来说,这个事情就头疼了。假设:
你有一个类,我们知道使用一个类的第一步就是新建这个类,new MyClass()。这里直接就出问题了,new方法通过编译器计算MyClass的大小来分配相应的内存空间,设置MyClass内存空间后,但是如果库升级了,相应的类可能会增加新成员,大小就变了,那么使用原来定义分配出来的空间就不能在新的库中使用。
要解决这个问题,我们必须在dll当中导出一个CreateObject的方法,用来代替构造函数,然后返回一个接口。然而,接口的定义在不同版本当中也是有可能会变化的,为了兼容以前的版本同时也提供新功能,还需要让这个对象可以返回不同版本的接口。接口其实是一个只有纯虚函数的C++类,不过对它进行了一些改造来兼容C和其他一些编程语言。
在这样改造后,出问题还有析构过程~MyClass()或者delete myClass,因为同一个对象可能返回了很多个接口,有些接口还在被使用,如果其中一个被delete了,其他接口都会出错,所以又引入了引用计数,来让许多人可以共享同一个对象。
4、其实,如果到此为止也不算是很奇怪的技术,我们用C++的时候也会使用Factory方法来代替构造函数实现某些特殊的多态。COM技术的奇怪地方在于微软实在是脑洞太大了,它们构造了一个操作系统级别的Factory,规定所有人的Interface(接口)都统一用UUID来标识,以后想要使用哪个Interface(接口)只要报出UUID来就行了。这样甚至连链接到特定的dll都省了。
这就好比一个COM程序员,只要他在Windows平台上,调用别的库就只要首先翻一下魔导书,查到了一个用奇怪文字写的“Excel = {xxx-xxx-xxx}”的记号,然后它只要对着空中喊一声:“召唤,Excel!CoCreateInstance,{xxx-xxx-xxx}”。
然后,从呼出的魔法阵里面钻出来一个怪物,它长什么样我们完全看不清,因为这时候它的类型是IUnknow,这是脑洞奇大无比的微软为所有接口设计的一个基类。
我们需要进一步要求它变成我们能控制的接口形态,于是我们再喊下一条指令:“变身,Excel 2003形态!QueryInterface, {xxx-xxx-xxx}”。QueryInterface使用的是另一个UUID,用来表示不同版本的接口。于是怪物就变成了我们需要的Excel 2003接口,虽然我们不知道它实际上是2003还是2007还是更高版本。
等我们使唤完这只召唤兽,我们就会对它说“回去吧,召唤兽!Release!”但是它不一定听话,因为之前给它的命令也许还没有执行完,它会忠诚地等到执行完再回去,当然我们并不关心这些细节。
5、微软大概会觉得自己设计出了软件史上最完美的二进制接口,从今以后所有的第三方库都可以涵盖在这套接口之下。然而历史的车轮是无情的,它碾过那些自以为是的人的速度总是会比想象的更快。Java的直接基于类的接口被广泛应用,开发使用起来远远来的简单,即便偶尔出点问题大家也都想办法解决了,事实证明程序员并不愿意花10倍的编写代码的时间来解决二进制库的版本兼容问题,他们更愿意假装没看见。很快微软也抄了一个.NET托管dll的方案出来,于是纯的二进制接口COM就慢慢被抛弃了。
6、COM,OLE,ActiveX,OCX,VBScript,等诸多技术逐渐被冷落,但是,COM在Windows操作系统底层继续发挥余热的时间肯定还会很长,因为永远都会有偏底层的C++开发的需要,必须游戏之类。只是技术趋势已经很明确了。

posted @ 2023-04-13 11:04  Fusio  阅读(2046)  评论(0编辑  收藏  举报