ATL s Normal Interface Map Macros

[Previous] [Next]

The "normal" interface map macros make up the first group of interface macros we'll look at. Many of these macros simply locate an offset into the ATL-based class. Some of the macros add interesting features, such as inserting debug breaks.


COM_INTERFACE_ENTRY is the most basic interface map macro. Use this macro whenever you have an interface you want to expose as a plain old vptr. Here's the COM_INTERFACE_ENTRY macro:

 #define COM_INTERFACE_ENTRY(x)\     {&_ATL_IIDOF(x), \     offsetofclass(x, _ComMapClass), \     _ATL_SIMPLEMAPENTRY}, 

When this macro expands, the interface map contains an _ATL_INTMAP_ENTRY filled with an IID, the address of the vptr within the class with object identity, and the number 1, which signifies that this is a simple entry (just a vptr within a C++ class). When a client calls QueryInterface, ATL looks up the interface. If it's a simple vptr entry, the client gets the address of the vptr. The offsetofclass macro simply performs a static cast on the class's this pointer. (_ComMapClass is an alias for the class containing the interface map—it's set up during the BEGIN_COM_MAP macro.) Here's the offsetofclass macro:

 #define offsetofclass(base, derived) \     ((DWORD)(static_cast<base*>((derived*)_ATL_PACKING))-\     _ATL_PACKING) 

As you can see, COM_INTERFACE_ENTRY represents the good old standard QueryInterface that most hardcore C++ developers are used to.


QueryInterface lies at the heart of COM. When COM developers find out something intriguing about COM or have a really interesting bug to kill, a call to QueryInterface is often involved. Being able to put a breakpoint on a call to QueryInterface is important for COM developers. ATL's interface map macros include an entry for breaking on QueryInterface requests. Here's the macro.

 #define COM_INTERFACE_ENTRY_BREAK(x)\     {&_ATL_IIDOF(x), \     NULL, \     _Break}, // Break is a function that issues int 3. 

COM_INTERFACE_ENTRY_BREAK is also easy to understand. This macro adds an _ATL_INTMAP_ENTRY structure containing the interface's GUID. The pFunc field points to the ATL function _Break, which simply wraps the function DebugBreak. In turn, DebugBreak issues an int 3, causing the debugger to break. This macro is useful for debugging client code to find when a client queries for a specific interface. This macro causes the debugger to break, but fails the QueryInterface.


The intersection between C++ developers and COM developers is quite large. Developers who have a long history with C++ are often accustomed to employing implementation inheritance in their coding practices. When you inherit one class from another, the derived class ends up with whatever's in the base class, including implementations of COM interfaces. Sometimes you implement a class in which you want to disable an interface implemented in a base class. COM_INTERFACE_ENTRY_NOINTERFACE exists for just this purpose—this macro disables an interface in a base class. Here's the macro:

 #define COM_INTERFACE_ENTRY_NOINTERFACE(x)\     {&_ATL_IIDOF(x), \     NULL, \     _NoInterface}, // NoInterface returns E_NOINTERFACE. 

When clients query for the interface, ATL searches the interface map for the entry. If ATL finds the entry, this macro causes QueryInterface to return E_NOINTERFACE, thereby disabling the interface.


Normally, COM interfaces have IIDs that closely resemble their names. For example, the interface ISomeInterface has the IID IID_ISomeInterface. If you define your interfaces using IDL, you'll get interface identifiers such as this when you compile the IDL file. However, you might encounter an interface whose GUID doesn't follow this pattern. If you do come across a situation in which an interface's name is different from its symbolic C++ name, COM_INTERFACE_ENTRY_IID is for you. Here's the macro:

 #define COM_INTERFACE_ENTRY_IID(iid, x)\     {&iid,\     offsetofclass(x, _ComMapClass),\     _ATL_SIMPLEMAPENTRY}, 

Use this macro if the IID for your interface is different from the name of your interface. COM_INTERFACE_ENTRY_IID performs the same static cast as COM_INTERFACE_ENTRY when ATL encounters this structure in the interface map.


Sometimes your COM class needs to expose an interface that is ambiguous. The perfect example is a dual interface that can be interpreted as both IDispatch and a larger vtable-based interface. This isn't a problem when only one dual interface is implemented on a class. However, you sometimes need to implement multiple dual interfaces on a class. In that case, you need a way to disambiguate the interface. That is, when the client asks for IDispatch, which interface should the client get back? Here's the macro:

 #define COM_INTERFACE_ENTRY2(x, x2)\     {&_ATL_IIDOF(x),\     (DWORD)((x*)(x2*)((_ComMapClass*)8))-8,\     _ATL_SIMPLEMAPENTRY}, 

Like IUnknown, IDispatch is strongly tied to an object's identity. If you decide you want to implement two dual interfaces on a COM class, you must decide which of the dual interfaces IDispatch applies to. If you derive your class from two dual interfaces, you need to expose IDispatch using COM_INTERFACE_ENTRY2, since IDispatch can be obtained from either of the interfaces.

COM_INTERFACE_ENTRY2 performs what is sometimes known as a branching cast. COM_INTERFACE_ENTRY2's interface entry casts the object's this pointer to the intermediate interface. For example, the following code shows how you might implement a COM class with two dual interfaces:

 class ATL_NO_VTABLE CSomeATLObj :      public CComObjectRootEx<CComSingleThreadModel>,     public CComCoClass<CSomeATLObj, &CLSID_SomeATLObj>,     public IDispatchImpl<IDual1, &IID_IDual1,         &LIBID_SOMEATLSVRLib>,     public IDispatchImpl<IDual2, &IID_IDual2,         &LIBID_SOMEATLSVRLib> { public:     CSomeATLObj()     {     } BEGIN_COM_MAP(CSomeATLObj)     COM_INTERFACE_ENTRY(IDual1)     COM_INTERFACE_ENTRY(IDual2)     COM_INTERFACE_ENTRY2(IDispatch, IDual1) END_COM_MAP() // function implementations removed for clarity }; 

The COM_INTERFACE_ENTRY2 macro causes ATL's QueryInterface to return IDual1's vpointer when the client requests IDispatch.


COM_INTERFACE_ENTRY2_IID provides another way to resolve intermediate interfaces like IDispatch. Unlike COM_INTERFACE_ENTRY2, COM_INTERFACE_ENTRY2_IID lets you resolve the interface entry using a different GUID. Here's the macro:

 #define COM_INTERFACE_ENTRY2_IID(iid, x, x2)\     {&iid,\     (DWORD)((x*)(x2*)((_ComMapClass*)8))-8,\     _ATL_SIMPLEMAPENTRY}, 

You use this macro in the same way that you use COM_INTERFACE_ENTRY2 except that you can specify a separate IID. For example, imagine having two interfaces derive from a single interface, as shown here:

 [     object,     uuid(B8D22023-8E87-11d2-802E-407A76000000),     helpstring("IBase interface"),     pointer_default(unique) ] interface IBaseInterface : IUnknown {     [helpstring("method BaseMethod1")] HRESULT BaseMethodOne();     [helpstring("method MethodTwo")] HRESULT BaseMethodTwo(); }; [     object,     uuid(B8D22027-8E87-11d2-802E-407A76000000),     helpstring("IDerived1 interface"),     pointer_default(unique) ] interface IDerived1Interface : IBaseInterface {     [helpstring("method DerivedInterface1Method1")]          HRESULT DerivedInterface1Method1();     [helpstring("method DerivedInterface1Method2")]          HRESULT DerivedInterface1Method2(); }; [     object,     uuid(B8D2202A-8E87-11d2-802E-407A76000000),     helpstring("IDerived2 interface"),     pointer_default(unique) ] interface IDerived2Interface : IBaseInterface {     [helpstring("method DerivedInterface2Method1")]           HRESULT DerivedInterface2Method1();     [helpstring("method DerivedInterface2Method2")]           HRESULT DerivedInterface2Method2(); }; 

To add this set of interfaces to your COM class, you'd derive your COM class from IDerived1Interface and IDerived2Interface, as shown here:

 class CSomeATLObj :      public CComObjectRootEx<CComSingleThreadModel>,     public CComCoClass<CSomeATLObj, &CLSID_SomeATLObj>,     public IDerived1Interface,     public IDerived2Interface {     // Implement the functions here. }; 

You might then use the COM_INTERFACE_ENTRY2_IID interface macros to implement QueryInterface, like this:

 BEGIN_COM_MAP(CSomeATLObj)     COM_INTERFACE_ENTRY2_IID(IID_IBaseInterface,          IBaseInterface,          IDerived1Interface)     COM_INTERFACE_ENTRY(IDerived1Interface)     COM_INTERFACE_ENTRY(IDerived2Interface) END_COM_MAP() 

When a client asks for the interface IBaseInterface, COM_INTERFACE_ENTRY2_IID chooses the vptr that belongs to IDerived1Interface.


The final two generic interface map entries from our list under "Interface Map Macros" create a way to hook into ATL's QueryInterface mechanism by providing a function for ATL to call whenever a specific interface is requested. Here's the COM_INTERFACE_ENTRY_FUNC macro:

 #define COM_INTERFACE_ENTRY_FUNC(iid, dw, func)\     {&iid, \     dw, \     func}, 

The first parameter to the macro is the interface ID. The second parameter to the macro is a DWORD utility value to be passed to the creation function that will be called by ATL. The last parameter to the macro is a pointer to the function to call. If the requested interface is found, the function specified by func is called. ATL expects the prototype for the function to look like this:

 HRESULT WINAPI func(void* pv, REFIID riid, LPVOID* ppv,     DWORD dw); 

As the COM class developer, you write this function. When ATL calls this function, ATL passes the class object in the first parameter (pv). ATL passes the IID in the second parameter (riid). The third parameter, ppv, is a place to hold the interface pointer. Finally, the fourth parameter, dw, is the parameter specified in the entry. This function's job is to instantiate the interface implementation and return the requested interface to the client.

For example, imagine that you want to implement an interface using a static global class inside our ATL server. You might write a class that looks like the one shown in the following code and then declare an instance of it in your code:

 class CSomeOtherObj : public ISomeOtherInterface { public:     STDMETHODIMP_(ULONG) AddRef()     {         _Module.Lock();         return 2;     }     STDMETHODIMP_(ULONG) Release()     {         _Module.Unlock();         return 1;     }     STDMETHODIMP QueryInterface(REFIID riid, void** ppv)     {         *ppv = 0;         if(riid == IID_ISomeOtherInterface)         {             *ppv = static_cast<ISomeOtherInterface*>(this);             ((IUnknown*)*ppv)->AddRef();             return S_OK;         } else         {             return E_NOINTERFACE;         }     }     STDMETHODIMP MethodOne(short x, short y, long* pz)     {         return S_OK;     }     STDMETHODIMP MethodTwo(long x, long y, long* pz)     {         return S_OK;     } }; CSomeOtherObj someOtherObj; 

One way to expose this interface as part of your COM class is to use the COM_INTERFACE_ENTRY_FUNC macro in the interface map. First write a function to go in the interface map. The function needs to return an HRESULT and takes a void*, an IID, a pointer to a pointer to a void, and a DWORD value. The following code shows the function:

 HRESULT _ _stdcall GetSomeOtherInterface(void* pv, REFIID riid,     void** ppv, DWORD dw) {     if(riid == IID_ISomeOtherInterface)     {         return someOtherObj.QueryInterface(riid, ppv);     } else     {         *ppv = 0;         return E_NOINTERFACE;     } } 

Finally, update the interface map to include the interface as shown here:


You can use the COM_INTERFACE_ENTRY_FUNC macro in several ways. The previous example shows only one, which is your generic hook for exposing interfaces from an ATL-based COM class.


This macro is similar to the COM_INTERFACE_ENTRY_FUNC macro. Instead of filtering out IIDs, however, this macro blindly calls a specified function during interface requests. Here's the macro:

 #define COM_INTERFACE_ENTRY_FUNC_BLIND(dw, func)\     {NULL, \     dw, \     func}, 

Use this macro whenever you want to insert a generic hook to be called during interface requests. The macro accepts a DWORD and a pointer function as parameters. The DWORD is an optional parameter to pass to the function listed in the macro. The function called through this interface map entry looks just like the one for COM_INTERFACE_ENTRY_FUNC, as shown here:

 HRESULT _ _stdcall CallThisForEveryQI(void* pv, REFIID riid,     void** ppv, DWORD dw) {     OutputDebugString("A call to QueryInterface  occurred!\n");     return S_FALSE;  } 

To make this hook work, just add the COM_INTERFACE_ENTRY_FUNC_BLIND macro to the interface map, as shown here:


CallThisForEveryQI will then be called during every call to QueryInterface.


The last normal interface map entry is COM_INTERFACE_ENTRY_CHAIN. This macro gives you a way to link the interface maps of a base class and a derived class. Here's the macro:

 #define COM_INTERFACE_ENTRY_CHAIN(classname)\     {NULL,\     (DWORD)&_CComChainData<classname, _ComMapClass>::data,\     _Chain}, 

When ATL hits this entry in an object's interface map, ATL looks to the interface map of the class specified in the argument of the macro. This is the macro for you if you want to tie the interface maps between a base class and a derived class together. ATL requires you to do this explicitly, and ATL-based objects don't inherit interface maps from their base classes. (Note that MFC's interface maps automatically chain themselves together.)

That does it for the normal, static-interface macros. Let's now take a look at ATL's support for dynamic interface composition techniques.

Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127 © 2008-2017.
If you may any questions please contact us: