编写可复用性更好的C++代码:Band对象和COMToys(6)
2006-07-21 11:46:06 来源:WEB开发网第一部分:Band 对象介绍
第二部分:BandObj的类层次和MyBands服务程序的注册
第三部分:深入Band内部,揭开Band的面纱
第四部分:Band对象使用中遇到的一些问题
第五部分:建立自己的COM编程平台ComToys
第六部分 设计和构造COMToys
由于MFC树形对象模型而带来的负担,并不意味COMToys也必须有这个负担。COMToys使用多继承进行COM编程。为了清楚的说明它的工作原理,让我们看看建立在COMToys中CBandObj的新版本。对于应用层的程序像MyBands来说——CBandObj与其前身没什么两样。不同的只是现在CBandObj派生于多个父类CWnd 和IDeskBand。 // 在BandObj.h文件中
因为Band对象是个窗口,所以CBandObj要从CWnd派生。同时要实现IDeskBand,所以还要从IDeskBand派生。DECLARE_IUnknown是一个COMToys宏,在其代码块中声明了AddRef,Release和QueryInterface。这个宏降低了错误输入的可能性并且形式可读性更强。如果声明了接口,当然就得实现它,为此,COMToys还提供了另一个宏。
class CBandObj : public CWnd,public IDeskBand
{
DECLARE_IUnknown();
// IDeskBand
HRESULT GetBandInfo(...);
……
}; // 在BandObj.cpp文件中
就这么简单,一行代码搞掂。CT的意思是CCmdTarget,而不是COMToys;应该将这一行看成是:"为CCmdTarget 的IUnknown实现"(记住,CBandObj从CWnd继承了CCmdTarget),IMPLEMENT_IUnknownCT为调用CCmdTarget的AddRef,,Release和QueryInterface产生库存实现。
IMPLEMENT_IUnknownCT(CBandObj); ULONG CBandObj::AddRef()
ExternalAddRef,ExternalRelease和ExternalQueryInterface是MFC用于实现IUnknown的标准。它们再调用内部版本——除非使用聚合,此时它们调用外部IUnknown。不要纠缠在内部/外部这些东西上;它只是MFC实现的细节。重点是COMToys使用CCmdTarget实现IUnknown。想一想——如果这是一条能行得通的路,为什么不用它呢?CMDTARGENTRYTR是个COMToys宏,它为CCmdTarget的派生类产生标准的入口或条目。它等价于METHOD_PROLOGUE。它初始化MFC的状态并产生TRACE诊断。
{
CMDTARGENTRYTR("CBandObj(%p)::AddRef,
count=%d\n", this, m_dwRef+1);
return ExternalAddRef();
} AFX_MANAGE_STATE(m_pModuleState);
还有一个宏是CMDTARGENTRY,它是一个没有TRACE诊断的版本,用于非CCmdTarget入口(象外来的DllGetClassObject),COMToys有MFCENTRY 和 MFCENTRYTR,它们用AfxGetStaticModuleState初始化状态,而不是用m_pModuleState,下面的代码列出了用于IMPLEMENT_IUnknownCT的所有宏扩展:
CTTRACEFN(...); // 用于 IMPLEMENT_IUnknownCT(CMyComClass)的宏扩展
目前还要对CBandObj的IUnknown实现进行编码,但这里存在一个问题,容器什么时候调用QueryInterface来请求IID_IDeskBand,CBandObj将请求传递给CCmdTarget——但MFC如何知道返回什么接口指针(vtbl)?我在前面讨论过,MFC查找包含在嵌套类偏移量中的接口映射。CBandObj不具备接口映射——COMToys也不进行接口映射。那么当有人用指定的IID进行调用时,CCmdTarget如何找到这个接口呢? 解开这个谜团的是CCmdTarget的虚函数:GetInterfaceHook。由于MFC设计者的先见之明,这个问题得到了解决。在进行接口映射之前,CCmdTarget 会调用 GetInterfaceHook。
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中实现GetInterfaceHook就可以了。
HRESULT
CCmdTarget::InternalQueryInterface
(REFIID iid, void** ppv)
{
*ppv = GetInterfaceHook(iid);
if (*ppv)
return S_OK;
// 搜索接口映射
…
} LPUNKNOWN CBandObj::GetInterfaceHook(void* piid)
无论请求什么接口 ,CBandObj以标准的多继承方式返回相应的强制转换类型。但对于IUnknown不能进行IUnknown*强制转换,原因是在类型还不明确的情况下是不能转换的;这时不知道所指的是哪一个IUnknown,IDeskBand中的那一个,仰或是其它接口中的某一个?还没有明确告知呢。所以只能强制转换某些继承了IUnknown的单继承类。其实哪一个并不重要,因为它们全都指向相同的函数(参见图十七)。 我原本想在多继承的基础上添加自己的接口映射版本来代替旧式风格——但我看不到那样做有什么好处。在每个接口上多加两行代码有多难呢?有时候旧式风格既简单又不易产生混乱。 综上所述,可以总结出用COMToys实现一个基本的COM对象的方法:从CCmdTarget直接派生,或从CCmdTarget的派生类——如CWnd——间接派生,用DECLARE_IUnknown宏声明IUnknown,并用IMPLEMENT_IUnknownCT宏实现它,并编写GetInterfaceHook返回正确的接口指针。当然还得实现方法,对于Band对象,必须实现IDeskBand::GetBandInfo。
{
REFIID iid = *((IID*)piid);
if (iid==IID_IUnknown)
return (IDeskBand*)this;
if (iid==IID_IDeskBand)
return (IDeskBand*)this;
return NULL;
} HRESULT CBandObj::GetBandInfo(...)
(待续)
{
// do it
}
......
更多精彩
赞助商链接