周海洋 黃小大
摘要:在COM組件技術(shù)的基礎(chǔ)上,對Win32平臺下應(yīng)用程序的二進(jìn)制接口跨編譯器兼容問題進(jìn)行了研究。通過利用Win32平臺下COM技術(shù)規(guī)約針對對象內(nèi)存分布的一致約定,結(jié)合C++語言虛函數(shù)表的特性,提出一種專用于Win32平臺的應(yīng)用程序二進(jìn)制接口跨編譯器兼容問題解決方法。并由該方法衍生出一系列二進(jìn)制接口兼容的類,形成一整套解決方案。與傳統(tǒng)Win32平臺上使用C語言接口或COM組件來達(dá)到二進(jìn)制接口兼容的方式不同,新方案采用精簡的類和虛函數(shù)表來規(guī)范對象內(nèi)存分布,借助Win32平臺下編譯器對COM技術(shù)的廣泛支持,實現(xiàn)了應(yīng)用程序接口在不同編譯器下二進(jìn)制級別的統(tǒng)一。同時,方案保持了原C++語言的面向?qū)ο筇匦?,還具有簡單、輕量級的特點。
關(guān)鍵詞:Win32平臺;二進(jìn)制接口兼容;跨編譯器;輕量級
中圖分類號:TP311.1
文獻(xiàn)標(biāo)識碼:A
“二進(jìn)制兼容”即使用舊版本組件的應(yīng)用程序,可以和該組件的新版本進(jìn)行正常連接與調(diào)用,而無需進(jìn)行重新編譯[1]。不同編譯器下開發(fā)的軟件模塊,即使在同一操作系統(tǒng)平臺下,也難以做到二進(jìn)制級別的兼容。
在Win32平臺下,傳統(tǒng)方法一般使用純C語言接口或COM組件技術(shù)來實現(xiàn)跨編譯器的二進(jìn)制兼容。M給出了跨邊界對象的特性[2],給跨邊界對象調(diào)用指明了方向。Box等探討了COM技術(shù)與C++的內(nèi)在聯(lián)系[3],為對象的跨編譯器兼容奠定了基礎(chǔ)。梁忠杰等給出了COM技術(shù)與動態(tài)連接庫技術(shù)相結(jié)合的開發(fā)案例[4]。上述研究單純關(guān)注跨邊界對象或COM技術(shù)本身,對于同時跨邊界、跨編譯器問題傾向于使用COM技術(shù)解決。純C語言接口不具有面向?qū)ο筇匦院拓S富的類庫,而COM技術(shù)對于中小型項目而言過于復(fù)雜。針對這些問題,依據(jù)Win32平臺下COM技術(shù)原理,利用C++語言的虛函數(shù)表等特性,生成二進(jìn)制接口兼容的組件,實現(xiàn)跨邊界、跨編譯器兼容,為中小型項目提供一種輕量級的解決方案。
2 建立定長的數(shù)據(jù)類型
相同的數(shù)據(jù)類型如bool,在不同架構(gòu)或不同編譯器下長度可能不同。由于bool類型被定義在標(biāo)準(zhǔn)中并由編譯器廠商實現(xiàn)[5],不同實現(xiàn)數(shù)據(jù)長度可能不同,這給兼容增加了難度,所以它在函數(shù)以及類的公共接口的規(guī)格聲明中很少使用。因此,實現(xiàn)二進(jìn)制兼容的第一步,即建立定長的數(shù)據(jù)類型。
C99標(biāo)準(zhǔn)就提供了一組定長的類型[6],包括int8_t、int16_t、uint32_t等。通過在C++中引用頭文件cstdint,可以結(jié)合typedef定義出定長整型數(shù)據(jù)類型。就浮點型數(shù)據(jù)類型而言,其長度和表示方式在各編譯器下均一致,無需特殊考慮。64位長整型在實際編程中使用較少,亦不作定義。需特別注意的是布爾類型,為增強可移植性,統(tǒng)一將其定義為32位有符整型,即int32_t。數(shù)據(jù)類型定義詳見表1:
3 設(shè)計符合COM標(biāo)準(zhǔn)的框架
COM(Component Object Model,組件對象模型),是由微軟提出的一套軟件接口規(guī)范,允許來自不同軟件供應(yīng)商的二進(jìn)制組件,以一種定義良好的方式連接和通信[7]。COM標(biāo)準(zhǔn)使用C++虛函數(shù)表來實現(xiàn)對象的二進(jìn)制兼容,各模塊定義統(tǒng)一抽象接口由其它模塊調(diào)用,將類的接口與實現(xiàn)相分離。其原理如圖1所示。
模塊的抽象接口生成全局唯一的虛函數(shù)表,提供給其它模塊引用。其它模塊通過接口指針或引用,取得該模塊虛表地址,通過虛表地址結(jié)合函數(shù)偏移量,解析出模塊內(nèi)函數(shù)在內(nèi)存中的指針,達(dá)到跨邊界函數(shù)調(diào)用的目的。
一般而言,符合COM標(biāo)準(zhǔn)的抽象接口具有如下特征:
(1)接口是一個純虛類,含有純虛函數(shù),且純虛函數(shù)之間不存在函數(shù)重載;
(2)接口中盡量不包含實體函數(shù),若需要使用實體函數(shù)則其必須為內(nèi)聯(lián)函數(shù);
(3)對于需要導(dǎo)出的接口函數(shù),其調(diào)用約定統(tǒng)一為_stdcall;
(4)析構(gòu)函數(shù)不能為虛函數(shù),需要額外定義函數(shù)用于析構(gòu)資源;
遵循以上規(guī)則,設(shè)計出框架的基類,如圖2所示:
抽象類IObject為所有框架類的基類,其中Destroy為受保護類型的純虛函數(shù),是模塊析構(gòu)資源的接口,起到替代析構(gòu)函數(shù)的作用。IObject重載了delete操作符,使其調(diào)用Destroy函數(shù)。當(dāng)外界對IObject或IObjectOtherA指針使用delete操作符時,將調(diào)用Destroy函數(shù)。若需要擴展接口,可繼承IObject,以提供多種擴展功能,如IObjectOtherA的做法。
IObjectDelete作為輔助模板類,實現(xiàn)了資源的正確釋放。該模板類所有函數(shù)均為內(nèi)聯(lián)函數(shù),主要用于綁定各類擴展接口如IObjectOtherA等。同時,它實現(xiàn)了Destroy接口,并將其析構(gòu)函數(shù)聲明為虛,重載了delete操作符用于真正釋放資源。IObject-Delete首先綁定擴展接口,其余IObject的實現(xiàn)類,如IObj ectlmplA等,通過繼承IObjectDelete模板類,來實現(xiàn)具體的接口功能。IObjectDelete可以綁定不同接口,相應(yīng)的也可以有IObjectlmplA、IOb-jectlmplB等不同實現(xiàn)。
框架調(diào)用的順序圖如圖3所示,以IObjec-tOtherA為例進(jìn)行說明。為了更清晰地表示調(diào)用關(guān)系,圖3中IObject、IObjectOtherA和IObjectDelete以虛對象(外框為虛線)表示,在實際內(nèi)存中均對應(yīng)IObj ectlmplA對象。在進(jìn)行對象析構(gòu)時,在組件外部對基類指針進(jìn)行delete操作,由于IObject重載了delete操作符,將調(diào)用其Destroy方法。此時通過虛函數(shù)表映射,跨越邊界,映射至IObjectDelete的Destroy實現(xiàn),在函數(shù)體中對this指針進(jìn)行delete操作,觸發(fā)對象的析構(gòu)。此時,由于IObjectDelete類析構(gòu)函數(shù)聲明為虛,子類IObjectImplA的析構(gòu)函數(shù)將優(yōu)先調(diào)用,整個對象的析構(gòu)流程自此開始。
4 構(gòu)建STL輔助類
為了增加新的優(yōu)化和特性,Win32平臺下部分編譯器對STL( Standard Template Library,標(biāo)準(zhǔn)模板庫)的實現(xiàn)有意打破了不同版本間的二進(jìn)制兼容性[8]。因此,在使用STL庫時,使用不同版本編譯器編譯的目標(biāo)文件和靜態(tài)庫不能在一個二進(jìn)制文件中混用(EXE或DLL),并且不同版本編譯的STL二進(jìn)制對象不能在組件之間作為參數(shù)傳遞。
STL庫在實際編程中,具有方便、快捷的特點。我們通過利用編譯器原有的STL庫實現(xiàn),使用上文中提出的框架對其進(jìn)行二次封裝,構(gòu)建一系列輔助類,實現(xiàn)了Win32平臺下STL庫的二進(jìn)制兼容,使其能跨編譯器使用。其類圖如圖4所示:
圖4中主要對STL庫中的string.vector和map類進(jìn)行了二次封裝,已能滿足基本應(yīng)用需求。其它標(biāo)準(zhǔn)容器的封裝方法與此類似,不再贅述。需特別注意的是,上述輔助類的實現(xiàn)必需是內(nèi)聯(lián)實現(xiàn),且函數(shù)的處理過程中不能拋出任何異常,不可使用任何運行期間類型信息( RTTI),只能使用返回值來返回異常狀態(tài)。
輔助類的使用分為兩種情況:一是從組件外部傳遞對象至組件內(nèi)部,此時只需在棧上聲明子類對象,再以基類接口指針或接口引用方式,傳遞給組件內(nèi)部使用,使用完畢后對象資源將自動釋放。另一種是由組件內(nèi)部傳遞對象至組件外部,這種情況需針對特定對象提供CreatelObject函數(shù)類似的C語言創(chuàng)建接口,返回基類接口指針以供外部使用,并由外部負(fù)責(zé)該對象資源的釋放。
5 方案效果驗證
運用上述框架構(gòu)建驅(qū)動模塊和樁模塊,在Win32平臺下使用若干較為陳舊的編譯器編譯驅(qū)動和樁模塊,通過不同編譯器下驅(qū)動和樁模塊的交叉調(diào)用結(jié)果,驗證方案的可行性和有效性。其結(jié)果如表2所示:
表2中打勾的部分表示一種可用的兼容組合。從表2中可知,除少部分特別老舊的編譯器如GCC2.9.5以外,框架在大部分編譯器的組合下均能正常使用,基本達(dá)到了跨編譯器二進(jìn)制兼容的設(shè)計目標(biāo)。
6 結(jié)論
詳細(xì)介紹了一種Win32應(yīng)用程序二進(jìn)制兼容接口設(shè)計方法,利用COM技術(shù)的核心本質(zhì),繼而建立了Win32平臺下二進(jìn)制兼容的程序框架,形成了一整套解決方案。該方案成功讓組件在大部分編譯器下達(dá)到二進(jìn)制級別的兼容,實現(xiàn)了跨邊界、跨編譯器調(diào)用的目標(biāo),同時較COM組件更簡單、更輕量級。本方案的不足之處在于,缺乏統(tǒng)一的組件注冊管理機制,增加了組件調(diào)用和資源管理的復(fù)雜度,不適用于大型系統(tǒng)的設(shè)計。針對大型項目,由于缺乏統(tǒng)一的資源管理方式,該方案會增加程序設(shè)計的負(fù)擔(dān)。
參考文獻(xiàn)
[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技術(shù)和動態(tài)鏈接庫技術(shù)的應(yīng)用研究[J].微計算機應(yīng)用,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/.