在windows系統(tǒng)中,注冊表是一個正式的共享系統(tǒng)數(shù)據(jù)庫。注冊表中包含關(guān)于系統(tǒng)軟硬件以及配置和用戶的各種信息。在COM技術(shù)中使用注冊表存儲關(guān)于組件的信息。客戶可以再注冊表中搜索它需要的組件。
注冊表有許多關(guān)鍵字構(gòu)成的層次結(jié)構(gòu)。每個關(guān)鍵字又可以有一些列子關(guān)鍵字、以及值。使用regedit.exe可以編輯、查看注冊表。
在注冊表HKEY_CLASSES_ROOT分支下有一個CLSID關(guān)鍵字。CLSID關(guān)鍵字下列有系統(tǒng)安裝的所有組件的CLSID。在CLSID下最重要的關(guān)鍵字是InprocServer32.此關(guān)鍵字的值是組件所在的DLL路徑名稱。由于使用CLSID來查看組件非常的麻煩,因此在每個CLSID關(guān)鍵字下都對應(yīng)著一個ProgID。它是程序員給某個CLSID指定的一個易記的名稱。但是ProgID不能保證唯一。
ProgID主要作用是獲得相應(yīng)的CLSID。COM庫提供了兩個函數(shù)CLSIDFromProgID和ProgIDFromCLSID.來完成ProgID和CLSID之間的相互轉(zhuǎn)換。
客戶可以在注冊表中查詢需要的組件。 但是注冊表中怎么存儲我們的組件信息呢?這是因為每個DLL都知道它所包含的組件。DLL可以將它所包含的組建信息注冊到注冊表中。因此在每個包含組件的DLL中,我們必須要輸出以下兩個函數(shù):
- extern "C" HRESULT _stdcall DllRegisterServer()
-
- extern "C" HRESULT _stdcall DllUnregisterServer()。
這兩個函數(shù)并不需要客戶直接調(diào)用,而是提供給COM庫調(diào)用的。COM會自動搜索這兩個函數(shù)并調(diào)用。在許多程序的安裝過程中大多數(shù)安裝程序都會調(diào)用DllRegisterServer完成組件的注冊。用戶也可以使用程序REGSVR32.exe來手動注冊某個組件。RegSvr32實際上上是通過調(diào)用上述兩個函數(shù)來完成組件注冊的。
DllRegisterServer和DllUnregister是組件的生產(chǎn)者提供的,通過調(diào)用注冊表函數(shù)來在注冊表中添加某些項目。
有關(guān)的注冊表函數(shù)為:
- RegOpenKeyEx,
- RegCreateKeyEx,
- RegSetValueEx,
- RegEnumKeyEx,
- RegDeleteKey,
- RegClosekey.
組件類別實際上就是一個接口集合。每個組件類別都有一個GUID。此時的GUID被稱為CATID(category ID)。對于某個組件,如果它實現(xiàn)了某個組件類別的所有接口,那么它就可以注冊為該組件類別的一個成員。這樣,客戶就能夠通過從注冊表中選擇只屬于某個特定組件類別的組件中準(zhǔn)確找到所需的組件。
COM庫函數(shù)提供一一組對COM對象操作的函數(shù)。它們是在OLE32.DLL中實現(xiàn) 。
首先介紹COM的初始化函數(shù)。因為在使用其他函數(shù)之前必須調(diào)用CoInitialize來初始化COM庫。不再使用COM庫時必須調(diào)用CoUninitialize。對每個COM庫只需初始化一次。COM庫的初始化一般在客戶代碼中進(jìn)行。在提供組件的dll中則不需進(jìn)行。
COM庫可以提供給用戶一個內(nèi)存分配器。使用此分配器組件可以給客戶提供一塊內(nèi)存。該內(nèi)存可以由用戶刪除。
CoGetMalloc返回一個內(nèi)存分配器IMalloc。可以使用IMalloc::Alloc申請一塊內(nèi)存。使用IMalloc::Free釋放。上述過程比較麻煩。因此COM庫實現(xiàn)了一些方便的幫助函數(shù)如CoTaskMemAlloc和CoTaskMemFree.它們分別完成內(nèi)存申請和釋放的過程。不再需要先獲得內(nèi)存分配器在申請和釋放內(nèi)存。
在注冊表中包含的CLSID是以字符串形式表示的。因此需要一些函數(shù)完成CLSID與字符串之間的轉(zhuǎn)換。如:
- StringFromGUID2,
- StringFromCLSID,
-
- StringFromIID,
-
- CLSIDFromString,IIDFromString。
前面我們介紹了使用CreateInstance來創(chuàng)建組建對象的例子。但是那是我們自己定義的函數(shù)。實際上在COM庫中也提供了專門創(chuàng)建COM對象的函數(shù):CoCreateInstance。此函數(shù)也是創(chuàng)建COM對象最簡單的方式。但是CoCreateInstance卻不太靈活。在此情況下引入了類廠。
所有的組件都是通過類廠來創(chuàng)建的,客戶使用類廠來創(chuàng)建對象有很大的靈活性。
CoCreateInstance函數(shù):
- <span style="font-size:18px;">HRESULT _stdcall CoCreateInstance(
-
- CLSID &clsid,
-
- IUnknown *pIUnknownOuter,
-
- DWORD dwClsContext,
-
- Const IID&iid,
-
- Void **ppv);
-
- </span>
clsid標(biāo)識一個想要創(chuàng)建的組件。
iid標(biāo)識此組件的一個接口,ppv返回接口指針。此函數(shù)可以在創(chuàng)建COM對象的同時返回該對象的相應(yīng)接口指針。
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要加載的是進(jìn)程中服務(wù)器或dll中的組件。
CoCreateInstance的第三個參數(shù)控制所創(chuàng)建的組件是在與客戶相同的進(jìn)程中運行,還是在不同的進(jìn)程中運行或者是在另一臺機(jī)器上運行。
此參數(shù)可以是一下值:
CLSCTX_INPROC_SERVER 客戶希望在同一進(jìn)程創(chuàng)建組建。此組件必須在dll中實現(xiàn)。
CLSCTX_INPROC_HANDLER客戶希望創(chuàng)建進(jìn)程中處理器。所謂進(jìn)程中處理器實際上是只實現(xiàn)了某個組件的一部分的進(jìn)程中組件
CLSCTX_LOCAL_SERVER 客戶希望創(chuàng)建一個在同一機(jī)器的另外一個進(jìn)程中運行的組件。
CLSCTX_REMOTE_SERVER 客戶希望創(chuàng)建一個在遠(yuǎn)程機(jī)器上運行的組件。
客戶可以在三種不同的進(jìn)程上下文中使用某個組件:進(jìn)程中、本地及遠(yuǎn)程。
下面的例子創(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,它接受一個標(biāo)識組件的CLSID,并返回相應(yīng)類廠中某個接口指針函數(shù)。
- <span style="font-size:18px;">HRESULT _stdcall CoGetClassObject(
-
- Const CLSID&clsid,
-
- DWORD dwClsContext,
-
- COSERVERINFO*pServerInfo,
-
- Const IID&iid,
-
- Void **ppv);
-
- </span>
可以看到該函數(shù)與CoCreateInstance的參數(shù)很類似。它們的第一個參數(shù)都是待創(chuàng)建的組件的CLSID。第二個參數(shù)均為創(chuàng)建組件的上下文。pServerInfo將被DCOM用于對遠(yuǎn)程組件的訪問。
但最大的差別在于CoGetClassObject返回的是創(chuàng)建組件的類廠的接口指針,而不是指向組件的接口指針。
客戶可以調(diào)用CoGetClassObject的返回的指針來創(chuàng)建相應(yī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>
它有兩個成員函數(shù):
CreateInstance第一個參數(shù)為指向某個IUnknown接口的指針,同CoCreateInstance的IUnknown指針是相同的,也是用于組件聚合。最后兩個的參數(shù)與QueryInterface的參數(shù)是相同的。
但是CreateInstance沒有接受標(biāo)識要創(chuàng)建組件的CLSID,那么它如何獲得要創(chuàng)建組件的CLSID呢?大家可以看下CoGetClassObject。它接受一個標(biāo)識組件的CLSID。哦,原來如此??!
其實在前面介紹過的CoCreateInstance ,它在實現(xiàn)時使用了CoGetClassObject和CreateInstance。雖然使用CoCreateInstance函數(shù)靈活性不好,但是很簡單。在滿足需要的前提下此函數(shù)經(jīng)常被使用。接下來看一下CoCreateInstance的實現(xiàn):
- 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();
-
- }
-
-
- }
大多數(shù)情況下組件的創(chuàng)建都是使用CoCreateInstance而不是CoGetClassObject。但是當(dāng)創(chuàng)建一個組件的多個實例時使用CoGetClassObject具有很提高的效率。因為此時并不需要創(chuàng)建多個類廠。一個類廠完成所有組件實例的創(chuàng)建。
DllGetClassObject 完成類廠的創(chuàng)建。此函數(shù)被CoGetClassObject調(diào)用。
- <span style="font-size:18px;">STDAPI DllGetClassObject(
-
- Const CLSID&clsid,
-
- Const IID&iid,
-
- Void **ppv);
-
- </span>
此函數(shù)的三個參數(shù)與CoGetClassObject相同。
接下來我們完整的介紹下組件的創(chuàng)建過程:
首先,客戶調(diào)用COM庫函數(shù)CoGetClassObject,此函數(shù)調(diào)用組件內(nèi)提供的函數(shù)DllGetClassObject完成類廠的創(chuàng)建并返回類廠指針,然后使用類廠指針調(diào)用COM庫IClassFactory::CreateInstance創(chuàng)建組件對象并返回接口指針。
可以注意到dll中導(dǎo)出四個函數(shù)。它們分別是:
- DllGetClassObject,
-
- DllCanUnloadNow,
-
- DllReigisterServer,
-
- DllUnregisterServer。
前面提到過可以在一個dll中實現(xiàn)多個組件。之所以可以實現(xiàn)這一點就是因為導(dǎo)出函數(shù):DllGetClassObject。它能夠根據(jù)不同的CLSID,創(chuàng)建對應(yīng)的類廠。一個DLL可以支持多個組件也從側(cè)面說明了DLL并不等價于組件,而是相當(dāng)于一個組件服務(wù)器。
DLL的卸載
DllCanUnloadNow和LockServer。
DLL內(nèi)可以有多個組件。為了使DLL在所有組件都不使用后被卸載,需要在dll內(nèi)維護(hù)一個當(dāng)前可用組件的計數(shù)g_NumOfCom;當(dāng)此值為0時說明沒有對象正在被使用,dll就可以被卸載了。g_NumCom會在組件的構(gòu)造函數(shù)或IClassFactory::CreateInstance中被增加,在組件的析構(gòu)函數(shù)中減小。
LockServer是IClassFactory的成員函數(shù),它對鎖計數(shù)器進(jìn)行操作。當(dāng)鎖計數(shù)器大于0時,可以防止該類廠所占空間被釋放掉。假設(shè)客戶擁有一個指向某類廠的指針,在某個DLL被卸載后,該類廠對應(yīng)的空間被釋放掉,類廠指針就變成了一個野指針,使用時會造成違規(guī)訪問。
初始時鎖計數(shù)器為0,LockServer(true)會使鎖計數(shù)器加一,此時類廠對象被鎖住,保留在內(nèi)存中不被釋放,使用完畢后再次調(diào)用此函數(shù)解鎖。
LockServer(false)會使鎖計數(shù)器的值減一。
DllCanUnloadNow可以返回該DLL是否可以被卸載。若現(xiàn)在沒有組件在被使用,那么此時該DLL就可以從內(nèi)存中撤銷了。
組件個數(shù)和鎖計數(shù)器可以使用同一個數(shù)值g_NumOfCom,也可以分別使用。當(dāng)使用不同的值時在DllCanUnloadNow中就必須對這兩個值進(jìn)行判斷,只有當(dāng)它們都為0時,dll才能被卸載。
Win7下調(diào)用RegCreateKeyEx在HKEY_CLASSES_ROOT添加鍵,需要在程序運行時獲得管理員權(quán)限。否則返回值為5,權(quán)限不足。
下面的代碼綜合了前面介紹的所有知識,是一個相對比較完整的例子:
- <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構(gòu)造函數(shù)被調(diào)用??!"<<std::endl;
- }
- ~CA()
- {
- std::cout<<"CA析構(gòu)函數(shù)被調(diào)用??!"<<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被調(diào)用??!"<<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被調(diào)用??!"<<std::endl;
- m_Ref++;
- return m_Ref;
- }
- ULONG CA::Release()
- {
- std::cout<<"CA:release被調(diào)用??!"<<std::endl;
- if(--m_Ref==0)
- {
- delete this;
- }
- return m_Ref;
- }
- void CA::IX_Func()
- {
- std::cout<<"IX_Func被調(diào)用!!"<<std::endl;
- }
- void CA::IY_Func()
- {
- std::cout<<"IY_Func被調(diào)用??!"<<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被調(diào)用!!"<<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被調(diào)用!!"<<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被調(diào)用??!"<<std::endl;
- m_Ref++;
- return m_Ref;
- }
-
- ULONG _stdcall CFactory::Release()
- {
- std::cout<<"CFactory::Release被調(diào)用!!"<<std::endl;
- if(--m_Ref==0)
- {
- delete this;
- }
- return m_Ref;
- }
-
- CFactory::CFactory()
- {
- std::cout<<"CFactory構(gòu)造函數(shù)被調(diào)用?。?<<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析構(gòu)函數(shù)被調(diào)用??!"<<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 導(dǎo)出更簡單的
- // 宏的標(biāo)準(zhǔn)方法。此 DLL 中的所有文件都是用命令行上定義的 DLL_EXPORTS
- // 符號編譯的。在使用此 DLL 的
- // 任何其他項目上不應(yīng)定義此符號。這樣,源文件中包含此文件的任何其他項目都會將
- // DLL_API 函數(shù)視為是從 DLL 導(dǎo)入的,而此 DLL 則將用此宏定義的
- // 符號視為是被導(dǎo)出的。
- #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 應(yīng)用程序的入口點。
- #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>
|