项目组里准备带人做封装SDK,想找一个好的教程给他学习,可是网上找半天没有找到满意的,一狠心自己写了一个。

有什么问题大家看了尽量提出啊。

 

此文档阐述如何在.NET框架下调用C++写的DLL

 

1、             .NET的目标码和C++的目标码的区别和联系

所谓目标码是由源代码编译链接(生成)产生的二进制码,在vs中,使用“生成”命令即可把源代码生成为目标码,目标码的形式通常有可执行文件(*.exe)和动态链接库(*.dll)等。

为了理解.NET和C++的目标码的区别,首先要了解一下他们生成后的可执行模块在运行阶段的区别(本文提到的C++均指非.NET平台的C++语言,对应VS中的win32应用程序,而非C++.NET,下同)。

 

对于.NET应用程序,并非直接运行在CPU上,而是运行在.NET运行时之上。这有点类似JAVA字节码和JAVA虚拟机之间的关系。在这里,我通常把.NET运行时理解为.NET虚拟机,它提供一个平台,供.NET生成的目标码来运行。向下,.NET运行时把程序要做的事情转变为机器指令,交给CPU去执行。微软称.NET运行时为“公共语言运行时(CLR)”。这里的公共语言指的是.NET的程序目标码是一个中间语言(IL)(相对于JAVA的字节码),在.NET平台下,不管你的程序是C#、VB还是其他语言编写的,经过生成都会变成IL。而真正在.NET运行时上运行的程序码就是IL码。VS自带了一个IL反汇编工具(IL DASM),可以把.NET平台下的EXE或者DLL反汇编成IL码。

对于C++应用程序,其编译生成之后变成的就是基于本机CPU平台的机器指令,因此又称“本机码”、“本地代码”、“平台代码”等,英文是Native Code。运行时直接在CPU上执行。

我们把.NET目标码称作“托管的(Managed)”代码,因为其运行在.NET运行时之上,受其托管;把C++的目标码称为“非托管的(Unmanaged)”代码,因为其直接运行在CPU之上。

通过上面的分析还可以得出,.NET的效率要低于C++,因为它在运行的时候需要.NET运行时把IL转换成本机码,这需要耗一定的时间。但.NET的代码要明显地比C++的代码安全,因为其受到.NET运行时的托管,不直接控制CPU及底层硬件。

2、             C++DLL中内部是什么?

C++的DLL有两种主要的类型,一种是类似C的(使用extern “C”导出函数)的,另一种是完全C++的。二者的区别是,由于在C++中允许函数重载,在编译得到DLL中的函数入口点名称并不一定是函数的声明名称。这里主要介绍C的DLL,因此下文提到的DLL均指C的DLL。

DLL的内部有什么?DLL中包含我们常用的功能,这些功能被封装成一个一个的函数。实际上,DLL内部就是一系列的函数,使用VS自带的工具dumpbin可以查看这些函数。这些函数又有个名称,叫做“入口点(EntryPoint)”,入口点指的是调用程序的入口。通常情况下,EXE程序使用main函数作为入口点。而DLL中的每一个可供调用的函数都是入口点,其含义为,程序可以从这个函数进入DLL。这些作为入口点的函数又称为“导出函数”,其含义为DLL导出供外部使用的函数。

3、             .NET平台下使用DLL的导出函数

在.NET程序中调用C++的DLL,其实质并不是.NET的目标文件直接调用C++的DLL的导出函数。由前面的分析可知,.NET的目标码是运行在.NET运行时之上,而C++的DLL是直接运行在CPU之上,.NET的代码不可能突破.NET运行时这一层,因此不可能直接调用。微软提供了一种办法,即通过.NET运行时的支持来调用C++的DLL,这种支持称作“互操作(Interop:Inter-Operation缩写)”。在“互操作”中,实质上是 .NET运行时 去调用C++的DLL,而用户在.NET源代码中只要告诉.NET运行时如何去调用即可,这就是程序员的工作。

 

由上面的分析可知,程序员需要告诉.NET运行时如何调用C++的DLL。这需要用.NET运行时能看得懂的方式把C++的DLL的导出函数声明一遍。因为托管的环境和非托管的环境有很大的区别,程序员需要告诉.NET运行时,非托管环境下的函数签名(function signature)是什么样子的。函数签名包括函数的参数和返回值,以及参数的封送顺序等等。

下面是一个例子:

 

例子中,C++的数据类型char*在.NET中对应string,C++的数据类型int对应.NET中的Int32。关于C++和C#中的数据类型对应关系在网上可以查到,这里就不列举了。用户在.NET代码中若需要使用C++的DLL的GetUserName这个功能时,就调用GetUserName函数,表面上看起来好像用户在调用C++的DLL中函数,其实不然,用户调用的是C#中的声明,这个调用由.NET运行时来托管。.NET运行时接收到这个调用请求,把用户提交的Int32转化为本地的int,由.NET运行时去封送参数值,并调用C++的函数GetUserName,然后由.NET运行时接收返回值,并把类型从char*转化为string,然后返回给用户。这样用户就接收到函数的返回值了。

在.NET平台下使用C++的DLL的导出函数的过程中,有几个概念是非常重要的:

l  模块名(Module Name):通常是DLL文件的文件名;

l  入口点(Entry Point):通常为函数的名字,但有时会有例外(……);

l  函数签名(Function Signature):函数的参数和返回值,在.NET中声明的托管的函数签名一定要和非托管的本地DLL中的函数签名完全一致,否则调用会出错。

l  封送(marshaling):调用时的实参由.NET运行时负责封送到非托管的DLL,本地代码执行的返回值由.NET运行时负责封送回托管代码中。

 

posted on 2013-05-03 20:39  永恒的bluebird  阅读(934)  评论(0编辑  收藏  举报