previous page
next page

Your Class

Because ATL provides the behavior for IUnknown in the CComObjectRootEx class and provides the actual implementation in the CComObject (and friends) classes, the job your class performs is pretty simple: derive from interfaces and implement their methods. Besides making sure that the interface map lists all the interfaces you're implementing, you can pretty much leave implementing IUnknown to ATL and concentrate on your custom functionality. This is, after all, the whole point of ATL in the first place.

ATL's Implementation Classes

Many standard interfaces have common implementations. ATL provides implementation classes of many standard interfaces. For example, IPersistImpl, IConnectionPointContainerImpl, and IViewObjectExImpl implement IPersist, IConnectionPointContainer, and IViewObjectEx, respectively. Some of these interfaces are common enough that many objects can implement themfor example, persistence, eventing, and enumeration. Some are more special purpose and related only to a particular framework, as with controls, Internet-enabled components, and Microsoft Management Console extensions. Most of the general-purpose interface implementations are discussed in Chapters 7, "Persistence in ATL"; 8, "Collections and Enumerators"; and 9, "Connection Points." The interface implementations related to the controls framework are discussed in Chapters 10, "Windowing," and 11, "ActiveX Controls." One implementation is general purpose enough to discuss right here: IDispatchImpl.

Scripting Support

For a scripting environment to access functionality from a COM object, the COM object must implement IDispatch:

interface IDispatch : IUnknown {                   
    HRESULT GetTypeInfoCount([out] UINT * pctinfo);
                                                   
    HRESULT GetTypeInfo([in] UINT iTInfo,          
        [in] LCID lcid,                            
        [out] ITypeInfo ** ppTInfo);               
                                                   
    HRESULT GetIDsOfNames([in] REFIID riid,        
        [in, size_is(cNames)] LPOLESTR * rgszNames,
        [in] UINT cNames,                          
        [in] LCID lcid,                            
        [out, size_is(cNames)] DISPID * rgDispId); 
                                                   
    HRESULT Invoke([in] DISPID dispIdMember,       
        [in] REFIID riid,                          
        [in] LCID lcid,                            
        [in] WORD wFlags,                          
        [in, out] DISPPARAMS * pDispParams,        
        [out] VARIANT * pVarResult,                
        [out] EXCEPINFO * pExcepInfo,              
        [out] UINT * puArgErr);                    
}                                                  

The most important methods of IDispatch are GetIDsOfNames and Invoke. Imagine the following line of scripting code:

penguin.wingspan = 102

This translates into two calls on IDispatch. The first is GetIDsOfNames, which asks the object if it supports the wingspan property. If the answer is yes, the second call to IDispatch is to Invoke. This call includes an identifier (called a DISPID) that uniquely identifies the name of the property or method the client is interested in (as retrieved from GetIDsOfNames), the type of operation to perform (calling a method, or getting or setting a property), a list of arguments, and a place to put the result (if any). The object's implementation of Invoke is then required to interpret the request the scripting client made. This typically involves unpacking the list of arguments (which is passed as an array of VARIANT structures), converting them to the appropriate types (if possible), pushing them onto the stack, and calling some other method implemented that deals in real data types, not VARIANTs. In theory, the object's implementation could take any number of interesting, dynamic steps to parse and interpret the client's request. In practice, most objects forward the request to a helper, whose job it is to build a stack and call a method on an interface implemented by the object to do the real work. The helper makes use of type information held in a type library typically bundled with the server. COM type libraries hold just enough information to allow an instance of a TypeInfo objectthat is, an object that implements ITypeInfoto perform this service. The TypeInfo object used to implement IDispatch is usually based on a dual interface, defined in IDL like this:

[ object, dual, uuid(44EBF74E-116D-11D2-9828-00600823CFFB) ]
interface IPenguin : IDispatch {
    [propput] HRESULT Wingspan([in] long nWingspan);
    [propget] HRESULT Wingspan([out, retval] long* pnWingspan);
              HRESULT Fly();
}

Using a TypeInfo object as a helper allows an object to implement IDispatch like this (code in bold indicates differences between one implementation and another):

class CPenguin :
    public CComObectRootEx<CComSingleThreadModel>,
    public IBird,
    public ISnappyDresser,
    public IPenguin {
public:
    CPenguin() : m_pTypeInfo(0) {
        IID*      pIID   = &IID_IPenguin;
        GUID*     pLIBID = &LIBID_BIRDSERVERLib;
        WORD      wMajor = 1;
        WORD      wMinor = 0;
        ITypeLib* ptl = 0;
        HRESULT hr = LoadRegTypeLib(*pLIBID, wMajor, wMinor,
            0, &ptl);
        if( SUCCEEDED(hr) ) {
            hr = ptl->GetTypeInfoOfGuid(*pIID, &m_pTypeInfo);
            ptl->Release();
        }
    }

    virtual ~Penguin() {
        if( m_pTypeInfo ) m_pTypeInfo->Release();
    }

BEGIN_COM_MAP(CPenguin)
    COM_INTERFACE_ENTRY(IBird)
    COM_INTERFACE_ENTRY(ISnappyDresser)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IPenguin)
