- COM是構(gòu)造二進制兼容軟件組件的規(guī)范榆浓。它不是編程語言、代碼庫或者編譯器弱贼,而是個二進制規(guī)范
- COM的好處
- com組件易替換
- com組件適合于改變業(yè)務(wù)需求
- com組件使復(fù)用性成為可能
使用com蒸苇,將很容易對某些代碼實現(xiàn)一次編寫和多處使用。而且你可以對這個組簡進行任何糾正和改進吮旅,而不必改變使用這個組件的應(yīng)用程序溪烤。- com組件有助于并行開發(fā)
一旦設(shè)計接口之后,就可以將其分布到幾個程序中庇勃,組件的實現(xiàn)可以并列進行檬嘀。
- COM的局限性
- 版本問題
每個COM組件都有GUID,是操作系統(tǒng)標(biāo)識這個組件的唯一ID.這些GUID存放在Windows Registry(注冊表)中。這樣每次改變組件接口時责嚷,都賦予新的GUID枪眉。這個機制可以作為版本號,表示組件改變了接口再层。使用該組件的軟件要注意。它能保證堡纬,一旦開始使用一個組件聂受,就總是使用這個組件的同一版本。如果要使用新的版本烤镐,則要取得新的GUID蛋济。
版本問題是無法避免的。因此炮叶,在開發(fā)期間碗旅,你需要特別注意所用的組件的版本渡处。每次更新都會導(dǎo)致新的版本。在開發(fā)期間每次生成新構(gòu)件時祟辟,要有檢查組件版本的習(xí)慣医瘫。- 舊接口應(yīng)當(dāng)停用
COM要求的是接口一旦建立,就無法拋棄它旧困。這就保證一旦程序利用特定版本的組件醇份,就總是支持這個版本的功能。
作為開發(fā)人員吼具,我們需要保證舊版本組件接口保持不動僚纷。可以在組件中增加功能或更新現(xiàn)有功能拗盒。但是不能刪除現(xiàn)有功能怖竭。這個特點造成的唯一真正問題是組件不斷膨脹的演變。到一定時候(如果組件變得太大或舊功能太多)陡蝇,可能要把所要功能移植到新組件中痊臭。- com接口要認真規(guī)劃
接口是COM組件的集成部分。接口是組件之間相互訪問的通信機制毅整。每次組件或其接口改變時趣兄,就有一個新版本與組件相關(guān)聯(lián)。認真規(guī)劃可以防止開發(fā)期間產(chǎn)生太多接口版本悼嫉。
- 接口
- 接口定義了其它軟件和組件能利用的公用功能艇潭。COM接口使應(yīng)用程序和其它組件可以和COM組件的功能進行通信。組件功能通過虛擬函數(shù)表(virtual function table)訪問,也稱vtable或VTBL戏蔑。vtable不包含實際函數(shù)蹋凝,只是包含組件函數(shù)的一組指針。組件要訪問其它組件的功能時总棵,要通過這個vtable鳍寂。
- 客戶機不能直接訪問vtable,另一指針叫接口指針情龄,增加了與接口的另一層間接迄汛。使得這個接口得以實現(xiàn)。即客戶機見到了vtable表中指針的指針骤视。
- COM接口的vtable唯一要求是表的第一個字段應(yīng)為IUnknow的指針鞍爱。IUnknow是任何組件變?yōu)镃OM組件必須實現(xiàn)的唯一接口。其它所有接口都要從IUnknow接口繼承而來专酗。
- 一個COM組件可以包含任意多個接口的實現(xiàn)方法睹逃。
- 組件要靠接口通信。接口定義并發(fā)布后祷肯,就不能改變沉填。COM的規(guī)則之一是保證軟件接口在發(fā)布后不變疗隶,如果改變了接口,則要作為另一新接口發(fā)布翼闹,這樣可以保證前面的接口不變斑鼻。由于這種不變性,所以在接口生成之前一定要慎重規(guī)劃橄碾。
3.1. 接口特征
- 接口不是類
- 接口不是對象
- 接口有唯一性
每個接口有一個唯一標(biāo)識符GUID卵沉。來保證不會與其它接口發(fā)生沖突。- 接口是不可變的法牲。
接口沒有版本史汗,因此避免了版本問題。接口的增加拒垃、刪除功能或者改變語法之后的新版本成為全新的接口停撞,要指定新的接口標(biāo)識符。
3.2 接口類型
- IUnknow接口:它是COM組件必須實現(xiàn)的唯一接口悼瓮。
- IDispatch接口:如果要從腳本語言訪問組件戈毒,則還要實現(xiàn)IDispatch接口。
- 自定義接口:表示系統(tǒng)尚未支持的接口横堡。需要提供調(diào)用代碼(如果沒有提供)埋市。
- 雙接口:Automation對象(自動化對象)實現(xiàn)IDispatch和vtable接口時,就稱為雙接口(dual-interface)組件命贴。
- IUnknow接口
IUnknow接口是最重要的接口道宅。
每個COM組件都必須實現(xiàn)這個接口。
因為要用這個接口來管理有對象支持的所有其它接口胸蛛。
IUnknow接口包含三個方法:
- QueryInterface
用于尋找對象提供的其它接口污茵。- AddRef
要使用這些接口時,調(diào)用AddRef- Release
完成某個接口使用后葬项,調(diào)用Release
內(nèi)存管理:接口指針的壽命管理總是通過每個COM接口中的AddRef和Release方法完成的泞当。
- IDispatch接口
IDispatch接口是從IUnknow接口派生而來。這個接口主要用于腳本語言民珍。而過開發(fā)的應(yīng)用程序不要從腳本語言訪問襟士,最好不要使用IDispatch(避免增加開銷)
IDispatch接口包括的函數(shù)允許訪問COM對象的方法和屬性。IDispatch接口使VB和其他腳本語言可以操作對象的方法和屬性嚷量。
IDispatch接口有連個重要的方法:
- GetIDsOfName
- Invoke
雙接口
Automation對象(自動化對象)實現(xiàn)IDispatch和vtable接口時敌蜂,就稱為雙接口(dual-interface)組件。
在雙接口中津肛,vtable中的前三個項目是IUnknow的成員,后四個是IDispatch的成員汗贫,再后面是雙接口成員的地址身坐。
所有VB組件都支持雙接口秸脱,因此不必另外實現(xiàn)提供雙接口的類類型庫
類型庫是描述一個或幾個COM對象類型的文件或部分文件。類型庫特別有用部蛇,可以在編譯時訪問摊唇。在VB中,類型庫組合在同一個二進制文件中涯鲁。接口一般規(guī)則
- 設(shè)計遠程使用
COM提供靈活性巷查。可以設(shè)計成無法作為"遠程"的抹腿。
一般來說岛请,所有COM接口都應(yīng)設(shè)計成支持分布式處理- 從IUnknow派生
所有COM接口必須直接或間接從IUnkonw接口派生。也就是說警绩。任何在COM對象上實現(xiàn)的接口必須以QueryInterface崇败、AddRef和Release為前的三個方法。- 生成唯一標(biāo)識符
接口的真名是128位GUID肩祥。因此后室,對于每個新定義接口,要產(chǎn)生新的接口標(biāo)識符(interface identifier)或IID- 接口是不可變的
接口發(fā)布之后混狠,與某個IID之間的合同是不能改變的岸霹。前面介紹過,這個規(guī)則保證軟件能肯定接口在發(fā)布之后保持相同将饺。- 函數(shù)應(yīng)返回HRESULT
接口的所有方法返回類型為HRESULT,但是IUnknow::AddRef 和IUnknow::Release是例外贡避。
HRESULT就是一個狀態(tài)碼
當(dāng)不準(zhǔn)備遠程使用時,可以忽略這條規(guī)則- 字符串參數(shù)應(yīng)當(dāng)為Unicode
所有COM接口傳遞的所有字符串都是Unicode字符串
- 接口設(shè)計
COM接口中應(yīng)包括下列元素:
- 接口標(biāo)識符Interface identifier
每個接口要有一個GUID,作為編程名稱俯逾,這個接口ID(IID)是接口的唯一標(biāo)識符贸桶。帶有特定IID的接口設(shè)計編譯為二進制形式并發(fā)布后,所有構(gòu)成接口其它元素的規(guī)定均不能改變桌肴。- 接口簽名Interface signature
接口簽名定義下列內(nèi)容:1. 接口中的方法個數(shù)和順序 2. 每個方法中每個參數(shù)的數(shù)值皇筛、順序和類型 3. 每個方法的返回值類型- 接口詞法Interface semantics
接口詞法是合同的一部分,是對簽名描述的補充坠七。
使用MIDL定義自定義接口
MIDL:接口定義語言Microsoft interface Definition Language,它是定義COM接口的說明性語言水醋。
1. COM服務(wù)器的三個關(guān)鍵要求
- 接口(Interface)
接口是服務(wù)器與客戶機之間的協(xié)議,客戶機通過接口與服務(wù)器通信彪置。- 組件類(CoClass)
組件類(Component Class)提供所定義接口的實現(xiàn)方法拄踪。- 組類型庫(Type Library)
是編譯的IDL文件,向支持COM的環(huán)境傳送接口的信息拳魁。
2. IDL文件
2.1 下面創(chuàng)建CustomComWapper.idl文件
//import語句惶桐,將引入另一個idl文件中包含的額定義并在本文中使用
import "oaidl.idl" //輸入文件oaidl.idl中的接口定義,包括IDispatch
import "ocidl.idl"
//1. 定義類型庫
[//方括號中列出 類型庫屬性清單
uuid(DAD1DA4G-0C17-455C-B8FE-314B56DD10CCD), //類型庫唯一標(biāo)識符 LIBID
version(1.0),//版本
helpstring("CustomComTypeLibraryLib 1.0 Type Library")
]
//類型庫屬性清單后緊跟著類型庫定義
library CustomComTypeLibraryLib
{
//importlib將引入二進制(編譯類型庫)
//所有類型庫都要用importlib輸入Stdole3d.tlb中定義的基礎(chǔ)類型庫。用importlib指令輸入類型庫時姚糊,需要保證向客戶機提供這個庫(要在Com服務(wù)器上安裝這個庫)
importlib("stdole32.tlb");
importlib("stdole2.tlb");
//--- if the follwing import fails then it means that
//--- the type library is not on the system path ,
//---you can fix the problem in two ways:
//---1.Add it to system wide PATH environment variable
//---2. Add it to the executable file list in Developer Studio.
importlib("axdb18enu.tlb");
//--interface definition
//2. 在類型庫中 定義接口ICustomInterfaceOne
//當(dāng)然你也可以定義多個接口
[ //方括號中為屬性清單
object, //object屬性贿衍,告訴編譯器這是個Com接口定義而不是RPC(遠程過程調(diào)用)接口定義。
uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx),//接口的唯一標(biāo)識符IID
dual,//dual屬性表示客戶機有兩個不同的方法可以訪問這個接口提供的屬性和方法救恨,即雙接口
nonextensible,
helpstring("ICustomInterfaceOne interface"),//幫助字符串
pointer_default(unique)//該屬性指定除參數(shù)表中所列特征外所有指針的缺省特征贸辈;unique表示指針可以說Null,但不支持別名
]
//屬性清單后面是接口定義本身肠槽。這里定于ICustomInterfaceOne接口擎淤,是從IDispach接口繼承來的
//該接口中定義了兩個屬性 和一個方法
interface ICustomInterfaceOne:IDispach
{
[propget,id(NameId),helpstring("屬性 姓名")] HRESULT Name([out,retval] BSTR* pVal);
[propget,id(NameId),helpstring("屬性 姓名")] HRESULT Name([in] BSTR newVal);
[propget,id(AgeId),helpstring("屬性 年齡")] HRESULT Age([out,retval] DOUBLE* pVal);
[propget,id(AgeId),helpstring("屬性 年齡")] HRESULT Age([in] DOUBLE newVal);
HRESULT DoSomething();//方法
}
//3. 定義組件類 實現(xiàn)ICustomInterfaceOne
[
uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx),//組件類的唯一標(biāo)識符 CLSID
helpstring("ICustomInterfaceOne interface"),//幫助字符串
]
coclass ComCustomCoClassOne
{
[default] interface ICustomInterfaceOne;//組件了指向簽名定義的接口ICustomInterfaceOne
[source]interface iAcadObjectEvents;
}
}
2.2 編譯IDL文件
使用MIDL.EXE編譯后將生成如下文件
- 接口代理文件 CustomComWapper__p.c
包含IDL中所定義接口的調(diào)用代碼- 頭文件 CustomComWapper.h
包含C++接口和類型定義,并說明接口ID(IID_ICustomInterfaceOne) 和 CLSID(CLSID_ComCustomCoClassOne)的符號化常量秸仙。- 接口UUID文件 CustomComWapper_i.c
GUID定義文件 :包含頭文件所說明的IID嘴拢、CLSID和LIBID定義- 類型庫文件 CustomComWapper.tlb
表示IDL文件的二進制版本。
2.3 實現(xiàn)IUnknow和自定義接口
添加自定義類CCustomClassOne實現(xiàn)接口ICustomInterfaceOne
#include <windows.h> //包含windows.h文件筋栋,其中包含后面要用的一些API聲明
#include "CustomComWapper.h" //編譯idl生成的文件
class CCustomClassOne :public ICustomInterfaceOne
{
...
public :
//STDMETHOD宏和STDMETHOD_宏描述函數(shù)的返回類型和調(diào)用規(guī)則
//STDMETHOD宏假設(shè)函數(shù)的返回類型為HRESULT
//STDMETHOD_宏允許在第一個參數(shù)中指定函數(shù)的返回類型
//QueryInterface方法接茬請求的接口炊汤,看看這個組件是否支持,它比較riid與各種支持的接口ID(因為程序員是知道要支持那些接口的) 弊攘,然后返回相應(yīng)的指針抢腐,將this調(diào)整為相應(yīng)的基礎(chǔ)類
//在返回請求接口指針之前,我們要對齊采用AddRef方法襟交,這個規(guī)則保證組件不會再客戶機用完接口之前突然死亡
STDMETHOD (QueryInterface)(REFIID riid,void ** ppv)
{
if(riid == IID_IUnknow)
*ppv = static_cast<IUnknow*>(this);
else if(riid == IID_IDispatch)
*ppv = static_cast<IDispatch*>(this);
else if(riid == IID_ICustomInterfaceOne)
*ppv = static_cast<ICustomInterfaceOne*>(this);
//當(dāng)然如果還支持其它接口在這里添加else if就好了
else
*ppv = NULL;
if(*ppv != NULL)
{
static_cast<IUnknow*>(*ppv)->AddRef();
return S_OK;
}
else
return E_NOINTERFACE;
}
//STDMETHOD_宏允許在第一個參數(shù)中指定函數(shù)的返回類型
//AddRef和Release功能相類似迈倍,都是用Win32API專門控制變量m_ulRefCount的增減。
STDMETHOD_ (ULONG ,AddRef)()
{
InterlockedIncrement(reinterpret_cast<LPLONG>(&m_ulRefCount));
return m_ulRefCount;
}
//Release方法捣域,除了調(diào)用win32API中專門的方法類減少引用計數(shù)外啼染,
//還應(yīng)該在引用數(shù)為0時刪除對象實例
STDMETHOD_ (ULONG ,Release)()
{
if(!InterlockedDecrement(reinterpret_cast<LPLONG>(&m_ulRefCount)))
{
delete this;
return 0;
}
return m_ulRefCount;
}
//下面是實現(xiàn)接口的方法和屬性 屬性省略
STDMETHOD (DoSomething)()
{
}
//下面實現(xiàn)屬性
...
private:
ULING m_ulRefCount;
}