编写可复用性更好的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

    由于MFC树形对象模型而带来的负担,并不意味COMToys也必须有这个负担。COMToys使用多继承进行COM编程。为了清楚的说明它的工作原理,让我们看看建立在COMToys中CBandObj的新版本。对于应用层的程序像MyBands来说——CBandObj与其前身没什么两样。不同的只是现在CBandObj派生于多个父类CWnd 和IDeskBand。
 // 在BandObj.h文件中
class CBandObj : public CWnd,public IDeskBand
{
DECLARE_IUnknown();
// IDeskBand
HRESULT GetBandInfo(...);
……
};
    因为Band对象是个窗口,所以CBandObj要从CWnd派生。同时要实现IDeskBand,所以还要从IDeskBand派生。DECLARE_IUnknown是一个COMToys宏,在其代码块中声明了AddRef,Release和QueryInterface。这个宏降低了错误输入的可能性并且形式可读性更强。如果声明了接口,当然就得实现它,为此,COMToys还提供了另一个宏。
 // 在BandObj.cpp文件中
IMPLEMENT_IUnknownCT(CBandObj);
    就这么简单,一行代码搞掂。CT的意思是CCmdTarget,而不是COMToys;应该将这一行看成是:"为CCmdTarget 的IUnknown实现"(记住,CBandObj从CWnd继承了CCmdTarget),IMPLEMENT_IUnknownCT为调用CCmdTarget的AddRef,,Release和QueryInterface产生库存实现。
 ULONG CBandObj::AddRef()
{
CMDTARGENTRYTR("CBandObj(%p)::AddRef,
count=%d/n", this, m_dwRef+1);
return ExternalAddRef();
}
    ExternalAddRef,ExternalRelease和ExternalQueryInterface是MFC用于实现IUnknown的标准。它们再调用内部版本——除非使用聚合,此时它们调用外部IUnknown。不要纠缠在内部/外部这些东西上;它只是MFC实现的细节。重点是COMToys使用CCmdTarget实现IUnknown。想一想——如果这是一条能行得通的路,为什么不用它呢?CMDTARGENTRYTR是个COMToys宏,它为CCmdTarget的派生类产生标准的入口或条目。它等价于METHOD_PROLOGUE。它初始化MFC的状态并产生TRACE诊断。
AFX_MANAGE_STATE(m_pModuleState);
CTTRACEFN(...);
    还有一个宏是CMDTARGENTRY,它是一个没有TRACE诊断的版本,用于非CCmdTarget入口(象外来的DllGetClassObject),COMToys有MFCENTRY 和 MFCENTRYTR,它们用AfxGetStaticModuleState初始化状态,而不是用m_pModuleState,下面的代码列出了用于IMPLEMENT_IUnknownCT的所有宏扩展:
 // 用于 IMPLEMENT_IUnknownCT(CMyComClass)的宏扩展

STDMETHODIMP_(ULONG) CMyComClass::AddRef()
{
CMDTARGENTRYTR(_T("CMyComClass(%p)::AddRef "),this);
DWORD dwRef = ExternalAddRef();
CTTRACE(_T("> returns count=%d/n"),dwRef);
return dwRef;
}

STDMETHODIMP_(ULONG) CMyComClass::Release()
{
CMDTARGENTRYTR(_T("CMyComClass(%p)::Release "),this);
DWORD dwRef = ExternalRelease();
CTTRACE(_T("> returns count=%d/n"),dwRef);
return dwRef;
}

STDMETHODIMP CMyComClass::QueryInterface(REFIID iid, LPVOID* ppvRet)
{
CTCHECKARG(ppvRet);
CMDTARGENTRYTR(_T("CMyComClass(%p)::QueryInterface(%s) "), this, _TR(iid));
HRESULT hr = ExternalQueryInterface(&iid, ppvRet);
CTTRACE(_T("> returns %s, *ppv=%p, count=%d/n"),
_TR(hr), *ppvRet, CCmdTarget::m_dwRef);
return hr;
}

// 扩展 CMDTARGENTRYTR.
// 注: 当创建 CCmdTarget 对象时, MFC 初始化 m_pModuleState = AfxGetModuleState();
//
AFX_MANAGE_STATE(m_pModuleState);
TRACE(...);
    目前还要对CBandObj的IUnknown实现进行编码,但这里存在一个问题,容器什么时候调用QueryInterface来请求IID_IDeskBand,CBandObj将请求传递给CCmdTarget——但MFC如何知道返回什么接口指针(vtbl)?我在前面讨论过,MFC查找包含在嵌套类偏移量中的接口映射。CBandObj不具备接口映射——COMToys也不进行接口映射。那么当有人用指定的IID进行调用时,CCmdTarget如何找到这个接口呢? 解开这个谜团的是CCmdTarget的虚函数:GetInterfaceHook。由于MFC设计者的先见之明,这个问题得到了解决。在进行接口映射之前,CCmdTarget 会调用 GetInterfaceHook。
 // 伪码
HRESULT
CCmdTarget::InternalQueryInterface
(REFIID iid, void** ppv)
{
*ppv = GetInterfaceHook(iid);
if (*ppv)
return S_OK;
// 搜索接口映射

}
另外,为了旁路掉整个接口映射,只要在CBandObj中实现GetInterfaceHook就可以了。
 LPUNKNOWN CBandObj::GetInterfaceHook(void* piid)
{
REFIID iid = *((IID*)piid);
if (iid==IID_IUnknown)
return (IDeskBand*)this;
if (iid==IID_IDeskBand)
return (IDeskBand*)this;
return NULL;
}
    无论请求什么接口 ,CBandObj以标准的多继承方式返回相应的强制转换类型。但对于IUnknown不能进行IUnknown*强制转换,原因是在类型还不明确的情况下是不能转换的;这时不知道所指的是哪一个IUnknown,IDeskBand中的那一个,仰或是其它接口中的某一个?还没有明确告知呢。所以只能强制转换某些继承了IUnknown的单继承类。其实哪一个并不重要,因为它们全都指向相同的函数(参见图十七)。 我原本想在多继承的基础上添加自己的接口映射版本来代替旧式风格——但我看不到那样做有什么好处。在每个接口上多加两行代码有多难呢?有时候旧式风格既简单又不易产生混乱。 综上所述,可以总结出用COMToys实现一个基本的COM对象的方法:从CCmdTarget直接派生,或从CCmdTarget的派生类——如CWnd——间接派生,用DECLARE_IUnknown宏声明IUnknown,并用IMPLEMENT_IUnknownCT宏实现它,并编写GetInterfaceHook返回正确的接口指针。当然还得实现方法,对于Band对象,必须实现IDeskBand::GetBandInfo。
 HRESULT CBandObj::GetBandInfo(...)
{
// do it
}
......
(待续)

Google
<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>

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



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

相关文章

C#如何调用C++库

《C#如何调用C++库》:本文主要介绍C#如何调用C++库方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录方法一:使用P/Invoke1. 导出C++函数2. 定义P/Invoke签名3. 调用C++函数方法二:使用C++/CLI作为桥接1. 创建C++/CL

Java对象转换的实现方式汇总

《Java对象转换的实现方式汇总》:本文主要介绍Java对象转换的多种实现方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java对象转换的多种实现方式1. 手动映射(Manual Mapping)2. Builder模式3. 工具类辅助映

利用Python调试串口的示例代码

《利用Python调试串口的示例代码》在嵌入式开发、物联网设备调试过程中,串口通信是最基础的调试手段本文将带你用Python+ttkbootstrap打造一款高颜值、多功能的串口调试助手,需要的可以了... 目录概述:为什么需要专业的串口调试工具项目架构设计1.1 技术栈选型1.2 关键类说明1.3 线程模

Python Transformers库(NLP处理库)案例代码讲解

《PythonTransformers库(NLP处理库)案例代码讲解》本文介绍transformers库的全面讲解,包含基础知识、高级用法、案例代码及学习路径,内容经过组织,适合不同阶段的学习者,对... 目录一、基础知识1. Transformers 库简介2. 安装与环境配置3. 快速上手示例二、核心模

Java的栈与队列实现代码解析

《Java的栈与队列实现代码解析》栈是常见的线性数据结构,栈的特点是以先进后出的形式,后进先出,先进后出,分为栈底和栈顶,栈应用于内存的分配,表达式求值,存储临时的数据和方法的调用等,本文给大家介绍J... 目录栈的概念(Stack)栈的实现代码队列(Queue)模拟实现队列(双链表实现)循环队列(循环数组

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

Python中判断对象是否为空的方法

《Python中判断对象是否为空的方法》在Python开发中,判断对象是否为“空”是高频操作,但看似简单的需求却暗藏玄机,从None到空容器,从零值到自定义对象的“假值”状态,不同场景下的“空”需要精... 目录一、python中的“空”值体系二、精准判定方法对比三、常见误区解析四、进阶处理技巧五、性能优化