在windows系統(tǒng)中,注冊表是一個正式的共享系統(tǒng)數據庫。注冊表中包含關于系統(tǒng)軟硬件以及配置和用戶的各種信息。在COM技術中使用注冊表存儲關于組件的信息??蛻艨梢栽僮员碇兴阉魉枰慕M件。
注冊表有許多關鍵字構成的層次結構。每個關鍵字又可以有一些列子關鍵字、以及值。使用regedit.exe可以編輯、查看注冊表。
在注冊表HKEY_CLASSES_ROOT分支下有一個CLSID關鍵字。CLSID關鍵字下列有系統(tǒng)安裝的所有組件的CLSID。在CLSID下最重要的關鍵字是InprocServer32.此關鍵字的值是組件所在的DLL路徑名稱。由于使用CLSID來查看組件非常的麻煩,因此在每個CLSID關鍵字下都對應著一個ProgID。它是程序員給某個CLSID指定的一個易記的名稱。但是ProgID不能保證唯一。
ProgID主要作用是獲得相應的CLSID。COM庫提供了兩個函數CLSIDFromProgID和ProgIDFromCLSID.來完成ProgID和CLSID之間的相互轉換。
客戶可以在注冊表中查詢需要的組件。 但是注冊表中怎么存儲我們的組件信息呢?這是因為每個DLL都知道它所包含的組件。DLL可以將它所包含的組建信息注冊到注冊表中。因此在每個包含組件的DLL中,我們必須要輸出以下兩個函數:
- extern "C" HRESULT _stdcall DllRegisterServer()
-
- extern "C" HRESULT _stdcall DllUnregisterServer()。
這兩個函數并不需要客戶直接調用,而是提供給COM庫調用的。COM會自動搜索這兩個函數并調用。在許多程序的安裝過程中大多數安裝程序都會調用DllRegisterServer完成組件的注冊。用戶也可以使用程序REGSVR32.exe來手動注冊某個組件。RegSvr32實際上上是通過調用上述兩個函數來完成組件注冊的。
DllRegisterServer和DllUnregister是組件的生產者提供的,通過調用注冊表函數來在注冊表中添加某些項目。
有關的注冊表函數為:
- RegOpenKeyEx,
- RegCreateKeyEx,
- RegSetValueEx,
- RegEnumKeyEx,
- RegDeleteKey,
- RegClosekey.
組件類別實際上就是一個接口集合。每個組件類別都有一個GUID。此時的GUID被稱為CATID(category ID)。對于某個組件,如果它實現了某個組件類別的所有接口,那么它就可以注冊為該組件類別的一個成員。這樣,客戶就能夠通過從注冊表中選擇只屬于某個特定組件類別的組件中準確找到所需的組件。
COM庫函數提供一一組對COM對象操作的函數。它們是在OLE32.DLL中實現 。
首先介紹COM的初始化函數。因為在使用其他函數之前必須調用CoInitialize來初始化COM庫。不再使用COM庫時必須調用CoUninitialize。對每個COM庫只需初始化一次。COM庫的初始化一般在客戶代碼中進行。在提供組件的dll中則不需進行。
COM庫可以提供給用戶一個內存分配器。使用此分配器組件可以給客戶提供一塊內存。該內存可以由用戶刪除。
CoGetMalloc返回一個內存分配器IMalloc。可以使用IMalloc::Alloc申請一塊內存。使用IMalloc::Free釋放。上述過程比較麻煩。因此COM庫實現了一些方便的幫助函數如CoTaskMemAlloc和CoTaskMemFree.它們分別完成內存申請和釋放的過程。不再需要先獲得內存分配器在申請和釋放內存。
在注冊表中包含的CLSID是以字符串形式表示的。因此需要一些函數完成CLSID與字符串之間的轉換。如:
- StringFromGUID2,
- StringFromCLSID,
-
- StringFromIID,
-
- CLSIDFromString,IIDFromString。
前面我們介紹了使用CreateInstance來創(chuàng)建組建對象的例子。但是那是我們自己定義的函數。實際上在COM庫中也提供了專門創(chuàng)建COM對象的函數:CoCreateInstance。此函數也是創(chuàng)建COM對象最簡單的方式。但是CoCreateInstance卻不太靈活。在此情況下引入了類廠。
所有的組件都是通過類廠來創(chuàng)建的,客戶使用類廠來創(chuàng)建對象有很大的靈活性。
CoCreateInstance函數:
- <span style="font-size:18px;">HRESULT _stdcall CoCreateInstance(
-
- CLSID &clsid,
-
- IUnknown *pIUnknownOuter,
-
- DWORD dwClsContext,
-
- Const IID&iid,
-
- Void **ppv);
-
- </span>
clsid標識一個想要創(chuàng)建的組件。
iid標識此組件的一個接口,ppv返回接口指針。此函數可以在創(chuàng)建COM對象的同時返回該對象的相應接口指針。
dwClsContext限定所創(chuàng)建的組件的執(zhí)行上下文。
pIUnknownOuter用于組件聚合,稍后會有介紹。
- <span style="font-size:18px;"> HRESULT hr=CoCreateInstance(CLSID_CA,NULL,CLSCTX_INPROC_SERVER,IID_IUnknown,(void**)&pI);
-
- if(!SUCCEEDED(h))
-
- {
-
- pI->IY_Func();
-
- pI->Release();
-
- }
-
- </span>
CLSCTX_INPROC_SERVER告訴CoCreateInstance要加載的是進程中服務器或dll中的組件。
CoCreateInstance的第三個參數控制所創(chuàng)建的組件是在與客戶相同的進程中運行,還是在不同的進程中運行或者是在另一臺機器上運行。
此參數可以是一下值:
CLSCTX_INPROC_SERVER 客戶希望在同一進程創(chuàng)建組建。此組件必須在dll中實現。
CLSCTX_INPROC_HANDLER客戶希望創(chuàng)建進程中處理器。所謂進程中處理器實際上是只實現了某個組件的一部分的進程中組件
CLSCTX_LOCAL_SERVER 客戶希望創(chuàng)建一個在同一機器的另外一個進程中運行的組件。
CLSCTX_REMOTE_SERVER 客戶希望創(chuàng)建一個在遠程機器上運行的組件。
客戶可以在三種不同的進程上下文中使用某個組件:進程中、本地及遠程。
下面的例子創(chuàng)建了純COM客戶和組件
- <span style="font-size:18px;">int main(int argc,char**argv)
-
- {
-
- CoInitialize();
-
- IX*pIX=NULL;
-
- HRESULT hr=CoCreateInstance(CLSID_CA,NULL,CLSCTX_INPROC_SERVER,IID_IX,(void**)&pIX);
-
-
- if(SUCCEEDED(hr)
-
- {
-
- pIX->IX_Func();
-
-
- IY*pIY=NULL;
-
- hr=pIX->QueryInterface(IID_IY,(void**)&pIY);
-
- pIX->Release();
-
- if(SUCCEEDED(hr))
-
- {
-
- pIY->IY_Func();
-
- pIY->Release();
-
- }
-
- }
-
-
- CoUninitialize();
-
- return 0;
-
- }
-
- </span>
前面我們提到過使用CoCreateInstance創(chuàng)建對象不太靈活,那么接下來我們將介紹更加靈活的創(chuàng)建方式:使用類廠。
首先介紹COM庫的CoGetClassObject,它接受一個標識組件的CLSID,并返回相應類廠中某個接口指針函數。
- <span style="font-size:18px;">HRESULT _stdcall CoGetClassObject(
-
- Const CLSID&clsid,
-
- DWORD dwClsContext,
-
- COSERVERINFO*pServerInfo,
-
- Const IID&iid,
-
- Void **ppv);
-
- </span>
可以看到該函數與CoCreateInstance的參數很類似。它們的第一個參數都是待創(chuàng)建的組件的CLSID。第二個參數均為創(chuàng)建組件的上下文。pServerInfo將被DCOM用于對遠程組件的訪問。
但最大的差別在于CoGetClassObject返回的是創(chuàng)建組件的類廠的接口指針,而不是指向組件的接口指針。
客戶可以調用CoGetClassObject的返回的指針來創(chuàng)建相應的組件。該指針通常是一個IClassFactory指針。
IClassFactory接口聲明如下:
- <span style="font-size:18px;">class IClassFactory:public IUnknown
-
- {
-
- public:
-
- HRESULT _stdcall CreateInstance(
-
- IUnknown*pUnknownOuter,
-
- Const IID&iid,
-
- Void **ppv);
-
- HRESULT _stdcall LockServer
-
- };
-
- </span>
它有兩個成員函數:
CreateInstance第一個參數為指向某個IUnknown接口的指針,同CoCreateInstance的IUnknown指針是相同的,也是用于組件聚合。最后兩個的參數與QueryInterface的參數是相同的。
但是CreateInstance沒有接受標識要創(chuàng)建組件的CLSID,那么它如何獲得要創(chuàng)建組件的CLSID呢?大家可以看下CoGetClassObject。它接受一個標識組件的CLSID。哦,原來如此?。?/span>
其實在前面介紹過的CoCreateInstance ,它在實現時使用了CoGetClassObject和CreateInstance。雖然使用CoCreateInstance函數靈活性不好,但是很簡單。在滿足需要的前提下此函數經常被使用。接下來看一下CoCreateInstance的實現:
- HRESULT _stdcall CoCreateInstance(
-
- const CLSID&clsid,
-
- IUnknown*pUnknownOuter,
-
- DWORD dwClsContext,
-
- const IID&iid,
-
- void **ppv)
-
- {
-
- IClassFactory pIFactroy=NULL;
-
- HRESULT hr=CoGetClassObject(clsid,dwClsContext,NULL,IID_CLASSFACTORY,(void**)&pIFactroy);
-
- if(SUCCEEDED(hr))
-
- {
-
-
- pIFactroy->CreateInstance(pUnknownOuter,IID_IX,ppv);
-
- pIFactroy->Release();
-
- pIX->IX_Func();
-
- pIX->Release();
-
- }
-
-
- }
大多數情況下組件的創(chuàng)建都是使用CoCreateInstance而不是CoGetClassObject。但是當創(chuàng)建一個組件的多個實例時使用CoGetClassObject具有很提高的效率。因為此時并不需要創(chuàng)建多個類廠。一個類廠完成所有組件實例的創(chuàng)建。
DllGetClassObject 完成類廠的創(chuàng)建。此函數被CoGetClassObject調用。
- <span style="font-size:18px;">STDAPI DllGetClassObject(
-
- Const CLSID&clsid,
-
- Const IID&iid,
-
- Void **ppv);
-
- </span>
此函數的三個參數與CoGetClassObject相同。
接下來我們完整的介紹下組件的創(chuàng)建過程:
首先,客戶調用COM庫函數CoGetClassObject,此函數調用組件內提供的函數DllGetClassObject完成類廠的創(chuàng)建并返回類廠指針,然后使用類廠指針調用COM庫IClassFactory::CreateInstance創(chuàng)建組件對象并返回接口指針。
可以注意到dll中導出四個函數。它們分別是:
- DllGetClassObject,
-
- DllCanUnloadNow,
-
- DllReigisterServer,
-
- DllUnregisterServer。
前面提到過可以在一個dll中實現多個組件。之所以可以實現這一點就是因為導出函數:DllGetClassObject。它能夠根據不同的CLSID,創(chuàng)建對應的類廠。一個DLL可以支持多個組件也從側面說明了DLL并不等價于組件,而是相當于一個組件服務器。
DLL的卸載
DllCanUnloadNow和LockServer。
DLL內可以有多個組件。為了使DLL在所有組件都不使用后被卸載,需要在dll內維護一個當前可用組件的計數g_NumOfCom;當此值為0時說明沒有對象正在被使用,dll就可以被卸載了。g_NumCom會在組件的構造函數或IClassFactory::CreateInstance中被增加,在組件的析構函數中減小。
LockServer是IClassFactory的成員函數,它對鎖計數器進行操作。當鎖計數器大于0時,可以防止該類廠所占空間被釋放掉。假設客戶擁有一個指向某類廠的指針,在某個DLL被卸載后,該類廠對應的空間被釋放掉,類廠指針就變成了一個野指針,使用時會造成違規(guī)訪問。
初始時鎖計數器為0,LockServer(true)會使鎖計數器加一,此時類廠對象被鎖住,保留在內存中不被釋放,使用完畢后再次調用此函數解鎖。
LockServer(false)會使鎖計數器的值減一。
DllCanUnloadNow可以返回該DLL是否可以被卸載。若現在沒有組件在被使用,那么此時該DLL就可以從內存中撤銷了。
組件個數和鎖計數器可以使用同一個數值g_NumOfCom,也可以分別使用。當使用不同的值時在DllCanUnloadNow中就必須對這兩個值進行判斷,只有當它們都為0時,dll才能被卸載。
Win7下調用RegCreateKeyEx在HKEY_CLASSES_ROOT添加鍵,需要在程序運行時獲得管理員權限。否則返回值為5,權限不足。
下面的代碼綜合了前面介紹的所有知識,是一個相對比較完整的例子:
- <span style="font-size:18px;">IX.h
- #include"objbase.h"
- class IX:public IUnknown
- {
- public:
- virtual void IX_Func()=0;
- };</span>
- <span style="font-size:18px;">//IY.h
- #include"objbase.h"
- class IY:public IUnknown
- {
- public:
- virtual void IY_Func()=0;
- };</span>
- <span style="font-size:18px;">//CA.h
- #ifndef CA_H
- #define CA_H
- #include<iostream>
- #include"IX.h"
- #include"IY.h"
- extern UINT g_NumOfCom;
- class CA:public IX,public IY
- {
- public:
- CA()
- {
- m_Ref=0;
- g_NumOfCom++;
- std::cout<<"CA構造函數被調用??!"<<std::endl;
- }
- ~CA()
- {
- std::cout<<"CA析構函數被調用??!"<<std::endl;
- g_NumOfCom--;
- }
- HRESULT _stdcall QueryInterface(const IID&id,void **ppv);
- ULONG _stdcall AddRef();
- ULONG _stdcall Release();
-
- void IX_Func();
- void IY_Func();
- public:
- ULONG m_Ref;
- };
- #endif</span>
- <span style="font-size:18px;">//CA.cpp
- #include"StdAfx.h"
- #include"CA.h"
- extern IID IID_IX;
- extern IID IID_IY;
- // {AB4B7F96-B8A5-4BB3-BF44-8FB158ED36AD}
-
- extern IID IID_CA;
- HRESULT CA::QueryInterface(const IID&id,void**ppv)
- {
- std::cout<<"CA::QueryInterface被調用!!"<<std::endl;
- if(id==IID_IUnknown)
- {
- *ppv=static_cast<IX*>(this);
- }
- else if(id==IID_IX)
- {
- *ppv=static_cast<IX*>(this);
- }
- else if(id==IID_IY)
- {
- *ppv=static_cast<IY*>(this);
- }
- else
- {
- *ppv=NULL;
- return E_NOINTERFACE;
- }
- static_cast<IUnknown*>(*ppv)->AddRef();
- return S_OK;
- };
- ULONG CA::AddRef()
- {
- std::cout<<"CA::Addref被調用?。?<<std::endl;
- m_Ref++;
- return m_Ref;
- }
- ULONG CA::Release()
- {
- std::cout<<"CA:release被調用?。?<<std::endl;
- if(--m_Ref==0)
- {
- delete this;
- }
- return m_Ref;
- }
- void CA::IX_Func()
- {
- std::cout<<"IX_Func被調用??!"<<std::endl;
- }
- void CA::IY_Func()
- {
- std::cout<<"IY_Func被調用?。?<<std::endl;
- }</span>
- <span style="font-size:18px;">//CFactory.h
- #include<objbase.h>
-
- class CFactory:public IClassFactory
- {
- public:
- CFactory();
- ~CFactory();
- HRESULT _stdcall CreateInstance(IUnknown*pUnknownOuter,const IID&iid,void **ppv);
- HRESULT _stdcall LockServer( BOOL fLock);
- HRESULT _stdcall QueryInterface(const IID&iid,void**ppv);
- ULONG _stdcall AddRef();
- ULONG _stdcall Release();
- private:
- LONG m_Ref;
- };
-
- </span>
- <span style="font-size:18px;">//CFactory.cpp
- #include"stdafx.h"
- #include"CFactory.h"
- #include"CA.h"
- // {186E5F66-438C-49EF-B8E3-29BB2B8CC133}
-
-
- extern UINT g_NumOfCom;
- extern GUID CLSID_FACTORY;
- UINT g_ServerLocks=0;
- HRESULT _stdcall CFactory::CreateInstance(IUnknown*pUnknownOuter,const IID&iid,void **ppv)
- {
- std::cout<<"CFactory::CreateInstance被調用??!"<<std::endl;
- if(pUnknownOuter)
- {
- return CLASS_E_NOAGGREGATION;
- }
- CA*pCA=new CA;
- if(!pCA)
- {
- std::cout<<"new CA申請失?。?!"<<std::endl;
- return E_OUTOFMEMORY;
- }
- std::cout<<"new CA創(chuàng)建成功!?。?<<std::endl;
- HRESULT hr=pCA->QueryInterface(iid,ppv);
- pCA->Release();
- return hr;
-
- }
- HRESULT _stdcall CFactory::QueryInterface( const IID&iid,void**ppv )
- {
- std::cout<<"CFactory::QueryInterface被調用?。?<<std::endl;
- //if(iid==IID_IUnknown||iid==CLSID_FACTORY)
- //if()
- {
- *ppv=static_cast<IClassFactory*>(this);
- }
- //else
- {
- //*ppv=NULL;
- //std::cout<<"CFactory:接口查詢失?。?!"<<std::endl;
- //return E_NOINTERFACE;
- }
- static_cast<IUnknown*>(*ppv)->AddRef();
- return S_OK;
- }
-
- ULONG _stdcall CFactory::AddRef()
- {
- std::cout<<" CFactory::AddRef被調用??!"<<std::endl;
- m_Ref++;
- return m_Ref;
- }
-
- ULONG _stdcall CFactory::Release()
- {
- std::cout<<"CFactory::Release被調用??!"<<std::endl;
- if(--m_Ref==0)
- {
- delete this;
- }
- return m_Ref;
- }
-
- CFactory::CFactory()
- {
- std::cout<<"CFactory構造函數被調用!!"<<std::endl;
- m_Ref=1;
- g_NumOfCom++;
- }
-
- HRESULT _stdcall CFactory::LockServer( BOOL fLock )
- {
- if(fLock)
- {
- g_ServerLocks++;
- }
- else
- {
- g_ServerLocks--;
- }
- return S_OK;
- }
-
- CFactory::~CFactory()
- {
- std::cout<<"CFactory析構函數被調用?。?<<std::endl;
- g_NumOfCom--;
- }
- </span>
- <span style="font-size:18px;">//dll.DEF
- ;LIBRARY "dll.dll"
- EXPORTS
- DllGetClassObject PRIVATE
- DllCanUnloadNow PRIVATE</span>
- <span style="font-size:18px;">//dll.h
- // 下列 ifdef 塊是創(chuàng)建使從 DLL 導出更簡單的
- // 宏的標準方法。此 DLL 中的所有文件都是用命令行上定義的 DLL_EXPORTS
- // 符號編譯的。在使用此 DLL 的
- // 任何其他項目上不應定義此符號。這樣,源文件中包含此文件的任何其他項目都會將
- // DLL_API 函數視為是從 DLL 導入的,而此 DLL 則將用此宏定義的
- // 符號視為是被導出的。
- #ifdef DLL_EXPORTS
- #define DLL_API __declspec(dllexport)
- #else
- #define DLL_API __declspec(dllimport)
- #endif
- #include"objbase.h"
- DllCanUnloadNow(void);
- DLL_API STDAPI DllGetClassObject(const CLSID&clsid,const IID&iid,void**ppv);
- extern "C" DLL_API HRESULT DllRegisterServer();
- extern "C" DLL_API HRESULT DllUnregisterServer(); </span>
- <pre class="cpp" name="code"><span style="font-size:18px;">// dllmain.cpp :
- 定義 DLL 應用程序的入口點。
- #include "stdafx.h"
- extern HMODULE g_DLLModule;
- BOOL APIENTRY DllMain( HMODULE hModule,
- DWORD ul_reason_for_call,
- LPVOID lpReserved
- )
- {
- switch (ul_reason_for_call)
- {
- case DLL_PROCESS_ATTACH:
- g_DLLModule=hModule;
- case DLL_THREAD_ATTACH:
- case DLL_THREAD_DETACH:
- case DLL_PROCESS_DETACH:
- break;
- }
- return TRUE;
- }
- </span></pre>
- <pre></pre>
- <pre class="cpp" name="code"><span style="font-size:18px;">//GUID.cpp
- #include"StdAfx.h"
- IID IID_IUnknown =
- { 0x49fa8f03, 0x1ab0, 0x4d75, { 0xb0, 0x23, 0x54, 0xb7, 0xf, 0xa7, 0x31, 0xc2 } };
-
- IID IID_IX =
- { 0x8af3709f, 0xa8eb, 0x46c4, { 0xb5, 0x1, 0xbc, 0xb6, 0x7d, 0x45, 0x9a, 0xfe } };
- IID IID_IY =
- { 0xc18d13a4, 0x57af, 0x41d7, { 0xb5, 0xf2, 0x46, 0xc1, 0xfe, 0xa6, 0xbc, 0x37 } };
- CLSID CLSID_CA =
- { 0xab4b7f96, 0xb8a5, 0x4bb3, { 0xbf, 0x44, 0x8f, 0xb1, 0x58, 0xed, 0x36, 0xad } };
- CLSID CLSID_FACTORY =
- { 0x186e5f66, 0x438c, 0x49ef, { 0xb8, 0xe3, 0x29, 0xbb, 0x2b, 0x8c, 0xc1, 0x33 } };</span></pre><pre class="cpp" name="code"></pre><pre class="cpp" name="code"></pre>
- <pre></pre>
|