COM學(xué)習(xí)(三)——COM的跨語(yǔ)言

COM是基于二進(jìn)制的組件模塊,從設(shè)計(jì)之初就以支持所有語(yǔ)言作為它的一個(gè)目標(biāo),這篇文章主要探討COM的跨語(yǔ)言部分。

idl文件

一般COM接口的實(shí)現(xiàn)肯定是以某一具體語(yǔ)言來(lái)實(shí)現(xiàn)的胶哲,比如說(shuō)使用VC++語(yǔ)言,這就造成了一個(gè)問(wèn)題潭辈,不同的語(yǔ)言對(duì)于接口的定義鸯屿,各個(gè)變量的定義各不相同,如何讓使用vc++或者說(shuō)Java等其他語(yǔ)言定義的接口能被別的語(yǔ)言識(shí)別萎胰?為了達(dá)到這個(gè)要求碾盟,定義了一種文件格式idl——(Interface Definition Language)接口定義語(yǔ)言,IDL提供一套通用的數(shù)據(jù)類型技竟,并以這些數(shù)據(jù)類型來(lái)定義更為復(fù)雜的數(shù) 據(jù)類型冰肴。一般來(lái)說(shuō),一個(gè)文件有下面幾個(gè)部分說(shuō)明

  1. 接口的定義
  2. 組件庫(kù)的定義
  3. 實(shí)現(xiàn)類的定義
    而各個(gè)部分又包括他們的屬性定義榔组,以及函數(shù)成員的定義

屬性:

屬性是在接口定義的上方熙尉,使用“[]”符號(hào)包裹,一般在屬性中使用下面幾個(gè)關(guān)鍵字:
object:標(biāo)明該部分是一個(gè)對(duì)象(可以理解為c++中的對(duì)象搓扯,包括接口和具體的實(shí)現(xiàn)類)
uuid:標(biāo)明該部分的GUID
version:該部分的版本

接口定義

接口定義采用關(guān)鍵字interface检痰,接口函數(shù)定義在一對(duì)大括號(hào)中,它的定義與類的定義相似锨推,其中函數(shù)定義需要修飾函數(shù)各個(gè)參數(shù)的作用铅歼,比如使用in 表示它作為輸入?yún)?shù),out表示作為輸出參數(shù)换可,retval表示該參數(shù)作為返回值椎椰,一般在VC++定義的接口中,函數(shù)返回值為HRESULT沾鳄,但是需要返回一個(gè)值供外界調(diào)用慨飘,此時(shí)就使用輸出參數(shù)并加上retval表示它將在其他語(yǔ)言中作為函數(shù)的返回值。

組件庫(kù)定義

庫(kù)使用library關(guān)鍵字定義译荞,在定義庫(kù)的時(shí)候瓤的,它的屬性一般定義GUID和版本信息,而在庫(kù)中通常定義庫(kù)中的實(shí)現(xiàn)類的相關(guān)信息吞歼,庫(kù)中的信息也是寫(xiě)在一對(duì)大括號(hào)中

實(shí)現(xiàn)類的定義

接口實(shí)現(xiàn)類使用關(guān)鍵字coclass圈膏,接口類的屬性一般定義一個(gè)object,一個(gè)GUID篙骡,然后一般定義實(shí)現(xiàn)類不需要向在C++中那樣定義它的各個(gè)接口本辐,各個(gè)數(shù)據(jù)成員桥帆,只需要告知它實(shí)現(xiàn)哪些接口即可医增,也就是說(shuō)它繼承自哪些接口慎皱。
下面是一個(gè)具體的例子:

import "unknwn.idl";

[
    object,
    uuid(CF809C44-8306-4200-86A1-0BFD5056999E)
]
interface IMyString : IUnknown
{
    HRESULT Init([in] BSTR bstrInit);
    HRESULT GetLength([out, retval] ULONG *pretLength);
    HRESULT Find([in] BSTR bstrFind, [out, retval] BSTR* bstrSub);
};

[
    uuid(ADF50A71-A8DD-4A64-8CCA-FFAEE2EC7ED2),
    version(1.0)
]
library ComDemoLib 
{
    importlib("stdole32.tlb");
    [
        uuid(EBD699BA-A73C-4851-B721-B384411C99F4)
    ]
    coclass CMyString
    {
        interface IMyString;
    };
};

