《電子技術(shù)應(yīng)用》
您所在的位置:首頁 > 嵌入式技術(shù) > 設(shè)計應(yīng)用 > 利用C++ ATL技術(shù)實現(xiàn)反射機制
利用C++ ATL技術(shù)實現(xiàn)反射機制
來源:微型機與應(yīng)用2013年第2期
金百東, 李文舉
(遼寧師范大學(xué) 計算機與信息技術(shù)學(xué)院, 遼寧 大連 116081)
摘要: 編制靈活的應(yīng)用程序框架系統(tǒng),反射機制是重要的實現(xiàn)手段。但由于C++本身沒有成熟的反射技術(shù),對此進行了深入研究并提出一種實現(xiàn)方法。首先論述了反射機制的作用;然后描述了ATL動態(tài)鏈接庫實現(xiàn)反射機制的基本原理,完善了ATL IDL文件接口標(biāo)識符定義,利用前綁定或后綁定技術(shù)實現(xiàn)反射機制,比較了這兩種方法的不同之處,著重強調(diào)了Idispatch接口在實現(xiàn)反射機制中的作用,最后給出需要繼續(xù)研究的問題。由于采用了動態(tài)鏈接庫技術(shù),方便了構(gòu)件的維護和復(fù)用,有助于團隊開發(fā)和分布式開發(fā)。
Abstract:
Key words :

摘  要: 編制靈活的應(yīng)用程序框架系統(tǒng),反射機制是重要的實現(xiàn)手段。但由于C++本身沒有成熟的反射技術(shù),對此進行了深入研究并提出一種實現(xiàn)方法。首先論述了反射機制的作用;然后描述了ATL動態(tài)鏈接庫實現(xiàn)反射機制的基本原理,完善了ATL IDL文件接口標(biāo)識符定義,利用前綁定后綁定技術(shù)實現(xiàn)反射機制,比較了這兩種方法的不同之處,著重強調(diào)了Idispatch接口在實現(xiàn)反射機制中的作用,最后給出需要繼續(xù)研究的問題。由于采用了動態(tài)鏈接庫技術(shù),方便了構(gòu)件的維護和復(fù)用,有助于團隊開發(fā)和分布式開發(fā)。
關(guān)鍵詞: 反射機制;前綁定;后綁定;分布式開發(fā)

    眾所周知,Java語言有成熟的反射機制[1-2],主要提供了以下功能:(1)在運行時判斷任意一個對象所屬的類;(2)在運行時構(gòu)造任意一個類的對象;(3)在運行時判斷任意一個類所具有的成員變量和方法;(4)在運行時調(diào)用任意一個對象的方法; (5)生成動態(tài)代理。由于在struts、hibernate、spring框架中大量采用了反射機制,因此反射機制在編制框架類應(yīng)用程序中大量運用,其作用是巨大的。但是C++本身并沒有現(xiàn)成的反射機制,因此,這也成為了C++研究領(lǐng)域中的熱門話題,其中絕大多數(shù)人研究了C++下統(tǒng)一類(即所有類定義及實現(xiàn)都在一個工程中)的反射機制問題,但目前流行的開發(fā)方式是“主程序+動態(tài)鏈接庫方式”。本文旨在對該問題下的C++反射機制加以研究。
    為了便于說明問題,特以圖1為例。定義父類接口IFruit, 定義多態(tài)函數(shù)Draw(), 兩個類Apple、Pear實現(xiàn)了該接口,并重寫了多態(tài)函數(shù)。本文以該示例為基礎(chǔ)對C++動態(tài)鏈接庫反射機制加以論述來實現(xiàn)。
1 反射機制研究具體內(nèi)容
    由于反射機制主要應(yīng)用于框架程序,而框架程序動態(tài)信息往往封裝在配置文件中,配置文件中內(nèi)容均是字符串,如封裝了的類名信息等。因此,本文研究的主要內(nèi)容是:(1)根據(jù)類名,動態(tài)產(chǎn)生該類的一個對象實例;(2)根據(jù)函數(shù)名及參數(shù)信息,動態(tài)執(zhí)行該對象實例的相應(yīng)函數(shù)[3-4]。
