ActiveX控件的MFC设计之旅-第7步

    技术2022-05-11  72

    这一步很需要一些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; /    }

    最新回复(0)