一种轻量级的Win32应用程序二进制接口兼容方案

2019-06-11 03:39周海洋黄小大
计算技术与自动化 2019年1期

周海洋 黄小大

摘要:在COM组件技术的基础上,对Win32平台下应用程序的二进制接口跨编译器兼容问题进行了研究。通过利用Win32平台下COM技术规约针对对象内存分布的一致约定,结合C++语言虚函数表的特性,提出一种专用于Win32平台的应用程序二进制接口跨编译器兼容问题解决方法。并由该方法衍生出一系列二进制接口兼容的类,形成一整套解决方案。与传统Win32平台上使用C语言接口或COM组件来达到二进制接口兼容的方式不同,新方案采用精简的类和虚函数表来规范对象内存分布,借助Win32平台下编译器对COM技术的广泛支持,实现了应用程序接口在不同編译器下二进制级别的统一。同时,方案保持了原C++语言的面向对象特性,还具有简单、轻量级的特点。

关键词:Win32平台;二进制接口兼容;跨编译器;轻量级

中图分类号:TP311.1

文献标识码:A

“二进制兼容”即使用旧版本组件的应用程序,可以和该组件的新版本进行正常连接与调用,而无需进行重新编译[1]。不同编译器下开发的软件模块,即使在同一操作系统平台下,也难以做到二进制级别的兼容。

在Win32平台下,传统方法一般使用纯C语言接口或COM组件技术来实现跨编译器的二进制兼容。M给出了跨边界对象的特性[2],给跨边界对象调用指明了方向。Box等探讨了COM技术与C++的内在联系[3],为对象的跨编译器兼容奠定了基础。梁忠杰等给出了COM技术与动态连接库技术相结合的开发案例[4]。上述研究单纯关注跨边界对象或COM技术本身,对于同时跨边界、跨编译器问题倾向于使用COM技术解决。纯C语言接口不具有面向对象特性和丰富的类库,而COM技术对于中小型项目而言过于复杂。针对这些问题,依据Win32平台下COM技术原理,利用C++语言的虚函数表等特性,生成二进制接口兼容的组件,实现跨边界、跨编译器兼容,为中小型项目提供一种轻量级的解决方案。

2 建立定长的数据类型

相同的数据类型如bool,在不同架构或不同编译器下长度可能不同。由于bool类型被定义在标准中并由编译器厂商实现[5],不同实现数据长度可能不同,这给兼容增加了难度,所以它在函数以及类的公共接口的规格声明中很少使用。因此,实现二进制兼容的第一步,即建立定长的数据类型。

C99标准就提供了一组定长的类型[6],包括int8_t、int16_t、uint32_t等。通过在C++中引用头文件cstdint,可以结合typedef定义出定长整型数据类型。就浮点型数据类型而言,其长度和表示方式在各编译器下均一致,无需特殊考虑。64位长整型在实际编程中使用较少,亦不作定义。需特别注意的是布尔类型,为增强可移植性,统一将其定义为32位有符整型,即int32_t。数据类型定义详见表1:

3 设计符合COM标准的框架

COM(Component Object Model,组件对象模型),是由微软提出的一套软件接口规范,允许来自不同软件供应商的二进制组件,以一种定义良好的方式连接和通信[7]。COM标准使用C++虚函数表来实现对象的二进制兼容,各模块定义统一抽象接口由其它模块调用,将类的接口与实现相分离。其原理如图1所示。

模块的抽象接口生成全局唯一的虚函数表,提供给其它模块引用。其它模块通过接口指针或引用,取得该模块虚表地址,通过虚表地址结合函数偏移量,解析出模块内函数在内存中的指针,达到跨边界函数调用的目的。

一般而言,符合COM标准的抽象接口具有如下特征:

(1)接口是一个纯虚类,含有纯虚函数,且纯虚函数之间不存在函数重载;

(2)接口中尽量不包含实体函数,若需要使用实体函数则其必须为内联函数;

(3)对于需要导出的接口函数,其调用约定统一为_stdcall;

(4)析构函数不能为虚函数,需要额外定义函数用于析构资源;

遵循以上规则,设计出框架的基类,如图2所示:

抽象类IObject为所有框架类的基类,其中Destroy为受保护类型的纯虚函数,是模块析构资源的接口,起到替代析构函数的作用。IObject重载了delete操作符,使其调用Destroy函数。当外界对IObject或IObjectOtherA指针使用delete操作符时,将调用Destroy函数。若需要扩展接口,可继承IObject,以提供多种扩展功能,如IObjectOtherA的做法。

IObjectDelete作为辅助模板类,实现了资源的正确释放。该模板类所有函数均为内联函数,主要用于绑定各类扩展接口如IObjectOtherA等。同时,它实现了Destroy接口,并将其析构函数声明为虚,重载了delete操作符用于真正释放资源。IObject-Delete首先绑定扩展接口,其余IObject的实现类,如IObj ectlmplA等,通过继承IObjectDelete模板类,来实现具体的接口功能。IObjectDelete可以绑定不同接口,相应的也可以有IObjectlmplA、IOb-jectlmplB等不同实现。