上面的例子中定義了一個(gè)IMyString接口繼承自IUnknown接口,函數(shù)參數(shù)列表中in表示參數(shù)為輸入?yún)?shù),out表示它為輸出參數(shù)叶骨,retval表示該參數(shù)是函數(shù)的返回值茫多。import導(dǎo)入了一個(gè)庫(kù)文件類似于include。而importlib導(dǎo)入一個(gè)tlb文件忽刽,我們可以將其看成VC++中的#pragma comment導(dǎo)入一個(gè)lib庫(kù)
從上面不難看出一個(gè)IDL文件至少有3個(gè)ID天揖,一個(gè)是接口ID,一個(gè)是庫(kù)ID跪帝,還有一個(gè)就是實(shí)現(xiàn)類的ID
在VC環(huán)境中通過(guò)midl命令可以對(duì)該文件進(jìn)行編譯今膊,編譯會(huì)生成下面幾個(gè)我們?cè)诰帉?xiě)實(shí)現(xiàn)時(shí)會(huì)用到的重要文件:

  1. 一個(gè).h文件:包含各個(gè)部分的聲明,以及接口的定義
  2. 一個(gè)_i.c文件:包含各個(gè)部分的定義伞剑,主要是各個(gè)GUI的定義

需要實(shí)現(xiàn)的導(dǎo)出函數(shù)

一般我們需要在dll文件中導(dǎo)出下面幾個(gè)全局的導(dǎo)出函數(shù):

STDAPI DllRegisterServer(void);
STDAPI DllUnregisterServer(void);
STDAPI DllGetClassObject(const CLSID & rclsid, const IID & riid, void ** ppv);
STDAPI DllCanUnloadNow(void);

其中DllRegisterServer用來(lái)向注冊(cè)表中注冊(cè)模塊的相關(guān)信息斑唬,主要注測(cè)在HKEY_CLASSES_ROOT中,主要定義下面幾項(xiàng)內(nèi)容:

  1. 字符串名稱項(xiàng)黎泣,該項(xiàng)中包含一個(gè)默認(rèn)值恕刘,一般給組件的字符串名稱;CLSID子健抒倚,一般給實(shí)現(xiàn)類的GUID褐着;CurVer子健一般是子健的版本
  2. 以版本字符串為鍵的注冊(cè)表項(xiàng),該項(xiàng)中主要保存:默認(rèn)值托呕,當(dāng)前版本的項(xiàng)目名稱含蓉;CLSID當(dāng)前版本庫(kù)的實(shí)現(xiàn)類的GUID
  3. 在HKEY_CLASSES_ROOT/CLSID子健中注冊(cè)以實(shí)現(xiàn)類GUID字符串為鍵的注冊(cè)表項(xiàng),里面主要包含:默認(rèn)值项郊,組件字符串名稱;InprocServer32馅扣,組件所在模塊的全路徑;ProgID組件名稱呆抑;TypeLib組件類型庫(kù)的ID岂嗓,也就是在定義IDL文件時(shí),定義的實(shí)現(xiàn)庫(kù)的GUID鹊碍。
    下面是具體的定義:
const TCHAR *g_RegTable[][3] = {
    { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}"), 0, _T("FirstComLib.MyString")}, //組件ID
    { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}\\InprocServer32"), 0, (const TCHAR*)-1 }, //組建路徑
    { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}\\ProgID"), 0, _T("FirstComLib.MyString")}, //組件名稱
    { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}\\TypeLib"), 0, _T("{ADF50A71-A8DD-4A64-8CCA-FFAEE2EC7ED2}") }, //類型庫(kù)ID
    { _T("FirstComLib.MyString"), 0, _T("FirstComLib.MyString") }, //組件的字符串名稱
    { _T("FirstComLib.MyString\\CLSID"), 0, _T("{EBD699BA-A73C-4851-B721-B384411C99F4}")}, //組件的CLSID
    { _T("FirstComLib.MyString\\CurVer"), 0, _T("FirstComLib.MyString.1.0") }, //組件版本
    { _T("FirstComLib.MyString.1.0"), 0, _T("FirstComLib.MyString") }, //當(dāng)前版本的項(xiàng)目名稱
    { _T("FirstComLib.MyString.1.0\\CLSID"), 0, _T("{EBD699BA-A73C-4851-B721-B384411C99F4}")} //當(dāng)前版本的CLSID
};

