Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9727392
  • 博文数量: 1227
  • 博客积分: 10026
  • 博客等级: 上将
  • 技术积分: 20273
  • 用 户 组: 普通用户
  • 注册时间: 2008-01-16 12:40
文章分类

全部博文(1227)

文章存档

2010年(1)

2008年(1226)

我的朋友

分类: C/C++

2008-03-18 15:13:06

下载本文示例代码
第八部分 类厂和注册以及美中不足

类厂和注册

    到现在为止,我们的讨论没有涉及到一个重要的细节,那就是对象的创建。因为相对与设计和实现来说,它比较简单。前四个部分所讨论的类工厂和注册相当广义。其中描述了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或别的类来完成,(待续)
下载本文示例代码
阅读(978) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~