2 實現(xiàn)思想
2.1動態(tài)鏈接庫選擇

    本文選擇的是ATL動態(tài)鏈接庫。這是由于ATL本身提供了許多優(yōu)秀的功能,例如:(1)AppWizard,它負(fù)責(zé)創(chuàng)建初始的ATL工程;(2)Object Wizard(對象向?qū)?,它為基本的COM組件創(chuàng)建代碼;(3)對低級別的COM功能的內(nèi)置式支持,如IUnknown、類工廠和自注冊功能;(4)支持Microsoft的接口定義語言,它提供了對自定義的虛函數(shù)表Vtable接口的調(diào)度支持,以及通過類型庫進行自描述的功能;(5)支持IDispatch自動化接口及雙向接口。
    也就是說,運用AppWizard及Object Wizard產(chǎn)生的ATL動態(tài)鏈接庫初始代碼隱含了許多系統(tǒng)功能,可以直接或間接應(yīng)用,而且代碼質(zhì)量都是專家級的,避免了人為的低層次的重復(fù)開發(fā)。
    根據(jù)圖1功能,需要分別產(chǎn)生兩個ATL動態(tài)鏈接庫工程,假設(shè)名稱為MyApple及MyPear, 再分別插入IFruit接口,聲明Draw函數(shù);完善對應(yīng)的實現(xiàn)類Apple及Pear,實現(xiàn)具體的Draw函數(shù)功能。
2.2 完善ATL  IDL接口標(biāo)識副符定義
    由于ATL一個功能類中可能實現(xiàn)多個接口,ATL使用全局特有標(biāo)識符uuid來標(biāo)識類及接口。uuid是一個獨有的、128 bit、并且具有非常高可靠率的數(shù)值,它把一個獨有的網(wǎng)絡(luò)地址和一個非常精細(xì)的時間印簽(100 ns)結(jié)合在一起。因此,一個類對應(yīng)一個uuid,所實現(xiàn)的各個接口對應(yīng)各自的uuid。
    根據(jù)圖1產(chǎn)生的工程MyApple、MyPear,它們產(chǎn)生的類標(biāo)識符uuid一定是不同的。由于它們都實現(xiàn)了IFruit接口,希望IFruit接口對應(yīng)的uuid相同,但默認(rèn)情況下它們是不同的,因此,最好把它們改成相同的,只須修改相應(yīng)的接口定義IDL文件即可。下面截取了MyPear.idl關(guān)鍵部分代碼,如果以MyApple.idl為基準(zhǔn),則只須把接口標(biāo)識符對應(yīng)內(nèi)容修改為MyApple.idl文件中的內(nèi)容即可。這樣,就形成了完善的ATL多態(tài)組件。

    MyPear.idl關(guān)鍵部分代碼如下:
    接口標(biāo)識符:
[
    object,
    uuid(908F5798-4D39-4389-A426-43A23F3F5772),
    dual,
    helpstring("IFruit Interface"),
    pointer_default(unique)
]
interface IFruit : IDispatch
{
    [id(1), helpstring("method Draw")] HRESULT Draw();
};
    類標(biāo)識符:
[
    uuid(82B621A0-6224-45D9-9ECA-B83B835B63A6),
    helpstring("Pear Class")
]
coclass Pear
{
    [default] interface IFruit;
};
2.3 動態(tài)加載組件
    客戶端可通過前綁定或后綁定技術(shù)加載并執(zhí)行ATL動態(tài)鏈接庫組件。下面以圖1組件為例加以說明。
    (1)前綁定加載并執(zhí)行組件。主要代碼如下所示:
IID IID_IFruit={0x908F5798,0x4D39,0x4389,
    {0xA4,0x26,0x43,0xA2,0x3F,0x3F,0x57,0x72}};
                                           //接口uuid
