这一步很需要一些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 information
static 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 map
BEGIN_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_MAP
END_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
#include
#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 "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();
编译运行,具体结果就不说了,一看便知。
下面是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 information
HRESULT 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; \
}