分类: C/C++
2008-03-18 15:31:28
CBandObjFactory是创建CBandObj对象的类工厂。通常不必直接使用这个类。当你从InitInstance函数中调用AddBandClass时,CBandObjDll将创建一个新的类工厂,并将这个类工厂添加到一个列表中:
BOOL CBandObjDll::AddBandClass(...) { CBandObjFactory* pFact = OnCreateFactory(...); pFact->m_pNextBandFact = m_pBandFactories; m_pBandFactories = pFact; return TRUE; } OnCreateFactory 是一个虚函数,仅仅返回一个新的类工厂。 CBandObjFactory* CBandObjDll::OnCreateFactory(...) { return new CBandObjFactory(...); }我在这里提供OnCreateFactory的目的是一旦你想要派生自己专用的工厂类的话,就可以从CBandObjFactory派生并重载OnCreateFactory,让BandObj使用它。为清晰起见,我省略了参数,这里的参数与传递到AddBandClass的参数一样:类ID,MFC运行时类,种类ID和资源ID。CBandObjFactory传递头两个参数到MFC,后两个参数是自己用的。
CBandObjFactory::CBandObjFactory(REFCLSID clsid, CRuntimeClass* pClass, const CATID& catid, UINT nIDRes) : COleObjectFactory(clsid, pClass, FALSE, NULL) { m_catid = catid; m_nIDRes = nIDRes; }BandObj并不像通常的方式处理类工厂,一般在MFC中编写COM对象时,都使用DECLARE_OLECREATE 和IMPLEMENT_OLECREATE,它把COleObjectFactory创建成一个静态对象。使用这些宏的问题之一是它们将类COleObjectFactory写死在代码中了,这样你就无法使用其它的类,问题之二是它们将类工厂创建成静态数据,再一次将CYourClass::命名的类工厂代码写死了。所以说,何必非得用这些宏呢?之所以提供它们是出于方便。如果想在堆中而不是在栈中创建自己的类工厂并使用某些其它类的话,这样做是有好处的。只要我从COleObjectFactory派生,当COM调用DLL创建对象时,每一个对象工厂类COleObjectFactory会将自己添加到某个MFC搜索的主列表中,这样MFC也就会在创建对象时找到这个类。通过在堆中动态创建类工厂,从而BandObj能在程序员视野中隐藏起来。
HKEY_CLASSES_ROOT CLSID {4647E383-520B-11d2-A0D0-004033D0645D} = "&Web 搜索框" InprocServer32 = MyBands.dll ThreadingModel=Apartment Implemented Categories {00021492-0000-0000-C000-000000000046}前面的三个注册内容是所有进程内COM服务器都有的:
regsvr32.exe MyBands.dll //注册 regsvr32.exe /u MyBands.dll //注销regsvr32调用专门的入口DllRegisterServer来注册MyBands。如果使用 /u 参数,它就调用DllUnregisterServer来注销MyBands。BandObj.cpp文件中提供了它们的缺省标准实现,同时实现的还有DllGetClassObject 和 DllCanUnloadNow。这些缺省的实现调用专门的MFC函数完成相应的工作,如:
STDAPI DllRegisterServer() { AFX_MANAGE_STATE(AfxGetStaticModuleState()); return COleObjectFactory::UpdateRegistryAll(TRUE) ? S_OK : SELFREG_E_CLASS; }COleObjectFactory::UpdateRegistryAll遍历所有类工厂并对每一个类工厂调用UpdateRegistry(TRUE)。
CComPtr这段代码第一行中的尖括弧很有意思,它是一个ATL的智能指针(下文将要讨论)。CoCreateInstance创建一个注册器对象,然后直接使用它即可。但是我对尖括弧有点神经质,讨厌在代码中看到这种尖括弧,除非必须用它来做比较和转换。所以我在COMToys中写了一个小类 CIRegistrar 来进一步简化它,并且隐藏了不顺眼的尖括弧,这个类实际上是封装了ATL智能指针:ireg; ireg.CoCreateInstance(CLSID_Registrar, NULL, CLSCTX_INPROC); ireg->FileRegister("foo.rgs");
CTRegistrar r; r->FileRegister("foo.rgs");这样一来,你只要实例化CTRegistrar,由构造函数调用CoCreateInstance,FileRegister("foo.rgs")方法的调用不变。IRegistrar具备从脚本文件--甚至是资源--进行注册和注销的能力,IRegistrar的所有方法都在atliface.h文件中。CBandObjFactory::UpdateRegistry中有一个通用实现负责查找与类工厂有相同ID的注册资源并调用注册器加载它。
BOOL CBandObjFactory::UpdateRegistry(BOOL bRegister) { static const LPOLESTR RT_REGISTRY = OLESTR("REGISTRY"); UINT nID = GetResourceID(); if (!::FindResource(AfxGetResourceHandle(), MAKEINTRESOURCE(nID), CString(RT_REGISTRY))) return FALSE; CTRegistrar iReg; OnInitRegistryVariables(iReg); // see below LPOLESTR lposModuleName = /* get module pathname */ HRESULT hr = bRegister ? iReg->ResourceRegister(lposModuleName, nID, RT_REGISTRY) : iReg->ResourceUnregister(lposModuleName, nID, RT_REGISTRY); return SUCCEEDED(hr); }这里使用了典型的MFC处理资源的方法,BandObj很好地利用了资源IDs。对注册和注销自己的band对象要做的全部工作就是写一个注册脚本--而不必写任何代码!并且在代码中只涉及一个函数。实际上,你甚至都不用写注册脚本,因为BandObj例子程序已经写好了一个脚本文件,它适用于任何band对象。直接使用它即可。
// 例子中把脚本都放在了应用程序的资源中MyBands.rc IDR_INFOBAND REGISTRY DISCARDABLE "BandObj.rgs" IDR_COMMBAND REGISTRY DISCARDABLE "BandObj.rgs" IDR_DESKBAND REGISTRY DISCARDABLE "BandObj.rgs"不过,三个不同的COM对象怎么可能使用相同的注册脚本呢?它们不是有不同的名字和类IDs吗? 这就是IRegistrar的好处之所在。看一下注册脚本BandObj.rgs。到处是%CLSID%, %ClassName%, 和 %MODULE% ,这些标志是什么意思呢? 这些都是变量。在处理脚本之前,注册器会用实际的值(类ID,类名和模块名)替代这些变量。那它怎么知道使用什么值呢?因为你告诉它了--或者说是BandObj告诉它了。你可能注意到了在UpdateRegistry中有一个对OnInitRegistryVariables的调用。就是在这个地方,BandObj定义了它的变量。
BOOL CBandObjFactory::OnInitRegistryVariables(IRegistrar* pReg) { USES_CONVERSION; pReg->AddReplacement(OLESTR("CLSID"), StringFromCLSID(m_clsid)); pReg->AddReplacement(OLESTR("MODULE"), T2OLE(GetModuleName())); pReg->AddReplacement(OLESTR("ClassName"), T2OLE(GetClassName())); return TRUE; }下面是我在CBandObjFactory中建立的全部变量列表,它们都自动由BandObj定义。
%CLSID% = class ID (GUID)(COleObjectFactory::m_clsid) %MODULE% = DLL的全路径名 %Title% = 标题(资源子串 0) %ClassName% = 人可读的COM类名 (资源子串 1) %ProgID% = ProgID (资源子串 2)要想添加自己的变量,如%TimeStamp% 或者 %MyReleaseVersion%,,只要派生一个新类厂并重载OnInitRegistryVariables就可以了。不要忘了调用基类,因为MFC为每一个类厂调用UpdateRegistry,而对每个类而言,变量都被重新初始化。所以在MyBands中,第一个类厂的%CLSID% 是 CLSID_MYINFOBAND,第二个类厂的%CLSID% 是 CLSID_ MYCOMMBAND,而第三个类厂的%CLSID% 是 CLSID_MYDESKBAND。同一个脚本处理三种对象的注册,酷毙了!。
BOOL CBandObjFactory::UpdateRegistry(BOOL bReg) { ...... // 使用ICatRegister 注册/注销种类 CTCatRegister iCat; REFIID clsid = m_clsid; hr = bRegister ? iCat->RegisterClassImplCategories(clsid, 1, &m_catid) : iCat->UnRegisterClassImplCategories(clsid, 1, &m_catid); // 返回,旁路掉MFC return hr==S_OK; }由此可见,使用ATL智能指针,COMToys类以及CTCatRegister使编程更轻松。你只要声明实例就行了。(待续)