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

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

idl文件

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

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

屬性:

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

接口定義

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

組件庫定義

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

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

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

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;
    };
};

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

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

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

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

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

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

  1. 字符串名稱項(xiàng),該項(xiàng)中包含一個默認(rèn)值,一般給組件的字符串名稱;CLSID子健,一般給實(shí)現(xiàn)類的GUID;CurVer子健一般是子健的版本
  2. 以版本字符串為鍵的注冊表項(xiàng),該項(xiàng)中主要保存:默認(rèn)值,當(dāng)前版本的項(xiàng)目名稱;CLSID當(dāng)前版本庫的實(shí)現(xiàn)類的GUID
  3. 在HKEY_CLASSES_ROOT/CLSID子健中注冊以實(shí)現(xiàn)類GUID字符串為鍵的注冊表項(xiàng),里面主要包含:默認(rèn)值,組件字符串名稱;InprocServer32,組件所在模塊的全路徑;ProgID組件名稱;TypeLib組件類型庫的ID,也就是在定義IDL文件時,定義的實(shí)現(xiàn)庫的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}") }, //類型庫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
};

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

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

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

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

由于所有類都派生自IUnknown,所在在這里就不顯示這個基類了。
每個實(shí)現(xiàn)類都對應(yīng)了一個它具體的類工廠,而項(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宏展開如下:

#define STDMETHOD(method) virtual HRESULT __stdcall method

所以上面的代碼展開后就變成了:

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

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

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

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

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


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

這些全局函數(shù)的作用與之前的相同,它里面多了一個_Module的全局對象,該對象類似于MFC中的CWinApp類,它用來表示整個項(xiàng)目的實(shí)例,里面封裝了對于引用計數(shù)的管理,以及對項(xiàng)目中各個接口注冊信息的管理,所以看DllRegisterServer等函數(shù)就會發(fā)現(xiàn)它們里面其實(shí)很簡單,大部分的工作都由_Module對象完成。
整個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)容相差無幾,多了一個helpstring屬性,該屬性用于產(chǎn)生幫助信息,當(dāng)使用者在調(diào)用接口函數(shù)時IDE會將此提示信息顯示給調(diào)用者。
由于有系統(tǒng)框架給我們做的大量的工作,我們再也不用關(guān)心像引用計數(shù)的問題,只需要將精力集中在編寫接口的實(shí)現(xiàn)上,減少了不必要的工作量。
至此從結(jié)構(gòu)上說明了為了實(shí)現(xiàn)跨語言COM組件內(nèi)部做了哪些工作,當(dāng)然只有這些工作是肯定不夠的,后面會繼續(xù)說明它所做的另一塊工作——提供的一堆通用的變量類型。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,241評論 2 375

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