聚合,真正的聚合ActiveX控件

来源:互联网 发布:大圣 知乎 编辑:程序博客网 时间:2024/04/30 01:05
其实,个人以为,用聚合的方法来整合ActiveX控件并没有多少意思,还是用包容来得实在。不过,看到有许多人在msdn上问,而且也想看看聚合一个ActiveX控件的过程,就自己花了些时间用MFC来弄弄了。

1.建立一个普通的MFC ActiveX控件tagc

2. 缺省情况下,控件是从COleControl派生而来,所以已经有了自己的ActiveX控件的实现和自己相应的接口映射,这样 QueryInterface时,返回的ActiveX控件的各种标准接口(IOleObject,IViewObject,IPersist等等)就都 是自己的,而不是将要聚合的控件的了。个人以为至少有两种方法来修改,一种是重载GetInterfaceHook,强制从被聚合的控件中获得 ActiveX控件的标准接口,另一种是将控件改成从CCmdTarget派生。本文采用第二种方法。

a.注释掉OnDraw,DoPropExchange,OnResetState等COleControl特有的函数和
BEGIN_PROPPAGEIDS/END_PROPPAGEIDS 属性页对宏,BEGIN_EVENT_MAP/END_EVENT_MAP事件映射宏, BEGIN_MESSAGE_MAP/END_MESSAGE_MAP消息映射宏和BEGIN_DISPATCH_MAP /  END_DISPATCH_MAP分发映射宏,当然还有它们相应的声明DECLARE_XXXX。

b.将COleControl统统改成CCmdTarget。

c.添加成员变量
    const IID* m_piidPrimary;           // IID for control automation
    const IID* m_piidEvents;            // IID for control events
和成员函数
void CTagcCtrl::InitializeIIDs(const IID *piidPrimary, const IID *piidEvents)
{
    m_piidPrimary = piidPrimary;
    m_piidEvents = piidEvents;

    EnableTypeLib();
}
这是因为可能会用到m_piidPrimary,而且还是调用一下EnableTypeLib()比较好。
这样的话,构造函数中的    InitializeIIDs(&IID_DTagc, &IID_DTagcEvents);就不用注释掉了。

d.构造函数和析构函数中分别加上    AfxOleLockApp();和    AfxOleUnlockApp();另外加上EnableAggregation();以使我们的控件也可以被别人聚合。

CTagcCtrl::CTagcCtrl()
{
    InitializeIIDs(&IID_DTagc, &IID_DTagcEvents);

    // TODO: Initialize your control's instance data here.
    m_lpAggrInner = NULL;

    EnableAggregation();
  
    AfxOleLockApp();
}

CTagcCtrl::~CTagcCtrl()
{
    // TODO: Cleanup your control's instance data here.
    AfxOleUnlockApp();
}


e.将我们要聚合的控件的IUnknown接口指针保存为我们控件的成员变量,以备随时使用
    LPUNKNOWN m_lpAggrInner;

f.开始最重要的工作,重载OnCreateAggregates,在里面建立我们所需要聚合的控件,这里我们使用TreeView控件

BOOL CTagcCtrl::OnCreateAggregates()
{
    CLSID clsid;
    ::CLSIDFromProgID( L"MSComctlLib.TreeCtrl.2", &clsid );
    LPUNKNOWN pn = GetControllingUnknown();
    CoCreateInstance(clsid,
        pn, CLSCTX_INPROC_SERVER,
        IID_IUnknown, (LPVOID*)&m_lpAggrInner);
    if (m_lpAggrInner == NULL)
        return FALSE;
    return TRUE;
}

g.重载OnFinalRelease,在此时释放掉所聚合的控件
void CTagcCtrl::OnFinalRelease()
{
    // TODO: Add your specialized code here and/or call the base class
    if(m_lpAggrInner != NULL){
        m_lpAggrInner->Release();
        m_lpAggrInner = NULL;
    }
    CCmdTarget::OnFinalRelease();
}

h.在.h中添加接口映射声明    DECLARE_INTERFACE_MAP(),在.cpp中添加接口映射定义,这里只用INTERFACE_AGGREGATE添加了聚合接口,下面将会看到会出现问题。
BEGIN_INTERFACE_MAP(CTagcCtrl, CCmdTarget)
    INTERFACE_AGGREGATE(CTagcCtrl, m_lpAggrInner)
END_INTERFACE_MAP()

i.如果现在编译并测试的话,在ActiveX Control Test Container中将测试成功,但在VB中却不行。
最后发现原因在于VB中上面代码中的GetControllingUnknown()返回的是NULL。
这样当请求(QueryInterface)我们控件的一些标准接口如IOleObject等时因为有聚合接口的映射表,所以能找到接口,但是被聚合的控 件中的外部Unknown接口指针却是NULL(因为pn = GetControllingUnknown() = NULL)。这样就产生了问题。
这里的问题是内部聚合的控件AddRef时是增加自己的m_dwRef,而不是外部接口(即我们的控件)的m_dwRef,可是对于控件容器来说,它请求 的却是我们的控件的接口(它并不知道我们的控件聚合了还是没聚合,它只是请求某一个接口),要增加的是我们控件的m_dwRef,结果就乱套了。