框架调用的顺序图如图3所示,以IObjec-tOtherA为例进行说明。为了更清晰地表示调用关系,图3中IObject、IObjectOtherA和IObjectDelete以虚对象(外框为虚线)表示,在实际内存中均对应IObj ectlmplA对象。在进行对象析构时,在组件外部对基类指针进行delete操作,由于IObject重载了delete操作符,将调用其Destroy方法。此时通过虚函数表映射,跨越边界,映射至IObjectDelete的Destroy实现,在函数体中对this指针进行delete操作,触发对象的析构。此时,由于IObjectDelete类析构函数声明为虚,子类IObjectImplA的析构函数将优先调用,整个对象的析构流程自此开始。

4 构建STL辅助类

为了增加新的优化和特性,Win32平台下部分编译器对STL( Standard Template Library,标准模板库)的实现有意打破了不同版本间的二进制兼容性[8]。因此,在使用STL库时,使用不同版本编译器编译的目标文件和静态库不能在一个二进制文件中混用(EXE或DLL),并且不同版本编译的STL二进制对象不能在组件之间作为参数传递。

STL库在实际编程中,具有方便、快捷的特点。我们通过利用编译器原有的STL库实现,使用上文中提出的框架对其进行二次封装,构建一系列辅助类,实现了Win32平台下STL库的二进制兼容,使其能跨编译器使用。其类图如图4所示:

图4中主要对STL库中的string.vector和map类进行了二次封装,已能满足基本应用需求。其它标准容器的封装方法与此类似,不再赘述。需特别注意的是,上述辅助类的实现必需是内联实现,且函数的处理过程中不能抛出任何异常,不可使用任何运行期间类型信息( RTTI),只能使用返回值来返回异常状态。

辅助类的使用分为两种情况:一是从组件外部传递对象至组件内部,此时只需在栈上声明子类对象,再以基类接口指针或接口引用方式,传递给组件内部使用,使用完毕后对象资源将自动释放。另一种是由组件内部传递对象至组件外部,这种情况需针对特定对象提供CreatelObject函数类似的C语言创建接口,返回基类接口指针以供外部使用,并由外部负责该对象资源的释放。

5 方案效果验证

运用上述框架构建驱动模块和桩模块,在Win32平台下使用若干较为陈旧的编译器编译驱动和桩模块,通过不同编译器下驱动和桩模块的交叉调用结果,验证方案的可行性和有效性。其结果如表2所示:

表2中打勾的部分表示一种可用的兼容组合。从表2中可知,除少部分特别老旧的编译器如GCC2.9.5以外,框架在大部分编译器的组合下均能正常使用,基本达到了跨编译器二进制兼容的设计目标。

6 结论

详细介绍了一种Win32应用程序二进制兼容接口设计方法,利用COM技术的核心本质,继而建立了Win32平台下二进制兼容的程序框架,形成了一整套解决方案。该方案成功让组件在大部分编译器下达到二进制级别的兼容,實现了跨边界、跨编译器调用的目标,同时较COM组件更简单、更轻量级。本方案的不足之处在于,缺乏统一的组件注册管理机制,增加了组件调用和资源管理的复杂度,不适用于大型系统的设计。针对大型项目,由于缺乏统一的资源管理方式,该方案会增加程序设计的负担。

参考文献

[1] PONOMARENKO A.RUBANOV V.Automatic backward com-patibility analysis of software component binary interfaces[C].Shanghai:2011 IEEE International Conference on Computer Sci-ence and Automation Engineering (CSAE 2011),2011:167- 168.

[2]WILSON M.ImperfectC++[M].荣耀,刘未鹏,译,北京:人民邮电出版社,2006:110-111.

[3] BOX D.Essential COM[M]. Massachusetts:Addison WesleyLongman, 1998:14-20.

[4]粱忠杰,思敏,李婷.COM技术和动态链接库技术的应用研究[J].微计算机应用,2006,27( 6):701-703.

[5] ISO/IEC 14882:1998, Programming languages C++[S].New York:American National Standards Institute. 1998:76-77.

[6] ISOflEC 9899:1999,Programming languages C[S].New York:American National Standards Institute. 1999:254-259.

[7] PUGH B.The component object model: technical overview [EB/0L]. (1994 -12 -1) [2018.3.l].https://www.cs.umd.edu/-pugh/com/.

[8] DICANIO G.The perils of C++ interface DLLs[EB/OL].( 2016-7-11) [201 8.3.6].https://blogs.msmvps.com/gdicanio/201 6/07/11/the-perils-of-c-interface-dlls/.