使用上一篇博文的代碼厌殉,來(lái)循環(huán)注冊(cè)這些項(xiàng)即可
DllGetClassObject:該函數(shù)用來(lái)生成對(duì)應(yīng)的工廠類,而工廠類負(fù)責(zé)產(chǎn)生對(duì)應(yīng)接口的實(shí)現(xiàn)類侈咕。
DllCanUnloadNow:函數(shù)用來(lái)詢問(wèn)是否可以卸載對(duì)應(yīng)的dll公罕,一般在COM中有兩個(gè)全局的引用計(jì)數(shù),用來(lái)記錄當(dāng)前內(nèi)存中有多少個(gè)模塊中的類耀销,以及當(dāng)前有多少個(gè)線程在使用它楼眷,如果當(dāng)前沒(méi)有線程使用或者存在的對(duì)象數(shù)為0,則可以卸載

實(shí)現(xiàn)類的定義

實(shí)現(xiàn)部分的整體結(jié)構(gòu)圖如下:

整體結(jié)構(gòu)類圖.png

由于所有類都派生自IUnknown,所在在這里就不顯示這個(gè)基類了罐柳。
每個(gè)實(shí)現(xiàn)類都對(duì)應(yīng)了一個(gè)它具體的類工廠掌腰,而項(xiàng)目中CMyString類的類廠的定義如下:

class CMyClassFactory : public IClassFactory
{
public:
    CMyClassFactory();
    ~CMyClassFactory();

    STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
    STDMETHOD(LockServer)(BOOL isLock);

    STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject);
    STDMETHOD_(ULONG, AddRef)(void);
    STDMETHOD_(ULONG, Release)(void);

protected:
    ULONG m_refs;
};

STDMETHOD宏展開(kāi)如下:

#define STDMETHOD(method) virtual HRESULT __stdcall method

所以上面的代碼展開(kāi)后就變成了:

virtual HRESULT __stdcall CreateInstance((IUnknown *pUnkOuter, REFIID riid, void **ppvObject);

另外3個(gè)派生自IUnknown接口就沒(méi)什么好說(shuō)的,主要說(shuō)說(shuō)另外兩個(gè):
CreateInstance:主要用來(lái)生成對(duì)應(yīng)的實(shí)現(xiàn)類张吉,然后再調(diào)用實(shí)現(xiàn)類——CMyString的QueryInterface函數(shù)生成對(duì)應(yīng)的接口
LockServer:當(dāng)前是否被鎖壮萘骸:如果傳入的值為TRUE,則表示被鎖住肮蛹,對(duì)應(yīng)的鎖計(jì)數(shù)器+1勺择, 否則 -1
至于CMyString類的代碼與之前的大同小異,也就沒(méi)什么說(shuō)的伦忠。
其他語(yǔ)言想要調(diào)用省核,以該項(xiàng)目為例,一般會(huì)經(jīng)歷下面幾個(gè)步驟:

  1. 調(diào)用對(duì)應(yīng)語(yǔ)言提供的產(chǎn)生接口的函數(shù)昆码,該函數(shù)參數(shù)一般是傳入一個(gè)組件的字符串名稱气忠。如果要引用該項(xiàng)目中的組件則會(huì)傳入FirstComLib.MyString
  2. 在注冊(cè)表的HKEY_CLASSES_ROOT\組件字符串名\CLSID(比如HKEY_CLASSES_ROOT\FirstComLib.MyString\CLSID)中找到對(duì)應(yīng)的CLSID值
  3. 在HKEY_CLASSES_ROOT\CLSID\對(duì)應(yīng)ID\InprocServer32(CLSID\{EBD699BA-A73C-4851-B721-B384411C99F4}\InprocServer32)位置處找到對(duì)應(yīng)模塊的路徑
  4. 加載該模塊
  5. 根據(jù)IDL文件告知其他語(yǔ)言里面存在的接口,由語(yǔ)言調(diào)用對(duì)應(yīng)的創(chuàng)建接口的函數(shù)創(chuàng)建接口
  6. 調(diào)用模塊的導(dǎo)出函數(shù)DllGetClassObject將查詢到的CLSID作為第一個(gè)參數(shù)未桥,并將接口ID作為第二個(gè)參數(shù)傳入笔刹,得到一個(gè)接口
    6.后面根據(jù)idl文件中的定義,直接調(diào)用接口中提供的函數(shù)

真實(shí)ATLCOM項(xiàng)目的解析

最后來(lái)看看一個(gè)正式的ATLCOM項(xiàng)目里面的內(nèi)容冬耿,來(lái)復(fù)習(xí)前面的內(nèi)容舌菜,首先通過(guò)VC創(chuàng)建一個(gè)ATLCOM的dll項(xiàng)目
在項(xiàng)目上右鍵-->New Atl Object,輸入接口名稱亦镶,IDE會(huì)根據(jù)名稱生成一個(gè)對(duì)應(yīng)的接口日月,還是以MyString接口為例,完成這一步后缤骨,整個(gè)項(xiàng)目的類結(jié)構(gòu)如下:


項(xiàng)目類結(jié)構(gòu)圖

這些全局函數(shù)的作用與之前的相同爱咬,它里面多了一個(gè)_Module的全局對(duì)象,該對(duì)象類似于MFC中的CWinApp類绊起,它用來(lái)表示整個(gè)項(xiàng)目的實(shí)例精拟,里面封裝了對(duì)于引用計(jì)數(shù)的管理,以及對(duì)項(xiàng)目中各個(gè)接口注冊(cè)信息的管理虱歪,所以看DllRegisterServer等函數(shù)就會(huì)發(fā)現(xiàn)它們里面其實(shí)很簡(jiǎn)單蜂绎,大部分的工作都由_Module對(duì)象完成。
整個(gè)IDL文件的定義如下:

import "oaidl.idl";
import "ocidl.idl";
    [
        object,
        uuid(E3BD0C14-4D0C-48F2-8702-9F8DBC96E154),
        dual,
        helpstring("IMyString Interface"),
        pointer_default(unique)
    ]
    interface IMyString : IDispatch
    {
    };

[
    uuid(A61AC54A-1B3D-4D8E-A679-00A89E2CBE93),
    version(1.0),
    helpstring("FirstAtlCom 1.0 Type Library")
]
library FIRSTATLCOMLib
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");

    [
        uuid(11CBC0BE-B2B7-4B5C-A186-3C30C08A7736),
        helpstring("MyString Class")
    ]
    coclass MyString
    {
        [default] interface IMyString;
    };
};

