现在的位置: 首页 > 综合 > 正文

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

2013年01月10日 ⁄ 综合 ⁄ 共 11713字 ⁄ 字号 评论关闭

 这一步很需要一些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 <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.asp

http://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 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; /
    }

抱歉!评论已关闭.