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

2024-01-14 11:38

本文主要是介绍编写可复用性更好的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或别的类来完成,(待续)

这篇关于编写可复用性更好的C++代码——Band对象和COMToys(八)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/605036

相关文章

C++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

JAVA项目swing转javafx语法规则以及示例代码

《JAVA项目swing转javafx语法规则以及示例代码》:本文主要介绍JAVA项目swing转javafx语法规则以及示例代码的相关资料,文中详细讲解了主类继承、窗口创建、布局管理、控件替换、... 目录最常用的“一行换一行”速查表(直接全局替换)实际转换示例(JFramejs → JavaFX)迁移建

Go异常处理、泛型和文件操作实例代码

《Go异常处理、泛型和文件操作实例代码》Go语言的异常处理机制与传统的面向对象语言(如Java、C#)所使用的try-catch结构有所不同,它采用了自己独特的设计理念和方法,:本文主要介绍Go异... 目录一:异常处理常见的异常处理向上抛中断程序恢复程序二:泛型泛型函数泛型结构体泛型切片泛型 map三:文

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

C++构造函数中explicit详解

《C++构造函数中explicit详解》explicit关键字用于修饰单参数构造函数或可以看作单参数的构造函数,阻止编译器进行隐式类型转换或拷贝初始化,本文就来介绍explicit的使用,感兴趣的可以... 目录1. 什么是explicit2. 隐式转换的问题3.explicit的使用示例基本用法多参数构造

C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解

《C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解》:本文主要介绍C++,C#,Rust,Go,Java,Python,JavaScript性能对比全面... 目录编程语言性能对比、核心优势与最佳使用场景性能对比表格C++C#RustGoJavapythonjav

MyBatis中的两种参数传递类型详解(示例代码)

《MyBatis中的两种参数传递类型详解(示例代码)》文章介绍了MyBatis中传递多个参数的两种方式,使用Map和使用@Param注解或封装POJO,Map方式适用于动态、不固定的参数,但可读性和安... 目录✅ android方式一:使用Map<String, Object>✅ 方式二:使用@Param

SpringBoot实现图形验证码的示例代码

《SpringBoot实现图形验证码的示例代码》验证码的实现方式有很多,可以由前端实现,也可以由后端进行实现,也有很多的插件和工具包可以使用,在这里,我们使用Hutool提供的小工具实现,本文介绍Sp... 目录项目创建前端代码实现约定前后端交互接口需求分析接口定义Hutool工具实现服务器端代码引入依赖获

C++打印 vector的几种方法小结

《C++打印vector的几种方法小结》本文介绍了C++中遍历vector的几种方法,包括使用迭代器、auto关键字、typedef、计数器以及C++11引入的范围基础循环,具有一定的参考价值,感兴... 目录1. 使用迭代器2. 使用 auto (C++11) / typedef / type alias

利用Python在万圣节实现比心弹窗告白代码

《利用Python在万圣节实现比心弹窗告白代码》:本文主要介绍关于利用Python在万圣节实现比心弹窗告白代码的相关资料,每个弹窗会显示一条温馨提示,程序通过参数方程绘制爱心形状,并使用多线程技术... 目录前言效果预览要点1. 爱心曲线方程2. 显示温馨弹窗函数(详细拆解)2.1 函数定义和延迟机制2.2