彙總,真正的彙總ActiveX控制項

來源:互聯網
上載者:User
其實,個人以為,用彙總的方法來整合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。也懶得弄了,所以就這樣了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.