最终的原因是好象ActiveX Control Test Container中就是采用聚合的方式聚合我们的控件的,不调用GetControllingUnknown(),直接用它的内部源码,可以发现它其实是有m_pOuterUnknown的。
LPUNKNOWN CCmdTarget::GetControllingUnknown()
{
    if (m_pOuterUnknown != NULL)
        return m_pOuterUnknown; // aggregate of m_pOuterUnknown

    LPUNKNOWN lpUnknown = (LPUNKNOWN)GetInterface(&IID_IUnknown);
    return lpUnknown;   // return our own IUnknown implementation
}
当注释掉构造函数中的EnableAggregation();时,可以发现在ActiveX Control Test Container中也无法建立我们的控件的。

j.所以最后的原因是接口映射表中没有我们控件自己的接口(包括IUnknown,挺有意思的吧,这就是MFC的CCmdTarget了,它是用包裹类来 实现各个接口的),我这里先用了CCmdTarget中的m_xInnerUnknown来作为IUnknown接口,使GetInterface可以找 到一个IUnknown接口,也不知道是不是可以,没仔细分析了,但是好象是没问题了(当然EnableAggregation()还是要去掉注释继续调 用的,否则m_xInnerUnknown就是0了,还是没有接口)。
BEGIN_INTERFACE_MAP(CTagcCtrl, CCmdTarget)
    INTERFACE_PART(CTagcCtrl, IID_IUnknown, InnerUnknown)
    INTERFACE_AGGREGATE(CTagcCtrl, m_lpAggrInner)
END_INTERFACE_MAP()

3.到目前为止,一个傀儡控件已经建立成功了,它自己只有一个IUnknown接口,所有的接口都是它所聚合的控件提供的。但是我们的目的显然并不是如此,我们希望能够扩展所聚合的控件的功能。
当然我们可以为我们的控件添加接口来扩展。
但是一般ActiveX控件是通过Dispatch接口来提供它的功能的,我们怎么办呢,我们自己的控件有自己的IDispatch接口,所聚合的控件也有它自己的IDispatch接口,这两个Dispatch接口的功能该怎样合在一起呢?
很简单,重新实现我们的IDispatch接口,先调用我们缺省的IDispatch接口实现,如果没找到正确的dispid,再调用我们所聚合的控件的IDispatch接口就可以了。
我原来是想重载COleDispatchImpl来实现的,但是,出现了一些不可思议的错误,也懒得去查了,就改成自己新弄一个包裹类来提供 IDispatch,在里面调用CCmdTarget的m_xDispatch(也就是COleDispatchImpl了)为我们提供的缺省的 IDispatch实现和所聚合控件的IDispatch接口。

a.在.h中用BEGIN_INTERFACE_PART/END_INTERFACE_PART宏添加包裹类的声明
    BEGIN_INTERFACE_PART(Dispatchx, IDispatch)
        INIT_INTERFACE_PART(CTagcCtrl, Dispatchx)
        STDMETHOD(GetTypeInfoCount)(UINT*);
        STDMETHOD(GetTypeInfo)(UINT, LCID, LPTYPEINFO*);
        STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR*, UINT, LCID, DISPID*);
        STDMETHOD(Invoke)(DISPID, REFIID, LCID, WORD, DISPPARAMS*, LPVARIANT,
            LPEXCEPINFO, UINT*);
    END_INTERFACE_PART(Dispatchx)

b.实现XDispatchx类

ULONG FAR EXPORT CTagcCtrl::XDispatchx::AddRef()
{
    METHOD_PROLOGUE(CTagcCtrl, Dispatchx)
    return pThis->ExternalAddRef();
}

ULONG FAR EXPORT CTagcCtrl::XDispatchx::Release()
{
    METHOD_PROLOGUE(CTagcCtrl, Dispatchx)
    return pThis->ExternalRelease();
}

