编写可复用性更好的C++代码——Band对象和COMToys(八)

编译/赵湘宁

原著:Paul Dilascia

MSJ November 1999 & December 1999

关键字:Bands 对象,Desk Bands,Info/Comm Bands,Explorer Bar,Tool Bands。

本文假设你熟悉C++,COM,IE。

下载本文源代码: MyBands.zip (128KB)
                TestEditSrch.zip (75KB)


第一部分:Band 对象介绍
第二部分:BandObj的类层次和MyBands服务程序的注册
第三部分:深入Band内部,揭开Band的面纱
第四部分:Band对象使用中遇到的一些问题
第五部分:建立自己的COM编程平台ComToys
第六部分:设计和构造COMToys
第七部分:类的实现



第八部分 类厂和注册以及美中不足

类厂和注册

    到现在为止,我们的讨论没有涉及到一个重要的细节,那就是对象的创建。因为相对与设计和实现来说,它比较简单。前四个部分所讨论的类工厂和注册相当广义。其中描述了BandObj如何利用资源脚本文件中所列的DLL的条目以及使用ATL注册器的类厂来注册COM对象。到了COMToys,我仅仅是将代码从BandObj移到了COMToys。 COMToys使用CTModule,CTFactory,和一个特殊的文件DllEntry.cpp来处理对象的创建,注册和DLL条目。CTModule是一个典型的“模块类”,就像MFC的CWinApp或者ATL的CComModule。其中的OnGetClassObject,OnDllRegisterServer等虚函数是可以重载的。不过必须包含(#include)实现DLL入口的DllEntry.cpp。
 extern "C"
 STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, 
                          LPVOID* ppv)
 {
   MFCENTRY;
   return CTModule::GetModule()->
     OnGetClassObject(rclsid, riid, ppv);
 }      
    如法炮制DllRegisterServer,DllCanUnloadNow及其它函数。CTModule::GetModule类似AfxGetApp。它返回一个且是仅有的一个全程CTModule对象。所以DllEntry.cpp的全部工作是将外部实现的硬性函数转换成较容易重载的虚函数。 至于MFC应用方面,COMToys 有一个用于COM的MFC 应用类 CTMfcModule,它从CTModule 和 COleControlModule 实现多继承。在这个CTMfcModule类中实现了OnGetClassObject和其它操作,这些操作中又调用相应的MFC函数来完成工作。将这些东西分离的理由是尽量将处理放到不依赖MFC的代码中。图十八显示了MFC和非MFC类之间的关系: 


图十八 MFC和非MFC类之间的关系

    关于类工厂的实现,COMToys 依赖于MFC模仿类:CTFactory。CTModule::OnRegisterServer调用一个静态函数,CTFactory::OnRegisterAll,它具备针对每一个类工厂调用的CTFactory::OnRegister。当创建CTFactory的时候。必须指定的一个参数之一是资源ID。缺省情况下,CTFactory::OnRegister查找有相同ID的脚本文件资源并调用ATL注册器运行它。但首先它要调用另一个虚函数:OnInitRegistryVariables,以便初始化通用的注册器变量,如%CLSID% 和%ClassName%.,下面是ComToys在 CTComObjFactory::OnInitRegistryVariables 定义的变量清单:
%CLSID%          = class ID (GUID) (COleObjectFactory::m_clsid)
%MODULE%         = full pathname of DLL
%Title%          = title (resource substring 0)
%ClassName%      = human-readable COM class name (resource substring 1)
%ProgID%         = ProgID (resource substring 2)
%ThreadingModel% = "", "Apartment", "Free", or "Both" (m_nThreadingModel)      
可以重载 OnInitRegistryVariables 添加自己的注册器变量。这些都与前几部分的做法一样,只是现在的代码被从Band对象中抽象出来进入了COMToys。最终,在进行注册与注销的处理时,所要做的全部工作是编写一个注册脚本并将它作为资源放入资源文件,让它的ID与COM类相同。
IDR_MYCOMCLASS REGISTRY DISCARDABLE "MyComClass.rgs"      
    应该指出,CTFactory有一个地方很伤脑筋:它本应该实现IClassFactory,但是它没有。那是因为我还没来得及顾上它。因为到目前为止我所建立的所有COM类都与MFC有关,我一直都用COleObjectFactory,它很好用。因此,我从COleObjectFactory 和 CTFactory 派生 CBandObjFactory。如果需要非MFC类工厂,可以为 CTFactory 实现一个IClassFactory。不过现在有CTFactory,COleObjectFactory相伴左右,CTFactory提供了所有注册工作所需要的东西,很容易使用。而COleObjectFactory提供了IClassFactory接口和与DllGetClassObject的联接。 当Windows资源管理器(Explorer)之类的进程想要创建自己的COM对象实例时——假设是创建MyBands.dll中的Band对象——它调用CoCreateInstance函数,这个函数读取注册表确定加载哪一个DLL并加载它,然后调用DllGetClassObject(在DllEntry.cpp中)函数,它再接着调用CTModule::OnGetClassObject。因为BandObj是一个基于MFC的对象,它使用CTMfcModule,其OnGetClassObject函数调用MFC的AfxDllGetClassObject函数。MFC搜索类工厂清单及其它处理。到这里也许有人会问:类工厂是在哪儿创建的呢?通常在MFC中创建类工厂用的是DECLARE_OLECREATE 和 IMPLEMENT_OLECREATE。这些宏声明并实例化一个静态COleObjectFactory对象:它的形式为CMyComClass::类工厂。但前面四个部分中的BandObj和COMToys都没有使用DECLARE/IMPLEMENT_OLECREATE,因此,为了创建自己的类工厂,这就要调用new进行动态创建:
 // 在InitInstance中
 new CTBandObjFactory(MYGUID, 
   RUNTIME_CLASS(CMyComClass),
   IDR_MAINFRAME);      
    当创建了新的CTFactory,COMToys将它添加到主清单列表中;MFC对COleObjectFactory也做同样的事情。换句话说,正像我在面几个部分中解释的那样,不必专门做些什么来让MFC或COMToys知道你的类工厂。只要创建就行了。也不必删除类工厂,CTModule会在ExitInstance中自动完成。如果宁愿用老式方法将类工厂创建成一个静态全局类,那也没问题——但必须在自己的类工厂构造函数中设置m_bAutoDel = FALSE。避开DECLARE/IMPLEMENT_OLECREATE的主要理由是这样做了以后,我就能按理想的方式派生自己的类工厂。在BandObj例子中,自己当然必须创建一个类工厂;如面几个部分所做的那样,依然要调用AddBandClass。 总之,为了处理对象的创建,所有要做的工作是从 CTFactory 和 COleObjectFactory 派生自己的类工厂并记住#include文件DllEntry.cpp。剩下的事情由COMToys来做。为了让COM对象自注册,自己得写一个用于注册的资源脚本(RGS文件),并将它作为与类工厂有相同ID的REGISTRY资源添加到.RC文件。RGS文件的具体细节请下载源代码。

美中不足

    我刚开始实现COMToys的时候,碰到一个小障碍。如果两个COM接口的方法有相同的名字和签名怎么办?虽然这种情况很少发生,但它确实发生了。例如,IPersistStream 和IPersistFile都有IsDirty。如果像下面这样写的话:
DECLARE_IPersistFile();
DECLARE_IPersistStream();      
    最后会声明IsDirty两次。尽管可以用手工方式进行更正,但这个问题总是让人有些忐忑不安。用手工来做,你必须重新敲入很多IMPLEMENT_IPersistStream代码,这太令人讨厌。为了避免这种事情,我引入了一个函数级的宏。
 // IPersistFile - 整个接口
 IMPLEMENT_IPersistFile(CMyClass,CTPersistFile);
 
 // IPersistStream - 每个单独的函数
 // IMPLEMENT_IPersistStream_IsDirty();
 IMPLEMENT_IPersistStream_Load(CMyClass,CTPersistStream);
 IMPLEMENT_IPersistStream_Save(CMyClass,CTPersistStream);
 IMPLEMENT_IPersistStream_GetSizeMax(CMyClass,CTPersistStream);      
    这里再次印证了多继承(Magic MI Ruler)的优点,只有一个IsDirty,它应用到两个接口:IPersistFile 和 IPersistStream。这不就是希望的结果吗?如果你确实要让两个接口有相同的函数,又有不同的实现,你完全可以用普通的C++语法来充分限定方法名字:CMyComClass::IPersistStream::IsDirty 和 CMyComClass::IPersistFile::IsDirty。但用这个函数级的宏,至少不用重新敲入——甚至是不用知道它的实现。函数级的宏还提供了为一个或多个接口方法重载标准实现的途径——尽管如此,我觉得最好还是通过派生自己的实现类——CTMyPersistStream或别的类来完成,(待续)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值