聚合,真正的聚合ActiveX控件

其实,个人以为,用聚合的方法来整合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。也懒得弄了,所以就这样了。
jacob-1.19-x64.dll jacob-1.19-x86.dll jacob.jar LICENSE.TXT BuildingJacobFromSource.html EventCallbacks.html JacobComLifetime.html JacobThreading.html ReleaseNotes.html UsingJacob.html allclasses-frame.html allclasses-noframe.html constant-values.html deprecated-list.html help-doc.html index-all.html index.html overview-frame.html overview-summary.html overview-tree.html package-list script.js serialized-form.html stylesheet.css ComException.html ComFailException.html ComThread.html Currency.html DateUtilities.html Dispatch.html DispatchEvents.html DispatchIdentifier.html DispatchProxy.html EnumVariant.html InvocationProxy.html InvocationProxyAllVariants.html JacobException.html JacobObject.html JacobReleaseInfo.html LibraryLoader.html MainSTA.html NotImplementedException.html package-frame.html package-summary.html package-tree.html package-use.html ROT.html SafeArray.html STA.html Variant.html VariantUtilities.html VariantViaEvent.html WrongThreadException.html ComException.html ComFailException.html ComThread.html Currency.html DateUtilities.html Dispatch.html DispatchEvents.html DispatchIdentifier.html DispatchProxy.html EnumVariant.html InvocationProxy.html InvocationProxyAllVariants.html JacobException.html JacobObject.html JacobReleaseInfo.html LibraryLoader.html MainSTA.html NotImplementedException.html ROT.html SafeArray.html STA.html Variant.html VariantUtilities.html VariantViaEvent.html WrongThreadException.html ActiveXComponent.html ActiveXDispatchEvents.html ActiveXInvocationProxy.html package-frame.html package-summary.html package-tree.html package-use.html ActiveXComponent.html ActiveXDispatchEvents.html ActiveXInvocationProxy.html
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值