基于C++/CLI实现托管代码与非托管代码的交互

2013-10-20 08:36冯士德
微型电脑应用 2013年1期
关键词:调用实例代码

冯士德

0 引言

由于托管代码与非托管代码在运行机制上存在差异,导致了这两者间无法实现直接的交互。虽然微软在.Net Framework中提供了P/Invoke技术来解决托管代码与非托管代码之间的互操作问题[1]。但是使用 P/Invoke的 DllImport方法,仅能实现在托管代码中调用非托管代码中的函数,而无法实现对非托管代码中类的引用。为了解决这个问题,通常做法是建立函数接口对非托管代码中的类进行包装,然后在托管代码中使用 P/Invoke方法,通过调用函数接口间接调用非托管代码中所定义的类。但是在托管代码与非托管代码互操作频繁的项目中,通过函数接口调用非托管代码中的类,将会破坏面向对象的编程思想,同时影响代码的执行效率。为了解决这一问题,本文就使用 C++/CLI语言实现在托管代码与非托管代码之间的交互进行了研究,并以 C#语言为例解释说明了其具体的实现的方法。

1 C++/CLI与托管/非托管代码

C++/CLI是标准 C++语言与CLI(Common Language Infrastructure)的集成。在代码编制方法上,它在保留了标准 C++语言的语法并对其进行了扩展以符合 CLI语言的要求。所以可以将C++/CLI语言简单的看作为标准C++语言的一个扩展。但在代码编译与执行的原理上,它却与标准C++语言截然不同,它遵守了CLI语言的规范。与其他CLI语言相同,C++/CLI语言在被编译时将会被编译成托管的微软中间语言(MSIL)代码,之后再由实时(JIT)编译器在执行时将中间语言代码编译为本机代码后执行[2]。不过又区别于其他CLI语言,C++/CLI代码中以#pragma unmanaged标记显式标注的代码段将被直接编译为本地二进制代码。所以C++/CLI代码在经过编译,最后所生成的是托管代码与非托管代码的混合程序集,如图1所示:

图1 C++/CLI代码编译机制

可见C++/CLI代码身处托管代码与非托管代码之间,其3者关系,如图2所示:

图2 C++/CLI、托管代码、非托管代码关系

C++/CLI代码,就好似在托管世界与非托管世界之间架起了一座桥梁,打通了两者之间的壁垒。通过在源代码层次的交互,C++/CLI提供了一个非常有价值的跨编程语言的集成方式。借助于这种方式,可以将非托管代码中的本地类在托管的世界中发布,这使得在同一个软件项目中,充分调动托管代码世界与非托管代码世界中,丰富软件资源成为了可能。

2 交互方法

由于托管代码与非托管代码的实现机制不同,所以在这两者间无法实现直接的函数调用或数据传递操作。而C++/CLI代码恰恰介于托管与非托管之间,所以以C++/CLI代码为中介,实现托管与非托管代码之间的交互,是一个可行的方法,也是本文所研究的重点。

2.1 基本思想

以C++/CLI代码为中介,实现托管代码与非托管代码交互的方式,如图3所示:

图3 托管代码与非托管代码交互方式

作为托管代码与非托管代码之间的中介,C++/CLI代码必须完成以下两项主要工作:

a) 在托管代码与非托管代码之间传递数据

b) 在托管代码与非托管代码之间转换内存地址

C++/CLI代码通过对非托管代码中的类,进行包装来完成以上两项工作。

首先,使用C++/CLI代码声明一个非托管类的包装类。此包装类中必须含有一个指向非托管类实例的指针,这样,包装类便能够通过这个指针,将托管代码对非托管类实例的操作请求,传递给非托管类实例。

然后,为包装类添加构造函数与析构函数。在构造函数中实现生成非托管类实例的操作,并将其地址赋给包装类中非托管类实例的指针。在析构函数中则应实现删除非托管类实例的操作。

最后,为包装类添加与非托管类中的公共成员属性及公共成员函数对应的成员属性与成员函数。当托管代码需要读写非托管类中的公共成员属性时,托管代码通过读写包装类中相应的成员属性间接实现读写操作。托管代码对非托管类中成员函数的调用操作亦是如此。

可见使用C++/CLI代码进行集成是一种代码级别的集成方式,通过 C++/CLI代码可以非常方便地对非托管代码中的类进行包装。所生成的包装类完全符合CLI代码规范,并可以在托管代码中自由地调用,因此能够实现托管代码与非托管代码之间的无缝集成。

2.2 交互方法举例

本节以C#语言为例,说明使用C++/CLI语言对非托管代码中的导出类进行包装的具体方法。并以非托管代码中的导出类UnmanagedClass为样例对其进行包装,其头文件声明如下:

UnmanagedClsss类中包括一个 int型的公共属性iParamA、一个公共函数Add以及相应的构造函数和析构函数。Add函数实现了将两个输入参数相加并返回结果的简单功能。这些函数与属性均为公共类型(pubulic)。由于非托管类中私有类型(private)的成员属性与成员函数仅在类的内部可见无需导出,所以在该例中省略了私有成员属性与私有成员函数。

为了将UnmanagedClass包装为托管代码中的类,使用C++/CLI代码声明 ManagedClass类对其进行包装。ManagedClass的头文件声明如下:

ManagedClass作为 UnmanagedClass被封装后的托管类,根据托管代码的规则,在声明ManagedClass时必须同时指定其namespace。在本例中将其设为“SampleSolution”。

在 ManagedClass中声明一个 protected的成员属性pInstance,并设置该成员属性为UnmanagedClass类的指针。当ManagedClass的构造函数被调用时,在构造函数中生成被包装类UnmanagedClass的实例,并将其地址保存于这个指针属性中。之后所有对ManagedClass的操作都将通过此指针传递给UnmanagedClass。ManagedClass的构造与析构函数如下:

