第7章 类厂
相信有了前六章的知识积累,学些以后的章节将会很顺利。本章实现了一个真正的COM组件,并通过client客户端来使用这个组件。学完这章,对COM组件的最基本的东西就已经学完了,可以实现一个真正意义上的组件,后续几章就是在此基础上进行的升华,例如组件的复用,EXE中的组件,多线程等等,也同样重要。
本章将介绍类厂,类厂是能够创建其他组件的组件,并且可以使用同一个类厂来创建不同的CLSID所对应的组件,也就是说,在同一个DLL中,可以包含多个组件(组件是用C++类来实现的),并可以使用同一个类厂来创建它们,我想这就是类厂名字的由来。
CoCreateInstance函数是创建组件并获得IUnknown接口,虽然在使用过程中并没有使用类厂,但是它也是按照一定方法通过类厂来创建组件的。
CoCreateInstance
CoCreateInstance函数是COM库的函数,函数原型如下
HRESULT __stdcall CoCreateInstance(const CLSID &clsid, IUnknown *pIUnkownOuter, //Outer Component DWORD dwClsContext, //Server Context const IID &iid, void **ppv);
其中前四个是输入参数,最后一个是输出参数。第一个参数clsid是所创建组件的CLSID,第二个参数pIUnkownOuter是聚合组件需要用的,将在第8章介绍,第三个参数dwClsContext是限定所创建组件的执行上下文,第四个参数iid是所创建组件的接口的IID,第五个参数ppv将返回该接口的指针。
dwClsContext
dwClsContext可以控制所创建是与客户在相同的进程中运行,还是在不同的进程中运行,或者是在另外一台机器上运行。此参数可以是如下值的组合
CLSCTX_INPROC_SERVER 客户希望创建在同一进程中运行的组件,因此组件必须是在DLL中实现。
CLSCTX_INPROC_HANDLER 客户希望创建进程中处理器。
CLSCTX_LOCAL_SERVER 客户希望创建一个在同一机器上的另外一个进程中运行的组件。组件是由EXE实现的。
CLSCTX_REMOTE_SERVER 客户希望创建一个在远程机器上运行的组件。分布式COM组件。
在OBJBASE.H中定义了一些上述值的组合。
CoCreateInstance的具有一定的不灵活性,解决问题的办法是使用专门用于创建所需组件的组件,这个组件就是类厂。
类厂
某个特定的类厂可以创建某个特定CLSID相对应的组件,客户可以通过类厂提供的接口来对组件的创建过程进行控制。客户使用CoCreateInstance所创建的组件实际上是通过类厂的IClassFactory创建的,使用类厂创建组件的步骤是首先创建类厂,然后使用IClassFactory创建所需的组件。
1.创建类厂
COM库函数CoGetClassObject创建同某个CLSID相应的类厂。函数原型如下
HRESULT __stdcall CoGetClassObject(const CLSID &clsid, DWORD dwClsContext, COSERVERINFO *pServerInfo, const IID &iid, void **ppv);
2.类厂组件
IClassFactory
大多数组件是使用IClassFactory来创建的,原型如下
interface IClassFactory:IUnknown { HRESULT __stdcall CreateInstance(IUnkown *pUnknownOuter, const IID &id, void **ppv); HRESULT __stdcall LockServer(BOOL bLock); };
同时返回此组件的某个接口指针。可以看到IClassFactory::CreateInstance并没有接收一个CLSID参数,这意味着此函数将只能创建同某个CLSID——即传给CoGetClassObject的参数CLSID相应的组件。
IClassFactory::CreateInstance和DllGetClassObject的实现是相同的,这两个函数都将创建一个组件然后向它查询某个接口。
在两种情况下使用创建类厂再创建组件的方法,而不是直接使用CoCreateInstance的方法直接创建组件
(1)想使用IClassFactory2来创建组件。IClassFactory2是Microsoft定义的另外一个接口,此接口在IClassFactory的基础上增加了获取组件接口的许可权限功能。
(2)需要创建一个组件的多个实例。这样只需创建相应的类厂一次,而CoCreateInstance需要为每一个组件实例分别创建并释放相应的类厂。
类厂的特性
(1)类厂将只能给你创建同某个CLSID相应的组件。
(2)与某个特定CLSID相应的类厂是由组件开发人员来实现的。大多数情况下,类厂组件包含在它所创建的组件的同一个的DLL中。
类厂的创建
客户通过CoGetClassObject来创建类厂,这就需要在组件的DLL中实现一个特定的函数,此函数名为DllGetClassObject,由COM库函数CoGetClassObject调用,函数原型如下
STDAPI DllGetClassObject(const CLSID &clsid, const IID &iid, void **ppv);函数的三个参数同CoGetClassObject中传入的参数的意义相同。
通过类厂来创建组件的示意图如下,COM库函数CoGetClassObject将根据传入参数CLSID查询注册表,装载组件所在的DLL库。
首先客户调用CoGetClassObject,COM库实现CoGetClassObject函数,组件所在的DLL输出DllGetClassObject函数由CoGetClassObject函数调用,它返回IClassFactory指针,客户调用IClassFactory来创建相应的组件。IClassFactory如何实现,也是由组件的创建者来实现的。
组件的注册
实现组件的DLL中输出四个函数,除了DllGetClassObject外,DllRegisterServer和DllUnregisterServer将用于组件在注册表中注册和取消注册(链接的时候需要链接Advapi32.lib),调用regsvr32来完成注册和取消。
类厂的复用
在设计类厂和组件的时候,可以做到只用一个类厂的实现来完成所有组件的创建。将在第9章中实现。但即使是这样,类厂CFactory的一个实例也仅能创建一个同某个CLSID相应的组件。
DLL的卸载
利用类厂创建组件,那什么时候卸载组件呢,卸载组件需要两个引用计数,一个是类厂的计数,一个是组件的计数。
COM库中实现了一个CoFreeUnusedLibraries的函数,以释放那些不再需要使用的DLL库所占用的内存,由组件的客户进行调用。
DllCanUnloadNow函数
在实现组件的DLL中的输出函数。使用者应该周期性调用CoFreeUnusedLibraries函数,它将调用组件的DllCanUnloadNow函数,以确定Dll是否可以被卸载。代码中g_lComponents的作用是对组件计数,因为DllCanUnloadNow是个全局函数,而且一个DLL中可能有一个类厂,但是多个组件。所以需要一个全局变量来对组件进行计数。可以看到IClassFactory::CreateInstance也就是说在创建其中一个组件的时候,组件的构造函数将g_lComponents增大,组件的析构函数将g_lComponents的值减小。若g_lComponets值为0,CoFreeUnusedLibraries可以将包含组件的DLL卸载掉。
此处需要分清组件的引用计数m_cRef和g_lComponent的关系。组件的引用计数,是一个组件自己的引用计数,调用AddRef和Release对它进行加减,控制这个组件的创建和析构。而g_lComponent是Dll的引用计数,是所有组件的计数,当一个组件创建的时候+1,析构的时候-1,当所有组件(不包括类厂)都析构完成后,g_lComponent==0;
LockServer函数
使用g_lComponents只是对DLL中的组件进行了记数,对DLL中的另一个组件类厂并没有记数。对类厂的记数使用了IFactory::LockServer函数,组件Server内部设置了另外一个与g_IComponents不同的计数值进行计数。(将在第10章进行讨论,主要原因是因为第10章将会讲到的进程外组件(exe实现的)的类厂无法像进程中的组件(dll实现的)一样方便的进行记数)。DllCanUnloadNow函数,需要对这两个数值同时进行统计,以确定组件及类厂所在的DLL是否可以卸载掉。
本章代码
组件端:
cmpnt.cpp
// //cmpnt.cpp //use: cl /LD cmpnt.cpp guids.cpp registry.cpp cmpnt.def uuid.lib ole32.lib Advapi32.lib // regsvr32 -s cmpnt.dll #include <objbase.h> #include "iface.h" //interface declarations #include "Registry.h" //Registry helper function #include <iostream> #include <string> using namespace std; //trace function void trace(string msg) { cout<<msg<<endl; } //global variable // static HMODULE g_hModule = NULL; //DLL Module handle static long g_lComponent = 0; //Count of active component static long g_lServerLocks = 0; //Count of locks //Friendly name of component const char g_szFriendlyName[] = "InsideCOM Chapter 7 Example"; //Version independent ProgID const char g_szVerIndProgID[] = "InsideCOM.Chap07"; //ProgID const char g_szProgID[] = "InsideCOM.Chap07.1"; //Component class CA:public IX, public IY { public: //IUnknown virtual HRESULT __stdcall QueryInterface(const IID &iid, void **ppv); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); virtual void __stdcall Fx() {cout<<"Fx"<<endl;} virtual void __stdcall Fy() {cout<<"Fy"<<endl;} CA(); ~CA(); protected: long m_cRef; }; CA::CA() { m_cRef = 1; //注意这里与前几章不一样,m_cRef=1意味着只要CA组件被创建就计数为1,这样IFactory::CreateInstance创建CA组 //件时,就不用调用AddRef()了 InterlockedIncrement(&g_lComponent); } CA::~CA() { InterlockedDecrement(&g_lComponent); trace("Component:destroy self"); } //IUnknown implement HRESULT __stdcall CA::QueryInterface(const IID &iid, void **ppv) { if(iid == IID_IUnknown) { *ppv = static_cast<IX*>(this); } else if(iid == IID_IX) { *ppv = static_cast<IX*>(this); trace("component: return pointer to ix"); } else if(iid == IID_IY) { *ppv = static_cast<IY*>(this); trace("component: return pointer to iy"); } else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast<IUnknown*>(*ppv)->AddRef(); return S_OK; } ULONG __stdcall CA::AddRef() { return InterlockedIncrement(&m_cRef); } ULONG __stdcall CA::Release() { if(InterlockedDecrement(&m_cRef)== 0) { delete this; return 0; } return m_cRef; } /////////////////////////////////////////// //class factory // class CFactory:public IClassFactory { public: //iunknown virtual HRESULT __stdcall QueryInterface(const IID &iid, void **ppv); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); //interface IClassFactory virtual HRESULT __stdcall CreateInstance(IUnknown *pUnknownOuter, const IID &iid, void **ppv); virtual HRESULT __stdcall LockServer(BOOL bLock); //constructor CFactory():m_cRef(1){} //destructor ~CFactory() { trace("class factory :destory self"); } private: long m_cRef; }; // //class factory IUnknown implementation // HRESULT __stdcall CFactory::QueryInterface(const IID &iid, void **ppv) { if((iid == IID_IUnknown) || (iid == IID_IClassFactory)) { *ppv = static_cast<IClassFactory*>(this); } else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast<IUnknown*>(*ppv)->AddRef(); return S_OK; } ULONG __stdcall CFactory::AddRef() { return InterlockedIncrement(&m_cRef); } ULONG __stdcall CFactory::Release() { if(InterlockedDecrement(&m_cRef) == 0) { delete this; return 0; } return m_cRef; } // //IClassFactory implementation // HRESULT __stdcall CFactory::CreateInstance(IUnknown *pUnknownOuter, const IID &iid, void **ppv) { trace("class factory : create component"); //Can not aggregate if(pUnknownOuter != NULL) { return CLASS_E_NOAGGREGATION; } //create component CA *pA = new CA(); if(pA == NULL) { return E_OUTOFMEMORY; } //Get requested interface HRESULT hr = pA->QueryInterface(iid, ppv); //Release the IUnknown pointer pA->Release(); return hr; } //lock server HRESULT __stdcall CFactory::LockServer(BOOL bLock) { if(bLock) { InterlockedIncrement(&g_lServerLocks); } else { InterlockedDecrement(&g_lServerLocks); } return S_OK; } ///////////////////////////////////////////// //export function // //get class factory STDAPI DllGetClassObject(const CLSID &clsid, const IID &iid, void **ppv) { trace("DllGetClassObeject: create class factory"); if(clsid != CLSID_Component1) { return CLASS_E_CLASSNOTAVAILABLE; } //create class factory CFactory *pFactory = new CFactory; if(pFactory == NULL) { return E_OUTOFMEMORY; } HRESULT hr = pFactory->QueryInterface(iid, ppv); pFactory->Release(); return hr; } STDAPI DllCanUnloadNow() { if((g_lComponent == 0) && (g_lServerLocks == 0)) { return S_OK; } else { return S_FALSE; } } //server registration STDAPI DllRegisterServer() { return RegisterServer(g_hModule, CLSID_Component1, g_szFriendlyName, g_szVerIndProgID, g_szProgID); } //server unregistration STDAPI DllUnregisterServer() { return UnregisterServer(CLSID_Component1, g_szVerIndProgID, g_szProgID); } BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD dwReason, void *lpReserved) { if(dwReason == DLL_PROCESS_ATTACH) { g_hModule = hModule; } return TRUE; }
cmpnt.def
LIBRARY Cmpnt.dll DESCRIPTION 'Chapter 7 Example COM Component' EXPORTS DllGetClassObject @2 PRIVATE DllRegisterServer @3 PRIVATE DllUnregisterServer @4 PRIVATE DllCanUnloadNow @5 PRIVATE
#ifndef _REGISTRY_H_ #define _REGISTRY_H_ HRESULT RegisterServer(HMODULE hModule, const CLSID &clsid, const char *szFriendlyName, const char *szVerIndProgID, const char *szProgID); HRESULT UnregisterServer(const CLSID &clsid, const char *szVerIndProgID, const char *szProgID); #endif
// //registry.cpp // #include <objbase.h> #include <cassert> #include "registry.h" // //Internal functions //Set the key and its value BOOL SetKeyAndValue(const char *szKey, const char *szSubKey, const char *szValue); //Convert a clsid to a char string void CLSIDtochar(const CLSID &clsid, char *szClsID, int nLength); //Delete szKeyChild and all its child LONG RecursiveDeleteKey(HKEY hKeyParent, const char *szKeyChild); const int CLSID_STRING_SIZE = 39 ; // // Register the component in the registry. // HRESULT RegisterServer(HMODULE hModule, // DLL module handle const CLSID& clsid, // Class ID const char* szFriendlyName, // Friendly Name const char* szVerIndProgID, // Programmatic const char* szProgID) // IDs { char szModule[512] ; DWORD dwResult =::GetModuleFileName(hModule, szModule, sizeof(szModule)/sizeof(char)); assert(dwResult != 0) ; // Convert the CLSID into a char. char szCLSID[CLSID_STRING_SIZE] ; CLSIDtochar(clsid, szCLSID, sizeof(szCLSID)) ; // Build the key CLSID\\{...} char szKey[64] ; strcpy(szKey, "CLSID\\") ; strcat(szKey, szCLSID) ; // Add the CLSID to the registry. SetKeyAndValue(szKey, NULL, szFriendlyName) ; // Add the server filename subkey under the CLSID key. SetKeyAndValue(szKey, "InprocServer32", szModule) ; // Add the ProgID subkey under the CLSID key. SetKeyAndValue(szKey, "ProgID", szProgID) ; // Add the version-independent ProgID subkey under CLSID key. SetKeyAndValue(szKey, "VersionIndependentProgID", szVerIndProgID) ; // Add the version-independent ProgID subkey under HKEY_CLASSES_ROOT. SetKeyAndValue(szVerIndProgID, NULL, szFriendlyName) ; SetKeyAndValue(szVerIndProgID, "CLSID", szCLSID) ; SetKeyAndValue(szVerIndProgID, "CurVer", szProgID) ; // Add the versioned ProgID subkey under HKEY_CLASSES_ROOT. SetKeyAndValue(szProgID, NULL, szFriendlyName) ; SetKeyAndValue(szProgID, "CLSID", szCLSID) ; return S_OK ; } // // Remove the component from the registry. // LONG UnregisterServer(const CLSID& clsid, // Class ID const char* szVerIndProgID, // Programmatic const char* szProgID) // IDs { // Convert the CLSID into a char. char szCLSID[CLSID_STRING_SIZE] ; CLSIDtochar(clsid, szCLSID, sizeof(szCLSID)) ; // Build the key CLSID\\{...} char szKey[64] ; strcpy(szKey, "CLSID\\") ; strcat(szKey, szCLSID) ; // Delete the CLSID Key - CLSID\{...} LONG lResult = RecursiveDeleteKey(HKEY_CLASSES_ROOT, szKey) ; assert( (lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND) ) ; // Subkey may not exist. // Delete the version-independent ProgID Key. lResult = RecursiveDeleteKey(HKEY_CLASSES_ROOT, szVerIndProgID) ; assert((lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND)) ; // Subkey may not exist. // Delete the ProgID key. lResult = RecursiveDeleteKey(HKEY_CLASSES_ROOT, szProgID) ; assert((lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND)) ; // Subkey may not exist. return S_OK ; } BOOL SetKeyAndValue(const char *szKey, const char *szSubKey, const char *szValue) { HKEY hKey; char szKeyBuf[1024] ; // Copy keyname into buffer. strcpy(szKeyBuf, szKey) ; // Add subkey name to buffer. if (szSubKey != NULL) { strcat(szKeyBuf, "\\") ; strcat(szKeyBuf, szSubKey ) ; } // Create and open key and subkey. long lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT , szKeyBuf, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, NULL) ; if (lResult != ERROR_SUCCESS) { return FALSE ; } // Set the Value. if (szValue != NULL) { RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE *)szValue, strlen(szValue)+1) ; } RegCloseKey(hKey) ; return TRUE ; } LONG RecursiveDeleteKey(HKEY hKeyParent, const char *szKeyChild) { // Open the child. HKEY hKeyChild ; LONG lRes = RegOpenKeyEx(hKeyParent, szKeyChild, 0, KEY_ALL_ACCESS, &hKeyChild) ; if (lRes != ERROR_SUCCESS) { return lRes ; } // Enumerate all of the decendents of this child. FILETIME time ; char szBuffer[256] ; DWORD dwSize = 256 ; while (RegEnumKeyEx(hKeyChild, 0, szBuffer, &dwSize, NULL, NULL, NULL, &time) == S_OK) { // Delete the decendents of this child. lRes = RecursiveDeleteKey(hKeyChild, szBuffer) ; if (lRes != ERROR_SUCCESS) { // Cleanup before exiting. RegCloseKey(hKeyChild) ; return lRes; } dwSize = 256 ; } // Close the child. RegCloseKey(hKeyChild) ; // Delete this child. return RegDeleteKey(hKeyParent, szKeyChild) ; } void CLSIDtochar(const CLSID &clsid, char *szClSID, int nLength) { assert(nLength >= CLSID_STRING_SIZE) ; // Get CLSID LPOLESTR wszCLSID = NULL ; HRESULT hr = StringFromCLSID(clsid, &wszCLSID) ; assert(SUCCEEDED(hr)) ; // Covert from wide characters to non-wide. wcstombs(szClSID, wszCLSID, nLength); // Free memory. CoTaskMemFree(wszCLSID) ; }
iface.h
//iface.h // #include <objbase.h> interface IX:IUnknown { virtual void __stdcall Fx() = 0; }; interface IY:IUnknown { virtual void __stdcall Fy() = 0; }; interface IZ:IUnknown { virtual void __stdcall Fz() = 0; }; extern const IID IID_IX; extern const IID IID_IY; extern const IID IID_IZ; extern const CLSID CLSID_Component1;
guids.cpp
// // GUIDs.cpp // #include <objbase.h> // {32bb8320-b41b-11cf-a6bb-0080c7b2d682} extern const IID IID_IX = {0x32bb8320, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ; // {32bb8321-b41b-11cf-a6bb-0080c7b2d682} extern const IID IID_IY = {0x32bb8321, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ; // {32bb8322-b41b-11cf-a6bb-0080c7b2d682} extern const IID IID_IZ = {0x32bb8322, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ; // {0c092c21-882c-11cf-a6bb-0080c7b2d682} extern const CLSID CLSID_Component1 = {0x0c092c21, 0x882c, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}} ;
客户端
clients.cpp
// //Client.cpp - client implementation //use:cl client.cpp guids.cpp uuid.lib ole32.lib // #include <objbase.h> #include "iface.h" #include <iostream> #include <string> using namespace std; void trace(string msg) { cout<<msg<<endl; } int main(void) { CoInitialize(NULL); trace("client:call CoCreateInstance to create component and get interface ix"); IX *pIx = NULL; HRESULT hr = ::CoCreateInstance(CLSID_Component1, NULL, CLSCTX_INPROC_SERVER, IID_IX, (void**)&pIx); if(SUCCEEDED(hr)) { trace("client:Succeeded getting IX"); pIx->Fx(); trace("client:Ask for interface IY"); IY *pIy = NULL; hr = pIx->QueryInterface(IID_IY, (void**)&pIy); if(SUCCEEDED(hr)) { trace("client:Succeeded getting IY"); pIy->Fy(); pIy->Release(); trace("client:Release IY interface"); } else { trace("client:Could not get interface IY"); } trace("client:Ask for interface IZ"); IZ *pIz = NULL; hr = pIx->QueryInterface(IID_IZ, (void**)&pIz); if(SUCCEEDED(hr)) { trace("client:Succeeded getting IZ"); pIz->Fz(); pIz->Release(); trace("client:Release IZ interface"); } else { trace("client:Could not get interface IZ"); } trace("client:Release IX interface"); pIx->Release(); } else { cout<<"Client: Could not create component.hr ="<<hex<<hr<<endl; } CoUninitialize(); return 0; }
运行结果