April 17, 2003 tools

Hello IDispatch Lovers

I’m probably jumping into this topic a bit late but it doesn’t seem to end. I admit I haven’t read all article in this thread but I think I have an implementation solution for multiple dual interfaces on one class. And I would like opinions on it from others as well, and as to whether or not it displays proper COM implementation.

The implementation is very much like one that has been posted earlier, which I’ve seen different variations of many times before. However, I feel the one I am presenting allows all dual interfaces to be on one object and accessible via scripting without explicitly asking for a piece. No new interface definitions will be necessary with this technique. However, there may be one flaw. It will work ideally when none of the dispid’s of any of the dual interfaces collide, and when none of the named functions collide.

I consider a collision when the dispids,or names are equal, but the parameter list/type are not. Even still, when they do collide, the implementers using this technique can choose which should take priority.

Using ATL, I defined a templated class which should be derived by the class that exposes multiple dual interfaces.

template<UINT t_uiNum, class tihclass = CComTypeInfoHolder>
class XMultiDualImpl : public IDispatch
{
public :
typedef tihclass _tihclass;
    void Add( UINT uiIndex, LPDISPATCH pDisp, _tihclass & tih)
    {
        ASSERT( t_uiNum > uiIndex-1 && uiIndex>0 );
        m_arrDispImpls[uiIndex-1] = pDisp; //no need to addref, pDisp==this
        m_arrpTih[uiIndex-1] = &tih;
    };

    STDMETHOD(GetTypeInfoCount)(UINT* pctinfo) {*pctinfo = 1; return S_OK;}
    STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo** pptinfo)
    {return m_arrpTih[0]->GetTypeInfo(itinfo, lcid, pptinfo);}
    STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames, UINT cNames,
        LCID lcid, DISPID* rgdispid)
    {
        /*perhaps the loop should check for a succeeded hresult instead*/
        HRESULT hr = DISP_E_UNKNOWNNAME;
        for ( UINT i=0; i < t_uiNum && hr == DISP_E_UNKNOWNNAME; i++ )
        {
            hr = m_arrpTih[i]->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
        }
        return hr;
    };

    STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr)
    {
        HRESULT hr = DISP_E_MEMBERNOTFOUND;
        for ( int i=0; i < t_uiNum && hr == DISP_E_MEMBERNOTFOUND ; i++ )
        {
            hr = m_arrpTih[i]->Invoke( m_arrDispImpls[i], dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr);
        }
        return hr;
    } ;

    LPDISPATCH m_arrDispImpls[t_uiNum];
    _tihclass * m_arrpTih[t_uiNum];

};

As an example, if we were to implement an object with 3 dual interfaces we would do the following

typedef XMultiDualImpl<3> XTriDualImpl ;

class DispTest3 : public XTriDualImpl,
    public IDispatchImpl<IDispTest1, &IID_IDispTest1, &LIBID_FirstLib>,
    public IDispatchImpl<IDispTest2, &IID_IDispTest2, &LIBID_SecondLib>,
    public IDispatchImpl<IThirdIntfTest, &IID_IThirdIntfTest, &LIBID_ThridLib>,
    public CComObjectRoot,
    public CComCoClass<DispTest3,&CLSID_DispTest3>
{
public:
    DispTest3()
    {
        Add( 1,(IDispTest1*)this, IDispatchImpl<IDispTest1, &IID_IDispTest1, &LIBID_FirstLib>::_tih );
        Add( 2,(IDispTest2*)this, IDispatchImpl<IDispTest2, &IID_IDispTest2, &LIBID_SecondLib>::_tih );
        Add( 3,(IThirdIntfTest*)this, IDispatchImpl<IThirdIntfTest, &IID_IThirdIntfTest, &LIBID_ThridLib>::_tih );
}

BEGIN_COM_MAP(DispTest3)
    COM_INTERFACE_ENTRY2(IDispatch, XTriDualImpl)
    COM_INTERFACE_ENTRY(IDispTest1)
    COM_INTERFACE_ENTRY(IDispTest2)
    COM_INTERFACE_ENTRY(IThirdIntfTest)
END_COM_MAP()

//IDispTest1 methods [code omitted for brevity]
//IDispTest2 methods [code omitted for brevity]
//IThirdIntfTest methods [code omitted for brevity]
};

I admit the constructor of the derived class leaves room for elegance.

Basically, all QueryInterfaces for IDispatch will result in the XMultiDualImpl vtable. The IDispatch implementation for that vtable basically asks each IDispatch vtable (of the dual interface) in it’s array one by one if it can carry out the call. If it fails, it moves on to the next dual interface vtable.

One might point out the following however :

pUnk->QueryInterface( IID_IDispatch, (void**)&pIDispatch );
pUnk->QueryInteface( IID_IDispTest1, (void**)&pIDispTest1 );

pIDispatch->GetIDsOfNames does not have the same implementation as pIDispTest1->GetIDsOfNames. as well as the implementation differs for ->Invoke. I question whether or not that’s a problem. Since IDispTest1 derives from IDispatch, I could see why it may be.

-herb