COM應用和原理摘自大神
為了把應用系統和元件程式連線起來,又能使它們協同工作,最簡單的做法就是先定義一組查字典的函式,而且這組函式儘可能一般化,不要加入特定的與字典庫相關的知識。
函式
功能說明
Initialize
初始化
LoadLibrary
裝入字典庫
InsertWord
插入一個單詞
DeleteWord
刪除一個單詞
LookupWord
查詢單詞
RestoreLibrary
把記憶體中的字典庫存入指定的檔案中
FreeLibrary
釋放字典庫
平面型的API介面層可以很好地把兩個程式連線起來,但存在以下一些問題:
(1) 當API函式非常多時,使用會非常不方便,需要對函式進行組織。
(2) API函式需要標準化,按照統一的呼叫方式進行處理,以適應不同的語言程式設計實現。引數的傳遞順序,引數型別,寒暑返回處理都需要標準化。
COM定義了一套完整的介面規範,不僅可以彌補以上API作為元件藉口的不足,還充分發揮了元件物件的優勢,並實現了元件物件的多型性。
介面定義和標識
從技術上講,介面是包含了一組函式的資料結構,通過這組資料結構,客戶程式碼可以呼叫元件物件的功能。介面定義了一組成員函式,這組成員函式是元件物件暴露出來的所有資訊,客戶程式利用這些函式或的元件物件的服務。
客戶程式用一個指向介面資料機構的指標來呼叫介面成員函式。介面指標實際上又指向另一個指標,這第二個指標指向一組函式,稱為介面函式表(虛擬函式表),介面函式表中每一項為4個位元組長的函式指標,每個函式指標與物件的具體實現連線起來。通過這種方式,客戶只要獲得了介面指標,就可以呼叫到物件的實際功能。
對於一個介面來說,他的虛擬函式表vtable是確定的,因此介面的成員函式個數是不變的,而且成員函式的先後順序也是不變的;對於每個成員函式來說,其引數和返回值也是確定的。
在一個介面的定義中,所有這些資訊都必須在二進位制一級確定,不管什麼語言,只要能支援這樣的記憶體結構描述,也就是能夠支援“structure“或“record“型別,並且這種型別能夠包含雙重的指向函式指標表的成員,則它就可以支援介面的描述,從而可以用於編寫COM元件或者使用COM元件。
介面描述語言IDL
COM規範在採用OSF的DCE規範描述遠端呼叫介面IDL的基礎上,進行擴充套件形成了COM介面的描述語言。
COM規範使用的IDL介面描述語言不僅可用於定義COM介面,同時還定義了一些常用的資料型別,也可以描述自定義的資料結構,對於介面成員函式,我們可以指定每個引數的型別,輸入輸出特性,甚至支援可變長度的陣列的描述。IDL支援指標型別,與C/C++很類似。
Microsoft Visual C++提供了MIDL工具,可以把IDL介面描述檔案編譯成C/C++相容的介面描述標頭檔案(.h)。
IUnknown的定義(IDL):
interface IUnknown
{
HRESULT QueryInterface([in] REFIID iid, [out] void **ppv);
ULONG AddRef(void);
ULONG Release(void);
}
IUnknown的定義(C++):
class IUnknown
{
Public:
virtual HRESULT _stdcall QueryInterface([in] REFIID iid, [out] void **ppv)=0;
virtual ULONG _stdcall AddRef(void)=0;
virtual ULONG _stdcall Release(void)=0;
}
程序內元件
因為程序內元件和客戶程式執行在同一個程序地址空間中,所以一旦客戶程式與元件程式建立起通訊關係之後,客戶程式得到的介面指標指向元件程式中介面的vtable,這個vtable包含了所有成員函式地址,客戶程式碼可以直接呼叫這些成員函式,所以其效率非常高。
因為DLL程式是在執行時刻被客戶裝入到記憶體中的,所以DLL模組本身也是獨立的,它並不依賴於客戶程式。
在C++語言中,為了使編制的DLL程式更為通用,一般指定DLL的引出函式使用_stdcall呼叫習慣,如果使用了_cdecl呼叫習慣,則有些程式語言環境就不能使用這些DLL程式。C++編譯器為DLL程式的每個引出函式生成了一個修飾名,這些修飾名對於不同的編譯器並不相容,因此,從通用性角度出發,我們在每個函式定義前加上extern ?C“ 說明符。在Visual C++ 開發環境中,下面的說明語句可以很好的說明一個引出函式:
extern ? C“ int _stdcall MyFunction(int n);
為了編制DLL程式,我們可以按照這樣的步驟:
(1) 建立一個DLL工程
(2) 對每個引出函式,使用extern ? C“說明符,以及_stdcall修飾符,如上面對MyFunction函式的說明。
(3) 按照傳統的程式設計方法,我們還應該編寫一個DEF檔案,用來描述DLL程式的模組資訊。在Win32平臺上,我們可以不使用DEF檔案,而是直接在函式說明時使用_declspec(dllexport)說明符,例如:
extern ? C“_declspec(dllexport) int _stdcall MyFunction(int n);
按照這樣的方法建立起來的DLL模組可以被其他程式呼叫,因為C++聯結器會把所有引出函式的資訊連線到最終的目的碼中。
從客戶程式一方來看,有三個系統函式可用於操作DLL程式,LoadLibrary, GetProcAddress, 和FreeLibrary。
一般地,對於DLL程式的使用過程按照這樣的步驟進行:
首先,客戶程式使用LoadLibrary函式裝入DLL,該函式返回模組的例項控制代碼,供以後操作該模組使用。
然後,客戶程式可以呼叫GetProcAddress函式獲得DLL中引出的函式的地址,我們既可以按函式的序號(在DEF檔案中指定)也可以按函式的名字來獲取引出函式的地址,因為客戶程式和DLL程式在相同的記憶體地址空間中,所以客戶程式可以直接呼叫這些引出函式。
最後FreeLibrary,把DLL程式卸出記憶體,以便釋放資源。
說明:
(1) DLL程式不僅可以引出函式,也可以引出全域性變數,因為客戶程式和DLL程式在同一個地址空間,所以,把DLL中的全域性變數引出到客戶程式中是有意義的。引用的方法並不複雜,或者把變數名放到DEF檔案的EXPORTS部分,並加上DATA選項; 或者在變數說明前面加上_declspec(dllexport)說明符。
(2) DumpBin 通過/Exports選項可以列出DLL程式中的所有被引出的資訊。
(3) 客戶程式本身也可以是一個DLL程式,但它一定先被裝入到程序空間中,以便可以呼叫系統函式操作作為服務程式的DLL模組。
程序外元件
因為程序外元件程式和客戶程式位於不同的程序空間之中,他們使用不同的地址空間,所以元件和客戶之間的通訊必須跨越程序邊界,這就涉及到以下一些問題:
(1) 一個程序如何呼叫另外一個程序中的函式
(2) 引數如何從一個程序被傳遞到另外一個程序中
Windows平臺上,在不同程序之間進行通訊的辦法很多,包括DDE, named pipe,或者共享記憶體等等,COM採用了LPC(Local Procedure Call)和RPC(Remote Procedure Call)
RegEdit可檢查CLSID子鍵下的COM物件(63頁)
Microsoft Visual C++提供OleView.exe,可列出當前機器上的所有類別資訊,以及每一種類別下的元件物件列表。
RegSvr32 D:\DicComp\DictComp.dll
RegSvr32 /u D:\DicComp\DictComp.dll
DLL元件必須有DllRegisterServer和DllUnregisterServer兩個用於註冊的入口函式,才能用RegSvr32註冊。
COM規定,支援自注冊的程序外元件必須支援兩個命令列引數/RegServer和/UnregServer,以便完成註冊或登出操作。
Class Factory
實際上,客戶程式並不直接呼叫元件程式的引出函式,它呼叫COM庫的函式進行元件物件的建立工作,COM庫的建立函式根據登錄檔的資訊呼叫元件程式的入口函式來建立元件物件。元件程式需要提供一個標準的入口函式DLLGetObjectClass,用於提供本組程式的元件資訊。
Class Factory和DLLGetObjectClass函式
類廠是COM物件的生產基地,COM庫通過類廠建立COM物件; 對應每一個COM類,有一個類廠專門用於該COM類的物件創操作。類廠本身也是一個COM物件,它支援一個特殊的介面:IClassFactory,其定義如下:
Class IClassFactory : public IUnknown
{
virtual HRESULT _stdcall CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void **ppv) = 0;
virtual HRESULT _stdcall LockServer(BOOL bLock) = 0;
};
介面IClassFactory有一個重要的成員函式CreateInstance,用於建立對應的COM物件。因為每個類廠之針對特定的COM類物件,所以CreateInstance成員函式知道該建立什麼樣的COM物件。在CreateInstance成員函式的引數中,第一個引數pUnknownOuter用於物件被聚合的情形,沒有聚合設成NULL。IClassFactory的另一個成員函式LockServer用於控制組建的生存週期。
因為類廠本身也是個COM物件,它被用於其它COM物件的建立過程,那麼類廠物件又由誰來建立呢?答案是DLLGetClassObject引出函式。DLLGetClassObject函式並不是COM庫的函式,而是由元件程式實現的引出函式,我們先看一下DLLGetClassObject函式的原型:
HRESULT DLLGetClassObject(const CLSID& clsid,
Const IID& iid,
(void **) ppv
);
COM庫在接到物件建立的指令後,它要調程序內元件的DLLGetClassObject函式,由該函式創類廠物件,並返回類廠物件的介面指標,COM庫或者客戶一旦有了類廠的介面指標,它們就可以通過類廠介面IClassFactory的成員函式CreateInstance建立相應的COM物件。
COM庫與類廠的互動(67頁)
在COM庫中,有三個API函式可用於物件的建立,它們分別是CoGetClassObject, CoCreateInstance和CoCreateInstanceEx。通常情況下,客戶程式呼叫其中之一完成物件的建立,並返回物件的初始介面指標。COM庫與類廠也通過這三個函式進行互動。