CLSID CLSID_Apple={0x59CA74CB,0x7AC2,0x4F88,{0x92,0xFF,
0x5B,0x91,0xA6,0xC0,0xC2,0x88}};                  //Apple uuid
CLSID CLSID_Pear= {0x82B621A0,0x6224,0x45D9,{0x9E,0xCA,
0xB8,0x3B,0x83,0x5B,0x63,0xA6}};                    //Pear uuid
interface IFruit : public IDispatch         //定義多態(tài)接口IFruit
{
public:
        virtual HRESULT STDMETHODCALLTYPE Draw()=0;   
};
void main( )
{
     CoInitialize(NULL);                     //初始化COM庫
     IFruit *p = NULL;
     CoCreateInstance(CLSID_Apple,NULL,
          CLSCTX_INPROC_SERVER,
       IID_IFruit, (LPVOID *)&p);
     p->Draw();
     p->Release();
     CoUninitialize();                         //卸載COM庫
}
    可知若實現(xiàn)前綁定技術(shù),客戶端需完成以下工作:①從組件IDL文件中提取類標(biāo)識符uuid及接口標(biāo)識符uuid放在客戶端。對圖1組件來說,由于在2.2節(jié)中已經(jīng)使各個IFruit接口的uuid值相同,因此提取出了兩個類標(biāo)識符CLSID_Apple、CLSID_Pear, 以及一個接口標(biāo)識符IID_
