今天要來介紹的是 Component Object Model (COM),內容主要是以 James Forshaw 的演講筆記為主,再搭配一些相關的文章。
COM 在 Windows 的使用非常廣泛,像是 Office 內建的 Object Linking and Embedding (OLE) 和 IE 瀏覽器用到的 ActiveX 都是基於 COM。
在 James Forshaw 的 COM in Sixty Seconds 中提到 COM 的發明在一開始是為了支援 Application 間的 Interoperability,像是在 Word 中可以操作 Excel,即使兩個 Application 可能是以不同的程式語言實作,甚至是在不同的電腦。
Vtable 的實作,讓人可以透過傳遞指標來呼叫 method
|----------------| |----------------| |-----------------|
| Object Pointer | -> | Vtable Pointer | -> | Method1 Pointer |
|----------------| |----------------| |-----------------|
| Data1 | | Method2 Pointer |
|----------------| |-----------------|
上圖對應到下方程式碼:
struct ObjectTable {
void (*SetInt)(struct Object* This, int i);
int (*GetInt)(struct Object* This);
};
struct Object {
struct ObjectVTable* Vtbl;
...
}
struct Object* obj;
obj-Vtbl->SetInt(obj, 1234);
Object 的開頭會是 Vtable 的指標,在使用 method 的時候都是從 Vtable 呼叫,並且第一個參數都是 Object 本身。
如果是在一個 Object 具有多個 interface 的狀況
struct Interface1 {
virtual void A() = 0;
};
struct Interface2 {
virtual void B() = 0;
};
class Object : public Interface1, public Interface2 {
void A() {}
void B() {}
};
Object 衍生了多個 interface
Interface1* intf1 = new Object;
將 Object cast 成 Interface1
Interface2* intf2 = (Interface2*)intf1;
再將 intf1 cast 成 Interface2 時,會發生非預期的問題。
因此 COM 提供了 IUnknown 作為所有 COM 的標準介面
DEFINE_GUID(IID_IUnknown, 00000000-0000-0000-C000-000000000046);
struct IUknown {
HRESULT QueryInterface(GUID& iid, void** ppv);
LONG AddRef();
LONG Release();
}
所有的 COM 都是從 IUnknown 衍生。
Interface ID (IID) 是唯一的。
AddRef 和 Release 是用來管理 COM Object 的生命週期,而且因為 COM Object 的分配不是在 C++ heap,因此需要特別管理,避免 memory leak 等問題。
QueryInterface 是用來檢查 Object 是否支援某種 interface (IID),有支援的話才會返回 pointer,這樣就能解決 Casting Problem。
struct Interface1 : public IUknown {};
struct Interface2 : public IUknown {};
Interface2* intf2;
if (intf1->QueryInterface(IID_Interface2, (void**)&intf2) >= 0) {
intf2->Release();
}
使用完 intf2 後執行 Release。
COM Object 需要在 registry HKEY_CLASSES_ROOT\CLSID\
中註冊。每個 CLSID 就代表一種 COM Object。
在 InprocHandler32
路徑下的 DLL 就是 COM Object 的實作。
DEFINE_GUID(IID_ClassFactory, 00000001-0000-0000-C000-000000000046)
struct IClassFactory : public IUknown {
HRESULT CreateInstance(
IUknown *pUnkOuter,
REFIID riid,
void **ppvObject
);
HRESULT LockServer(BOOL fLock);
}
IClassFactory
提供 CreateInstance Method,讓使用者更方便建立多個物件。
透過系統登錄中有 CLSID 的類別物件建立多個物件。透過 IClassFactory Interface 進行溝通。
HRESULT CoGetClassObject(
[in] REFCLSID rclsid,
[in] DWORD dwClsContext,
[in, optional] LPVOID pvReserved,
[in] REFIID riid,
[out] LPVOID *ppv
);
rclsid
指定要存取的 CLSID。
dwClsContext
指定要使用的 server,以下是常見的幾種:
在 Local 端,可以使用 CoCreateInstance。
而在 Remote 端可以使用 CoCreateInstanceEx。
HRESULT CoCreateInstanceEx(
[in] REFCLSID Clsid,
[in] IUnknown *punkOuter,
[in] DWORD dwClsCtx,
[in] COSERVERINFO *pServerInfo,
[in] DWORD dwCount,
[in, out] MULTI_QI *pResults
);
pServerInfo
指定 Remote 端的資訊。
剛剛在 registry 中的 InProcServer32
的 server.dll 會有 exported function DLLGetClassObject
讓 process 載入。
下圖是 Client 使用 InProcServer32 的 COM Object 的過程
|----------------|
ˊ--------> | Class Instance |
ˊ |----------------|
ˊ ^
ˊ |
|-------------| |----------------|
| Client | ---------------------> | Class Factory |
|-------------| |----------------|
ˋ ^
ˋ |
ˋ |----------------|
ˋ--------> | server.dll |
|----------------|
Client 先透過 DLLGetClassObject
取得 class object,在透過 Class Factory 取得 Class Instance,最後就可以使用該 Interface。
在 single process 的環境中,以 COM Apartment 將每個 COM Instance 隔開,主要分為兩種狀況:
CoInitializeEx
可以用來設定 MultiThread Appartment。
另外,LocalServer32 的 cross processes 環境下,則會使用 DCOM。
下一篇,我將會介紹 DCOM。