里面的內(nèi)容與上一次的內(nèi)容相差無(wú)幾笋鄙,多了一個(gè)helpstring屬性师枣,該屬性用于產(chǎn)生幫助信息,當(dāng)使用者在調(diào)用接口函數(shù)時(shí)IDE會(huì)將此提示信息顯示給調(diào)用者萧落。
由于有系統(tǒng)框架給我們做的大量的工作践美,我們?cè)僖膊挥藐P(guān)心像引用計(jì)數(shù)的問(wèn)題洗贰,只需要將精力集中在編寫(xiě)接口的實(shí)現(xiàn)上,減少了不必要的工作量陨倡。
至此從結(jié)構(gòu)上說(shuō)明了為了實(shí)現(xiàn)跨語(yǔ)言COM組件內(nèi)部做了哪些工作敛滋,當(dāng)然只有這些工作是肯定不夠的,后面會(huì)繼續(xù)說(shuō)明它所做的另一塊工作——提供的一堆通用的變量類型玫膀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末矛缨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子帖旨,更是在濱河造成了極大的恐慌,老刑警劉巖灵妨,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件解阅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡泌霍,警方通過(guò)查閱死者的電腦和手機(jī)货抄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)朱转,“玉大人蟹地,你說(shuō)我怎么就攤上這事√傥” “怎么了怪与?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)缅疟。 經(jīng)常有香客問(wèn)我分别,道長(zhǎng),這世上最難降的妖魔是什么存淫? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任耘斩,我火速辦了婚禮,結(jié)果婚禮上桅咆,老公的妹妹穿的比我還像新娘括授。我一直安慰自己,他們只是感情好岩饼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布荚虚。 她就那樣靜靜地躺著,像睡著了一般忌愚。 火紅的嫁衣襯著肌膚如雪曲管。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天硕糊,我揣著相機(jī)與錄音院水,去河邊找鬼腊徙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛檬某,可吹牛的內(nèi)容都是我干的撬腾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼恢恼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼民傻!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起场斑,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤漓踢,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后漏隐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體喧半,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年青责,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挺据。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脖隶,死狀恐怖扁耐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情产阱,我是刑警寧澤婉称,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站心墅,受9級(jí)特大地震影響酿矢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜怎燥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一瘫筐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧铐姚,春花似錦策肝、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至依许,卻和暖如春棺禾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背峭跳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工膘婶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缺前,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓悬襟,卻偏偏與公主長(zhǎng)得像衅码,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子脊岳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容