END_COM_MAP()

    // IDispatch methods
    STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) {
        return (*pctinfo = 1), S_OK;
    }
    STDMETHODIMP GetTypeInfo(UINT ctinfo, LCID lcid,
    ITypeInfo **ppti) {
        if( ctinfo != 0 ) return (*ppti = 0), DISP_E_BADINDEX;
        return (*ppti = m_pTypeInfo)->AddRef(), S_OK;
    }

    STDMETHODIMP GetIDsOfNames(REFIID riid, OLECHAR **rgszNames,
        UINT cNames, LCID lcid, DISPID *rgdispid) {
        return m_pTypeInfo->GetIDsOfNames(rgszNames, cNames,
            rgdispid);
    }

    STDMETHODIMP Invoke(DISPID dispidMember,
                        REFIID riid,
                        LCID lcid,
                        WORD wFlags,
                        DISPPARAMS *pdispparams,
                        VARIANT *pvarResult,
                        EXCEPINFO *pexcepinfo,
                        UINT *puArgErr) {
        return m_pTypeInfo->Invoke(static_cast<IPenguin*>(this),
                                   dispidMember, wFlags,
                                   pdispparams, pvarResult,
                                   pexcepinfo, puArgErr);
    }
    // IBird, ISnappyDresser and IPenguin methods...
private:
    ITypeInfo* m_pTypeInfo;
};

Because this implementation is so boilerplate (it varies only by the dual interface type, the interface identifier, the type library identifier, and the major and minor version numbers), it can be easily implemented in a template base class. ATL's parameterized implementation of IDispatch is IDispatchImpl:

template <class T,                                  
          const IID* piid = &__uuidof(T),           
          const GUID* plibid = &CAtlModule::m_libid,
          WORD wMajor = 1,                          
          WORD wMinor = 0,                          
          class tihclass = CComTypeInfoHolder>      
class ATL_NO_VTABLE IDispatchImpl : public T {...}; 

Given IDispatchImpl, our IPenguin implementation gets quite a bit simpler:

class CPenguin :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IBird,
    public ISnappyDresser,
    public IDispatchImpl<IPenguin, &IID_IPenguin> {
public:
BEGIN_COM_MAP(CPenguin)
    COM_INTERFACE_ENTRY(IBird)
    COM_INTERFACE_ENTRY(ISnappyDresser)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IPenguin)
END_COM_MAP()
    // IBird, ISnappyDresser and IPenguin methods...
};

Supporting Multiple Dual Interfaces

I wish it wouldn't, but this question always comes up: "How do I support multiple dual interfaces in my COM objects?" My answer is always, "Why would you want to?"

The problem is, of the scripting environments I'm familiar with that require an object to implement IDispatch, not one supports QueryInterface. So although it's possible to use ATL to implement multiple dual interfaces, you have to choose which implementation to hand out as the "default"that is, the one the client gets when asking for IDispatch. For example, let's say that instead of having a special IPenguin interface that represents the full functionality of my object to scripting clients, I decided to make all the interfaces dual interfaces.

[ dual, uuid(...) ] interface IBird : IDispatch {...}
[ dual, uuid(...) ] interface ISnappyDresser : IDispatch { ... };

You can implement both of these dual interfaces using ATL's IDispatchImpl:

class CPenguin :
    public CComObjectRootEx<CComSingleThreadModel>,
    public IDispatchImpl<IBird, &IID_IBird>,
    public IDispatchImpl<ISnappyDresser, &IID_ISnappyDresser> {
public:
BEGIN_COM_MAP(CPenguin)
    COM_INTERFACE_ENTRY(IBird)
    COM_INTERFACE_ENTRY(ISnappyDresser)
    COM_INTERFACE_ENTRY(IDispatch) // ambiguous
END_COM_MAP()
...
};

However, when you fill in the interface map in this way, the compiler gets upset. Remember that the COM_INTERFACE_ENTRY macro essentially boils down to a static_cast to the interface in question. Because two different interfaces derive from IDispatch, the compiler cannot resolve the one to which you're trying to cast. To resolve this difficulty, ATL provides another macro:

#define COM_INTERFACE_ENTRY2(itf, branch)

This macro enables you to tell the compiler which branch to follow up the inheritance hierarchy to the IDispatch base. Using this macro allows you to choose the default IDispatch interface:

class CPenguin :
    public CComObjectRootEx<CComSingleThreadModel>,
    public IDispatchImpl<IBird, &IID_IBird>,
    public IDispatchImpl<ISnappyDresser, &IID_ISnappyDresser> {
public:
BEGIN_COM_MAP(CPenguin)
    COM_INTERFACE_ENTRY(IBird)
    COM_INTERFACE_ENTRY(ISnappyDresser)
    COM_INTERFACE_ENTRY2(IDispatch, IBird) // Compiles
                                           // (unfortunately)
END_COM_MAP()
...
};

That brings me to my objection. Just because ATL and the compiler conspire to allow this usage doesn't mean that it's a good one. There is no good reason to support multiple dual interfaces on a single implementation. Any client that supports QueryInterface will not need to use GetIDsOfNames or Invoke. These kinds of clients are perfectly happy using a custom interface, as long as it matches their argument type requirements. On the other hand, scripting clients that don't support Query-Interface can get to methods and properties on only the default dual interface. For example, the following will not work:

// Since IBird is the default, its operations are available
penguin.fly
// Since ISnappyDresser is not the default, its operations
// aren't available
penguin.straightenTie // runtime error

So, here's my advice: Don't design your reusable, polymorphic COM interfaces as dual interfaces. Instead, if you're going to support scripting clients, define a single dual interface that exposes the entire functionality of the class, as I did when defining IPenguin in the first place. As an added benefit, this means that you have to define only one interface that supports scripting clients instead of mandating that all of them do.

Having said that, sometimes you don't have a choice. For example, when building Visual Studio add-ins, you need to implement two interfaces: _IDTExtensibility2 and IDTCommandTarget. Both of these are defined as dual interfaces, so the environment forces you to deal with this problem.[6] You'll need to look at the documentation and do some experimentation to figure out which of your IDispatch implementations should be the default.

[6] The annoying thing here is that Visual Studio never even calls the IDispatch side of these dual interfaces, so making them dual interfaces was completely unnecessary.


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)