IFruit;②客戶端定義多態(tài)接口Ifruit;③通過CoCreateInstance加載所需組件并執(zhí)行相應(yīng)函數(shù)。該函數(shù)的第1個參數(shù)是類標(biāo)識符uuid,第4個參數(shù)是接口標(biāo)識符uuid。其基本功能是產(chǎn)生相應(yīng)組件的一個對象實例,這其實是實現(xiàn)C++反射機制的核心所在。若沒有該函數(shù),則一定要編制一個組件工廠類,在眾多組件中根據(jù)條件選擇產(chǎn)生相應(yīng)的組件對象。由于編制的工廠類不是系統(tǒng)內(nèi)嵌部分,穩(wěn)定性、完善性都存在許多未知的因素。而現(xiàn)在這一切都被CoCreateInstance函數(shù)隱藏了,它屬于系統(tǒng)內(nèi)嵌的部分,編程者無須考慮它的完備性, 只須調(diào)用系統(tǒng)提供的接口即可。
    CoCreateInstance是通過類標(biāo)識符CLSID_xxx來創(chuàng)建組建對象的,該標(biāo)識符不好記憶,但在該組件向注冊表注冊時,同時注冊了一個 ProgID字符串,它是對CLSID_xxx的文字說明,是一一映射關(guān)系。因此,在產(chǎn)生ATL組件時添好ProgID字符串,之后,在程序中通過ProgID字符串即可獲得CLSID_xxx,如下述代碼所示,運用CLSIDFromProgID函數(shù)可完成所需信息的轉(zhuǎn)換。也就是說,可通過反射機制所要求的“類名”來加載相應(yīng)組件。
    CLSID clsid;
    CLSIDFromProgID(L"Apple",&clsid);
    (2) 后綁定加載并執(zhí)行組件。主要代碼如下:
    IID IID_IFruit={0x908F5798,0x4D39,0x4389,{0xA4,0x26,
0x43,0xA2,0x3F,0x3F,0x57,0x72}};              //接口uuid
     CLSID CLSID_Apple={0x59CA74CB,0x7AC2,0x4F88,{0x92,
0xFF,0x5B,0x91,0xA6,0xC0,0xC2,0x88}};          //Apple uuid         CLSID CLSID_Pear={0x82B621A0,0x6224,0x45D9, {0x9E,
0xCA,0xB8,0x3B,0x83,0x5B,0x63,0xA6}};           //Pear uuid
    void main( )
    {
         CoInitialize(NULL);                     //初始化COM庫
     IDispatch *p = NULL;
     CoCreateInstance(CLSID_Apple, NULL,
       CLSCTX_INPROC_SERVER,
          IID_IDispatch, (LPVOID *)&p);
     LPOLESTR lpOleStr = L"Draw";
     DISPID dispid;
     DISPPARAMS disp = {NULL, NULL, 0, 0};
     VARIANTARG vaResult;
     VariantInit(&vaResult);
     p->GetIDsOfNames(IID_NULL, &lpOleStr, 1,
    LOCALE_USER_DEFAULT, &dispid);
     p-Invoke(dispid,IID_NULL, LOCALE_USER_DEFAULT,
              DISPATCH_METHOD, &disp, NULL, 0, NULL);
     p->Release();
     CoUninitialize();                                    //卸載COM庫
    }
       可知若實現(xiàn)后綁定技術(shù),客戶端需完成以下工作:①從組件IDL文件中提取類標(biāo)識符uuid及接口標(biāo)識符uuid放在客戶端,同前綁定;②無需定義IFruit多態(tài)接口,只需通過CoCreateInstance獲得系統(tǒng)固有的IDispatch接口即可,該函數(shù)的第1個參數(shù)仍然是類標(biāo)識符對應(yīng)的uuid,第4個參數(shù)是ATL固有的IDispatch接口標(biāo)識符IID_IDispatch,而不是IID_IFruit;③創(chuàng)建所調(diào)用函數(shù)需要的函數(shù)參數(shù)結(jié)構(gòu)體DISPPARAMS,示例中Draw函數(shù)是無參的,所以DISPPARAM結(jié)構(gòu)體值非常簡單;④定義返回值參數(shù),如示例中的VARIANTARG vaResult;⑤通過系統(tǒng)函數(shù)GetIDsOfNames獲得Draw函數(shù)的分配號;⑥通過系統(tǒng)函數(shù)Invoke隱式地啟動Draw函數(shù)。
    同前綁定相比,由于不需要在客戶端定義多態(tài)接口,所以稱之為后綁定。后綁定技術(shù)主要應(yīng)用了IDispatch自動化技術(shù),其原理如圖2所示。

 

 

    當(dāng)客戶端獲得一個有效IDispatch指針后,就可以操作IDispatch虛函數(shù)表中的7個函數(shù)。如可通過Invoke函數(shù),同時根據(jù)組件功能函數(shù)在派發(fā)表中的函數(shù)號(函數(shù)號可在IDL文件中看出),即可間接調(diào)用組件中相應(yīng)函數(shù)。因此如果一個組件中有多個函數(shù),可以用相同的Invoke方法來調(diào)用。與Java反射Method類中的invoke方法非常相似。
    同前綁定相比,后綁定技術(shù)需要在運行時做大量的工作,所以其運行速度稍慢,但它的靈活性也是最高的。如果服務(wù)器接口發(fā)生變化,則客戶機程序不用重新編譯就可以執(zhí)行服務(wù)器新增的功能[5]。
    本文論述了利用ATL動態(tài)鏈接庫實現(xiàn)反射機制的基本原理,主要優(yōu)點有以下兩方面內(nèi)容:
    (1)利用類、接口標(biāo)識符及CoCreateInstance函數(shù),可方便產(chǎn)生組件的對象實例,省去了人為工廠類的編制。
    (2)利用IDispatch自動化接口,使用統(tǒng)一方法 Invoke屏蔽了組件接口函數(shù)的差異,可編制更加靈活的基于反射技術(shù)的框架程序。
    當(dāng)然,利用IDispatch接口的7個系統(tǒng)函數(shù)能否開發(fā)出類似Java的Class、Method、Field、Constructor等反射類,這些都是值得繼續(xù)探討的問題。
參考文獻(xiàn)
[1] 祝連鵬,王虎,趙學(xué)臣.基于SOA反射工廠的軟件體系架構(gòu)[J].計算機技術(shù)與發(fā)展,2011,21(7):125-128.
[2] 谷玉奎,曹寶香,袁玉珠.基于SOA的分布式構(gòu)件庫管理模型[J]. 計算機技術(shù)與發(fā)展,2008,18(04):101-103.
[3] 蒲海紅,侯秀平,趙云峰.構(gòu)件集成算法的研究[J].計算機技術(shù)與發(fā)展,2009,19(5):75-78.
[4] 雷寧寧,薛錦云,劉超. 基于構(gòu)件開發(fā)與傳統(tǒng)面向?qū)ο箝_發(fā)之比較[J].計算機技術(shù)與發(fā)展,2007,17(8):88-91.
[5] 羅巨波,吳可嘉,葉鵬,等. 基于反射機制的軟件體系結(jié)構(gòu)重用方法及工具[J]. 計算機工程,2009,35(14):90-92.

此內(nèi)容為AET網(wǎng)站原創(chuàng),未經(jīng)授權(quán)禁止轉(zhuǎn)載。