HRESULT FAR EXPORT CTagcCtrl::XDispatchx::QueryInterface(
    REFIID iid, void FAR* FAR* ppvObj)
{
    METHOD_PROLOGUE(CTagcCtrl, Dispatchx)
    return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

STDMETHODIMP CTagcCtrl::XDispatchx::GetTypeInfoCount(UINT* pctinfo)
{
    METHOD_PROLOGUE_EX_(CTagcCtrl, Dispatchx)
    *pctinfo = pThis->GetTypeInfoCount();
    return S_OK;
}

STDMETHODIMP CTagcCtrl::XDispatchx::GetTypeInfo(UINT itinfo, LCID lcid,
    LPTYPEINFO* pptinfo)
{
    METHOD_PROLOGUE_EX_(CTagcCtrl, Dispatchx)

    return ((LPDISPATCH)(&pThis->m_xDispatch))->GetTypeInfo(
    itinfo,
    lcid,
    pptinfo);
/*    ASSERT_POINTER(pptinfo, LPTYPEINFO);
    if (itinfo != 0)
        return E_INVALIDARG;

    IID iid;
    if (!pThis->GetDispatchIID(&iid))
        return E_NOTIMPL;

    return pThis->GetTypeInfoOfGuid(lcid, iid, pptinfo);*/
}
STDMETHODIMP CTagcCtrl::XDispatchx::GetIDsOfNames(
    REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid)
{
    METHOD_PROLOGUE_EX_(CTagcCtrl, Dispatchx)
    HRESULT hr = ((LPDISPATCH)(&pThis->m_xDispatch))->GetIDsOfNames(
    riid,
    rgszNames,
    cNames,
    lcid,
    rgdispid);
    if (rgdispid[0] == DISPID_UNKNOWN)
    {
        LPDISPATCH pd;
        if(pThis->m_lpAggrInner){
            pThis->m_lpAggrInner->QueryInterface(IID_IDispatch, (void**)&pd);
            if(pd){
                HRESULT hr = pd->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
                pd->Release();
                return hr;
            }
        }
    }
    return hr;

}

STDMETHODIMP CTagcCtrl::XDispatchx::Invoke(
    DISPID dispid, REFIID riid, LCID lcid,
    WORD wFlags, DISPPARAMS* pDispParams, LPVARIANT pvarResult,
    LPEXCEPINFO pexcepinfo, UINT* puArgErr)
{
    METHOD_PROLOGUE_EX_(CTagcCtrl, Dispatchx)
    const AFX_DISPMAP_ENTRY* pEntry = pThis->GetDispEntry(dispid);
    if (pEntry == NULL)
    {
        LPDISPATCH pd;
        if(pThis->m_lpAggrInner){
            pThis->m_lpAggrInner->QueryInterface(IID_IDispatch, (void**)&pd);
            if(pd){
                HRESULT hr = pd->Invoke(dispid, riid, lcid, wFlags, pDispParams,pvarResult,
                    pexcepinfo, puArgErr);
                pd->Release();
                return hr;
            }
        }
        return DISP_E_MEMBERNOTFOUND;
    }

    return ((LPDISPATCH)&(pThis->m_xDispatch))->Invoke(
    dispid,
    riid,
    lcid,
    wFlags,
    pDispParams,
    pvarResult,
    pexcepinfo,
    puArgErr);
}

另外重载GetDispatchIID以使GetTypeInfo可以调用成功

BOOL CTagcCtrl::GetDispatchIID(IID *pIID)
{
    if (m_piidPrimary != NULL)
        *pIID = *m_piidPrimary;

    return (m_piidPrimary != NULL);
}

c.在构造函数中添加EnableAutomation,给m_xDispatch赋给正确的COleDispatchImpl的vtbl,现在的构造函数如下:

CTagcCtrl::CTagcCtrl()
{
    InitializeIIDs(&IID_DTagc, &IID_DTagcEvents);

    // TODO: Initialize your control's instance data here.
    m_lpAggrInner = NULL;

    EnableAutomation();

    EnableAggregation();
  
    AfxOleLockApp();
}

d.取消分发接口的声明和定义宏的注释   

// Dispatch maps
    //{{AFX_DISPATCH(CTagcCtrl)
    afx_msg void Hello();
    //}}AFX_DISPATCH
    DECLARE_DISPATCH_MAP()

BEGIN_DISPATCH_MAP(CTagcCtrl, CCmdTarget)
    //{{AFX_DISPATCH_MAP(CTagcCtrl)
    DISP_FUNCTION(CTagcCtrl, "Hello", Hello, VT_EMPTY, VTS_NONE)
    //}}AFX_DISPATCH_MAP
    DISP_FUNCTION_ID(CTagcCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()
这里Hello为我定义的一个控件方法,一并列上了。

e.在接口映射表中添加IDispatch接口
BEGIN_INTERFACE_MAP(CTagcCtrl, CCmdTarget)
    INTERFACE_PART(CTagcCtrl, IID_IUnknown, InnerUnknown)
    INTERFACE_PART(CTagcCtrl, IID_IDispatch, Dispatchx)
    INTERFACE_AGGREGATE(CTagcCtrl, m_lpAggrInner)
END_INTERFACE_MAP()

当然你也可以通过GetInterfaceHook添加IDispatch接口,如下:
LPUNKNOWN CTagcCtrl::GetInterfaceHook(const void *piid)
{
    if(_AfxIsEqualGUID(IID_IDispatch, *(IID*)piid) ||
        _AfxIsEqualGUID(*m_piidPrimary, *(IID*)piid)  ){
        return &m_xDispatchx;
    }
    return NULL;
}
这里*m_piidPrimary即为IID_DTagc,这样我们也可以通过IID_DTagc来获得IDispatch接口了。

f.基本就是这样了,编译测试吧

g.虽然正常,不过在ActiveX Control Test Container中测试的话,会发现只有聚合控件的Dispatch方法和属性,这是为什么呢,这是因为ActiveX Control Test Container是通过查询控件的IProvideClassInfo来获得ITypeInfo接口指针,从而获得类型信息,很显然,这里的IProvideClassInfo接口指针来自所聚合的控件。获得的类型信息自然是所聚合的控件的类型信息了。虽然改成自己的控件的类型信息后也没什么好处(因为就显示不了被聚合控件的类型信息了),但是还是改一改吧。

用同样的方法增加包裹类声明XProvideClassInfo
    BEGIN_INTERFACE_PART(ProvideClassInfo, IProvideClassInfo2)
        INIT_INTERFACE_PART(CTagcCtrl, ProvideClassInfo)
        STDMETHOD(GetClassInfo)(LPTYPEINFO* ppTypeInfo);
        STDMETHOD(GetGUID)(DWORD dwGuidKind, GUID* pGUID);
    END_INTERFACE_PART(ProvideClassInfo)

实现包裹类

/////////////////////////////////////////////////////////////////////////////
// CTagcCtrl::XProvideClassInfo

STDMETHODIMP_(ULONG) CTagcCtrl::XProvideClassInfo::AddRef()
{
    METHOD_PROLOGUE_EX_(CTagcCtrl, ProvideClassInfo)
    return (ULONG)pThis->ExternalAddRef();
}

STDMETHODIMP_(ULONG) CTagcCtrl::XProvideClassInfo::Release()
{
    METHOD_PROLOGUE_EX_(CTagcCtrl, ProvideClassInfo)
    return (ULONG)pThis->ExternalRelease();
}

STDMETHODIMP CTagcCtrl::XProvideClassInfo::QueryInterface(
    REFIID iid, LPVOID* ppvObj)
{
    METHOD_PROLOGUE_EX_(CTagcCtrl, ProvideClassInfo)
    return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

STDMETHODIMP CTagcCtrl::XProvideClassInfo::GetClassInfo(
    LPTYPEINFO* ppTypeInfo)
{
    METHOD_PROLOGUE_EX(CTagcCtrl, ProvideClassInfo)

    CLSID clsid;
    pThis->GetClassID(&clsid);

    return pThis->GetTypeInfoOfGuid(GetUserDefaultLCID(), clsid, ppTypeInfo);
}

STDMETHODIMP CTagcCtrl::XProvideClassInfo::GetGUID(DWORD dwGuidKind,
    GUID* pGUID)
{
    METHOD_PROLOGUE_EX_(CTagcCtrl, ProvideClassInfo)

    if (dwGuidKind == GUIDKIND_DEFAULT_SOURCE_DISP_IID)
    {
        IProvideClassInfo2* pinfo = NULL;
        if(pThis->m_lpAggrInner != NULL){
            pThis->m_lpAggrInner->QueryInterface(IID_IProvideClassInfo2, (void**)&pinfo);
            pinfo->GetGUID(dwGuidKind, pGUID);
            pinfo->Release();
        }
        else{
            *pGUID = *pThis->m_piidEvents;
        }
        return NOERROR;
    }
    else
    {
        *pGUID = GUID_NULL;
        return E_INVALIDARG;
    }
}

添加接口映射
BEGIN_INTERFACE_MAP(CTagcCtrl, CCmdTarget)
    INTERFACE_PART(CTagcCtrl, IID_IUnknown, InnerUnknown)
    INTERFACE_PART(CTagcCtrl, IID_IDispatch, Dispatch)
    INTERFACE_PART(CTagcCtrl, IID_IProvideClassInfo, ProvideClassInfo)
    INTERFACE_PART(CTagcCtrl, IID_IProvideClassInfo2, ProvideClassInfo)
    INTERFACE_AGGREGATE(CTagcCtrl, m_lpAggrInner)
END_INTERFACE_MAP()

在ActiveX Control Test Container中测试的话,可以发现将列出自己控件的属性和方法。在VB中始终出现的是自己控件的属性和方法,估计是因为VB是直接自己从typelib中得到的缘故吧。

g.令人遗憾的是,目前还想不到整合两个控件的TypeLib到一起的方法,看了半天odl和idl的资料,还是不知道。也许可以做个工具从typelib中反向导出odl的属性和方法代码文字,或者自己调用CreateTypeLib来生成自己的TypeLib。也懒得弄了,所以就这样了。
原创粉丝点击