为了将UnmanagedClass中的每个公共成员属性导出,在ManagedClass中为UnmanagedClass的每个公共成员属性建立一个对应的属性。对ManagedClass中的公共属性执行读写操作时,将实际的操作通过ManagedClass::pInstance指针传递给 UnmanagedClass实例,代码举例如下:

从示例代码中可知,当对ManagedClass的iParamA执行读操作时,实际返回的是由ManagedClass::pInstance所指UnmanagedClass实例中成员属性iParamA的值。对iParamA的写操作也类似,实际写入的是UnmanagedClass实例中成员属性iParamA。

为了将ManagedClass中的每个公共成员函数导出,也采取与导出公共成员属性相似的方法。在ManagedClass中为UnmanagedClass的每个公共成员函数声明一个对应的公共成员函数,代码举例如下:

当ManagedClass中的成员函数被调用时,此调用操作将通过 ManagedClass::pInstance指针找到 UnmanagedClass中所对应的成员函数,并将调用参数传递给它,然后执行该函数并将返回值返回给ManagedClass的成员函数,最后由ManagedClass的成员函数将返回值返回给函数调用者。

3 执行效率测试及其结果分析

为了进一步研究使用C++/CLI语言技术实现托管代码与非托管代码之间交互的方式是否会降低非托管代码执行效率的问题,本文以冒泡排序法为案例对该交互方式的代码执行效率进行了测试。

3.1 测试方法

首先以 C++语言编制一个基于非托管代码的 DLL文件,该DLL文件中包应含一个实现冒泡排序法的导出类。然后使用C++/CLI语言对算法DLL文件进行包装,生成一个介于托管与非托管之间的DLL文件。最后使用C#语言编写一个调用程序来调用这个DLL文件,以此实现冒泡排序的功能。

同时作为对比试验的参照物,使用 C++语言编写另一个调用程序。该程序直接调用非托管的算法DLL文件实现冒泡排序功能。

通过对比这两个分别由C#语言与C++语言实现的调用程序的执行时间,便可以判断经过包装后的非托管代码的执行效率是否会降低。所生成的实验用DLL文件与调用程序的结构,如图4所示:

图4 试验用程序结构

3.2 试验结果

为了得到相对准确的试验结果,使用 C#调用程序与C++调用程序分别执行对2万、4万、6万、8万、10万个随机数的排序操作,并分别记录其执行时间。

同时考虑到Windows是多线程操作系统,为了减少线程间调度对本次试验结果产生的影响,在执行测试程序前已关闭了所有其它应用程序。并且对各个数量级别的测试分别执行 10次并取其平均数作为最后的试验结果。在CPU 为2.4GHz、内存2G、Win7操作系统的普通台式机环境中,实际测试结果,如图5所示:

图5 试验结果对比

由图5中显示的试验数据可知,经过包装的非托管代码在托管代码中的执行效率与直接在非托管代码中的执行效率的差距在上下千分之三之内,在部分情况下甚至要稍高于直接在非托管代码中的执行效率。通过对比这两组试验数据,可以近似认为非托管代码在经过包装后的执行效率等同于包装前的执行效率,代码执行效率几乎不受包装影响。

4 结束语

当前微软.Net平台的发展势头正劲,托管代码的执行效率也正逐步逼近非托管代码,很多软件开发公司也都将.Net作为其主要产品开发平台。.Net平台的发展正按照微软的规划突飞猛进,好似无所不能。但是C++语言经过了这么多年的发展与积累,数以万计的程序员以C++语言开发了海量的应用。特别是以科学计算、底层硬件通信为代表的,对代码执行效率、系统响应速度有较高要求的应用,多以C++语言实现。如果仅以.Net为开发平台,将不得不放弃在以 C++语言为代表的非托管代码世界中现存的众多宝贵软件资源。而随着 C++/CLI语言的出现,它以一种极其简单且高效的方式,打通了托管世界与非托管世界之间的壁垒,为我们在.Net平台的开发中充分的利用现有非托管软件资源提供了有效的途径。

当前能够实现在托管代码与非托管代码之间交互的技术有很多,例如利用随机数据文件作为交互中介[3]等。但是每种交互方式都相对的存在其优缺点,在软件项目中必须根据具体需求来决定选取何种交互方法。所以针对各种实现托管代码与非托管代码交互技术之间优缺点的研究还值得继续深入。

[1]彭邦伦.C#托管代码调用非托管代码参数传递的实现方式.[J]软件导刊2011,10(1)

[2]郑阿奇.Visual C++ .NET 2010 开发实践-基于C++/CLI.[M]北京,电子工业出版社 2010年 12月 ISBN:978-7-121-12153-1

[3]何淼,崔松健.一种基于随机文件的C#与非托管C代码交互模式.[J]信息化研究2011,37(2)

[4]Jeffrey Richter.CLR via C#(第3版).[M]清华大学出版社 2010年9月 ISBN:978-7-302-23259-9

[5]钱能.C++程序设计教程.[M]清华大学出版社, 1999年4月 ISBN:7-302-03421-4

[6]蔡昭权.C#和C++数据传递的研究与实现.[J]计算机应用与软件2009,26(3)

猜你喜欢
调用实例代码
核电项目物项调用管理的应用研究
创世代码
创世代码
创世代码
创世代码
基于系统调用的恶意软件检测技术研究
完形填空Ⅱ
完形填空Ⅰ
利用RFC技术实现SAP系统接口通信
C++语言中函数参数传递方式剖析