这一步很需要一些COM基础,因为没有这个基础的话,可能会有为什么要做这个,有必要吗,之类的疑问的。这回我们要看看双接口,为MFC设计的控件添加双接口(双接口是什么就不解释了)。
这里参考了msdn中的例子acdual,并且应用了例子里面的一些宏,这个例子可以直接搜索msdn找到,还有
TN065: Dual-Interface Support for OLE Automation Servers参考。
老是用一个例子,也有些腻了,咱新来一例子,TDual控件
好,开始:
1.新建MFC控件工程,这里添加了一个属性Hello(在控件上显示Hello属性对应的字符串),一个方法SayHello,弹出一消息框,显示方法参数中的字符串。
2.改造接口,
添加第二个接口
[ uuid(C3180013-EB23-4e8f-924C-38F5A201D3D8),
helpstring("Double interface"),
oleautomation,
dual ]
interface ITDual : IDispatch
{
[propput, id(1)] HRESULT Hello([in] BSTR newText);
[propget, id(1)] HRESULT Hello([out, retval] BSTR* ret);
[id(2)] HRESULT SayHello(BSTR strHello);
}
双接口的接口描述中必须有oleautomation和dual属性,而且接口必须派生自IDispatch。
这里的接口定义很象ATL中的IDL定义,如果用ATL设计过控件的话,应该不陌生的,具体的规则就不罗嗦了,如果不清楚的话,请自行查找资料了,大家可以和MFC中原来的接口定义对照一下:
properties:
// NOTE - ClassWizard will maintain property information here.
// Use extreme caution when editing this section.
//{{AFX_ODL_PROP(CTDualCtrl)
[id(1)] BSTR Hello;
//}}AFX_ODL_PROP
methods:
// NOTE - ClassWizard will maintain method information here.
// Use extreme caution when editing this section.
//{{AFX_ODL_METHOD(CTDualCtrl)
[id(2)] void SayHello(BSTR strHello);
//}}AFX_ODL_METHOD
定义好了新的接口后,需要在coclass描述中添加新接口的一个引用,如下:
[ uuid(A1E75855-8561-4416-BE49-97B4D9A228E2),
helpstring("TDual Control"), control ]
coclass TDual
{
// [default] dispinterface _DTDual;
// [default, source] dispinterface _DTDualEvents;
[default] interface ITDual;
[default, source] dispinterface _DTDualEvents;
dispinterface _DTDual;
};
这里将缺省的接口设置为了ITDual而不是原来的dispinterface _DTDual。
3.改造好了接口后,就要实现这个新的接口了。MFC使用嵌套类来实现一个接口,得到嵌套类的方法是使用BEGIN_INTERFACE_PART/END_INTERFACE_PART宏对,接着具体实现嵌套类的相应接口函数(包括IUnknown和IDispatch的接口函数)就可以了。
这里用了acdul例子中定义的
BEGIN_DUAL_INTERFACE_PART和
END_DUAL_INTERFACE_PART宏对和DELEGATE_DUAL_INTERFACE宏来简化实现IUnknown和IDispatch中的接口函数。这些宏的定义将在最后列出。在TDualCtl.h中,#include "mfcdual.h"添加新接口PART,如下:// Event maps //{{AFX_EVENT(CTDualCtrl) //}}AFX_EVENT DECLARE_EVENT_MAP()//此处为添加部分public:/// Double Interface Part DECLARE_INTERFACE_MAP()
BEGIN_DUAL_INTERFACE_PART(TDual, ITDual) STDMETHOD(put_Hello)(THIS_ BSTR newText); STDMETHOD(get_Hello)(THIS_ BSTR FAR* ret); STDMETHOD(SayHello)(THIS_ BSTR strHello); END_DUAL_INTERFACE_PART(TDual)在TDualCtl.cpp中
/// Control type informationstatic const DWORD BASED_CODE _dwTDualOleMisc = OLEMISC_ACTIVATEWHENVISIBLE | OLEMISC_SETCLIENTSITEFIRST | OLEMISC_INSIDEOUT | OLEMISC_CANTLINKINSIDE | OLEMISC_RECOMPOSEONRESIZE;IMPLEMENT_OLECTLTYPE(CTDualCtrl, IDS_TDUAL, _dwTDualOleMisc)//添加AddRef,QueryInterface等通用函数的实现
DELEGATE_DUAL_INTERFACE(CTDualCtrl, TDual)......//这两个函数是原来Dispatch接口分发时要用到的Hello属性和SayHello方法函数void CTDualCtrl::OnHelloChanged() { // TODO: Add notification handler code InvalidateControl(); SetModifiedFlag();}void CTDualCtrl::SayHello(LPCTSTR strHello) { // TODO: Add your dispatch handler code here AfxMessageBox(strHello);}//这三个函数是新添加的接口实现的函数,一般实现还是调用原接口的函数,这里加上一个描述,表示是从vtable中调用的。STDMETHODIMP CTDualCtrl::XTDual::put_Hello(BSTR newText){ METHOD_PROLOGUE(CTDualCtrl, TDual) // MFC automatically converts from Unicode BSTR to // Ansi CString, if necessary... CString str = newText; pThis->m_hello = "vtable:" + str; pThis->OnHelloChanged(); return NOERROR;}STDMETHODIMP CTDualCtrl::XTDual::get_Hello(BSTR* ret){ METHOD_PROLOGUE(CTDualCtrl, TDual) // MFC automatically converts from Ansi CString to // Unicode BSTR, if necessary... pThis->m_hello.SetSysString(ret); return NOERROR;}STDMETHODIMP CTDualCtrl::XTDual::SayHello(BSTR strHello){ METHOD_PROLOGUE(CTDualCtrl, TDual) // MFC automatically converts from Ansi CString to // Unicode BSTR, if necessary... CString str = strHello; str = "I'm vtable SayHello/n" + str; pThis->SayHello(str); return NOERROR;}4.连接新的接口到MFC的QueryInterface接口表机制,使能够通过QueryInterface获得ITDual接口。在.h中添加
public:/// Double Interface Part
DECLARE_INTERFACE_MAP() BEGIN_DUAL_INTERFACE_PART(TDual, ITDual) STDMETHOD(put_Hello)(THIS_ BSTR newText); STDMETHOD(get_Hello)(THIS_ BSTR FAR* ret); STDMETHOD(SayHello)(THIS_ BSTR strHello); END_DUAL_INTERFACE_PART(TDual)
在.cpp中添加/// Event mapBEGIN_EVENT_MAP(CTDualCtrl, COleControl) //{{AFX_EVENT_MAP(CTDualCtrl) // NOTE - ClassWizard will add and remove event map entries // DO NOT EDIT what you see in these blocks of generated code ! //}}AFX_EVENT_MAPEND_EVENT_MAP()//将新的接口连接到QueryInterface机制BEGIN_INTERFACE_MAP(CTDualCtrl, COleControl) INTERFACE_PART(CTDualCtrl, IID_IDispatch, Dispatch) INTERFACE_PART(CTDualCtrl, DIID__DTDual, Dispatch)
INTERFACE_PART(CTDualCtrl, IID_ITDual, TDual)END_INTERFACE_MAP()5.这时编译的话,会提示错误,因为没有ITDual接口声明和一些IID的定义。点击TDual.odl右键,弹出设置菜单,选择Setting菜单项,在Output header file name中键入你想要输出的接口.h文件名,这里用的是ITDual.h,然后将ITDual.h包括在stdafx.h中,以便可以在各处用到。接下来新建一个initIIDs.cpp文件,加上下面代码
#include <ole2.h>#include <initguid.h>#include "ITDual.h"这个initIIDs.cpp没什么用处,只是用来实现要用到的几个IID的,不过要
在Project-Setting中
把initIIDs.cpp的
Precompiled Headers设置为
Not using precompiled headers。
编译一下TDual.odl,OK了6.添加异常处理和自动化错误接口,这部分本例未提供,有兴趣的可以参考acdual例子。7.新建一个普通的MFC对话框exe工程testdual,在工程中引入TDual.ocx,这里用的方法是在stdafx.h中加入#import "<YourPath//>TDual.ocx" no_namespace。为CTestdualDlg添加成员变量CWnd m_wnd,响应WM_CREATE消息,添加如下代码 CRect rect(10, 10, 100, 100); BOOL b = m_wnd.CreateControl(__uuidof( TDual ), NULL, WS_CHILD | WS_VISIBLE, rect, this, 1); if(!b){ AfxMessageBox("Can't CreateControl"); return 0; } LPUNKNOWN pukn = m_wnd.GetControlUnknown(); if(!pukn){ AfxMessageBox("Can't get IUnknown interface"); return 0; } ITDual* pdual = NULL; pukn->QueryInterface(__uuidof(ITDual), (void**)&pdual); if(!pdual){ AfxMessageBox("Can't get ITDual interface"); return 0; } CString str = "Thanks"; pdual->SayHello(str.AllocSysString()); pdual->put_Hello(str.AllocSysString()); pdual->Release();编译运行,具体结果就不说了,一看便知。因为本文主要讲述如何做,至于为什么要如此做就不说了,有兴趣的朋友可以自己研究,这里提供几个网址,仅供参考http://www.microsoft.com/china/msdn/archives/others/visualc/atlmfc.asphttp://blog.csdn.net/RedStar81/archive/2003/04/03/19761.aspx下面是acdual例子中的一些宏定义代码#define BEGIN_DUAL_INTERFACE_PART(localClass, baseClass) / BEGIN_INTERFACE_PART(localClass, baseClass) / STDMETHOD(GetTypeInfoCount)(UINT FAR* pctinfo); / STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo); / STDMETHOD(GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, UINT cNames, LCID lcid, DISPID FAR* rgdispid); / STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pdispparams, VARIANT FAR* pvarResult, EXCEPINFO FAR* pexcepinfo, UINT FAR* puArgErr); //// END_DUAL_INTERFACE_PART is just like END_INTERFACE_PART. It// is only added for symmetry...#define END_DUAL_INTERFACE_PART(localClass) / END_INTERFACE_PART(localClass) //// DELEGATE_DUAL_INTERFACE expands to define the standard IDispatch// methods for a dual interface, delegating back to the default// MFC implementation#define DELEGATE_DUAL_INTERFACE(objectClass, dualClass) / STDMETHODIMP_(ULONG) objectClass::X##dualClass::AddRef() / { / METHOD_PROLOGUE(objectClass, dualClass) / return pThis->ExternalAddRef(); / } / STDMETHODIMP_(ULONG) objectClass::X##dualClass::Release() / { / METHOD_PROLOGUE(objectClass, dualClass) / return pThis->ExternalRelease(); / } / STDMETHODIMP objectClass::X##dualClass::QueryInterface( / REFIID iid, LPVOID* ppvObj) / { / METHOD_PROLOGUE(objectClass, dualClass) / return pThis->ExternalQueryInterface(&iid, ppvObj); / } / STDMETHODIMP objectClass::X##dualClass::GetTypeInfoCount( / UINT FAR* pctinfo) / { / METHOD_PROLOGUE(objectClass, dualClass) / LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE); / ASSERT(lpDispatch != NULL); / return lpDispatch->GetTypeInfoCount(pctinfo); / } / STDMETHODIMP objectClass::X##dualClass::GetTypeInfo( / UINT itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo) / { / METHOD_PROLOGUE(objectClass, dualClass) / LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE); / ASSERT(lpDispatch != NULL); / return lpDispatch->GetTypeInfo(itinfo, lcid, pptinfo); / } / STDMETHODIMP objectClass::X##dualClass::GetIDsOfNames( / REFIID riid, OLECHAR FAR* FAR* rgszNames, UINT cNames, / LCID lcid, DISPID FAR* rgdispid) / { / METHOD_PROLOGUE(objectClass, dualClass) / LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE); / ASSERT(lpDispatch != NULL); / return lpDispatch->GetIDsOfNames(riid, rgszNames, cNames, / lcid, rgdispid); / } / STDMETHODIMP objectClass::X##dualClass::Invoke( / DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, / DISPPARAMS FAR* pdispparams, VARIANT FAR* pvarResult, / EXCEPINFO FAR* pexcepinfo, UINT FAR* puArgErr) / { / METHOD_PROLOGUE(objectClass, dualClass) / LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE); / ASSERT(lpDispatch != NULL); / return lpDispatch->Invoke(dispidMember, riid, lcid, / wFlags, pdispparams, pvarResult, / pexcepinfo, puArgErr); / } //// TRY_DUAL and CATCH_ALL_DUAL are used to provide exception handling// for your dual interface methods. CATCH_ALL_DUAL takes care of// returning the appropriate error code.#define TRY_DUAL(iidSource) / HRESULT _hr = S_OK; / REFIID _riidSource = iidSource; / TRY /#define CATCH_ALL_DUAL / CATCH(COleException, e) / { / _hr = e->m_sc; / } / AND_CATCH_ALL(e) / { / AFX_MANAGE_STATE(pThis->m_pModuleState); / _hr = DualHandleException(_riidSource, e); / } / END_CATCH_ALL / return _hr; //// DualHandleException is a helper function used to set the system's// error object, so that container applications that call through// VTBLs can retrieve rich error informationHRESULT DualHandleException(REFIID riidSource, const CException* pAnyException);/// DECLARE_DUAL_ERRORINFO expands to declare the ISupportErrorInfo// support class. It works together with DUAL_ERRORINFO_PART and// IMPLEMENT_DUAL_ERRORINFO defined below.#define DECLARE_DUAL_ERRORINFO() / BEGIN_INTERFACE_PART(SupportErrorInfo, ISupportErrorInfo) / STDMETHOD(InterfaceSupportsErrorInfo)(THIS_ REFIID riid); / END_INTERFACE_PART(SupportErrorInfo) //// DUAL_ERRORINFO_PART adds the appropriate entry to the interface map// for ISupportErrorInfo, if you used DECLARE_DUAL_ERRORINFO.#define DUAL_ERRORINFO_PART(objectClass) / INTERFACE_PART(objectClass, IID_ISupportErrorInfo, SupportErrorInfo) //// IMPLEMENT_DUAL_ERRORINFO expands to an implementation of// ISupportErrorInfo which matches the declaration in// DECLARE_DUAL_ERRORINFO.#define IMPLEMENT_DUAL_ERRORINFO(objectClass, riidSource) / STDMETHODIMP_(ULONG) objectClass::XSupportErrorInfo::AddRef() / { / METHOD_PROLOGUE(objectClass, SupportErrorInfo) / return pThis->ExternalAddRef(); / } / STDMETHODIMP_(ULONG) objectClass::XSupportErrorInfo::Release() / { / METHOD_PROLOGUE(objectClass, SupportErrorInfo) / return pThis->ExternalRelease(); / } / STDMETHODIMP objectClass::XSupportErrorInfo::QueryInterface( / REFIID iid, LPVOID* ppvObj) / { / METHOD_PROLOGUE(objectClass, SupportErrorInfo) / return pThis->ExternalQueryInterface(&iid, ppvObj); / } / STDMETHODIMP objectClass::XSupportErrorInfo::InterfaceSupportsErrorInfo( / REFIID iid) / { / METHOD_PROLOGUE(objectClass, SupportErrorInfo) / return (iid == riidSource) ? S_OK : S_FALSE; / }