from
http://progtutorials.tripod.com/COM.htm
- vPtrs of Interfaces
- COM Map
- _ATL_INTMAP_ENTRY structure
- vPtr of IUnknown interface
- _InternalQueryInterface
- Interface Method Name Clash
- Solving Inheritance Path Ambiguity with COM Map Macros
- Tear-off Interface
- Tear-off interface in raw C++
- Tear-off interface with COM map macros
- CComTearOffObjectBase
- ATL tear-off class
- ATL owner class
- Caching the Tear-off Class
- COM Component Containment
- Making a Coclass Aggregable
- How to Aggregate Other Aggregable Coclasses
- Selective Aggregation
- Blind aggregation
- Auto aggregation
- Four Last COM Map Macros
1 vPtrs of Interfaces
As you can see from chapter ¡°Polymorphism¡± section ¡°Late Binding with vtable and vPtr¡± in my C++ tutorial, because interfaces do not have data members, in a COM component¡¯s memory footprint, the vPtrs of all interfaces that this component implements are placed one next to another. Each vPtr points to a vtable of one interface, whose function pointers points to the latest/lowest implementations of the interface¡¯s methods. Returning a reference of an interface to the client is all about returning the vPtr of the interface to the client.
2 COM Map
An ATL COM map provides a static array of _ATL_INTMAP_ENTRY structures, which stores the IIDs and vPtrs of all the interfaces implemented by the coclass. It also provides some simple functions that can be called to acquire from the array the vPtr of an interface given its IID.
An ATL COM map in a coclass definition looks like
class ATL_NO_VTABLE CSource :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CSource, &CLSID_Source>,
public IDispatchImpl<ISource, &IID_ISource, &LIBID_THREADSLib>
{
public:
CSource() {}
DECLARE_REGISTRY_RESOURCEID(IDR_SOURCE)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CSource)
COM_INTERFACE_ENTRY(ISource)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// ISource
public:
STDMETHOD(Advise)(/*[in]*/ ICallBack * pCallBack);
STDMETHOD(DoWork)();
private:
ICallBack * m_pCallBack;
}; The above four macros will expend to the following lines:
//****************** BEGIN_COM_MAP macro *****************
public:
typedef CSource _ComMapClass;
static HRESULT WINAPI _Cache(void* pv, REFIID iid, void** ppvObject, DWORD dw)
{
_ComMapClass* p = (_ComMapClass*)pv;
p->Lock();
HRESULT hRes = CComObjectRootBase::_Cache(pv, iid, ppvObject, dw);
p->Unlock();
return hRes;
}
IUnknown* _GetRawUnknown()
{
ATLASSERT(_GetEntries()[0].pFunc == _ATL_SIMPLEMAPENTRY);
return (IUnknown*)((int)this + _GetEntries()->dw);
}
IUnknown* GetUnknown()
{
return _GetRawUnknown();
}
HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject)
{
return InternalQueryInterface(this, _GetEntries(), iid, ppvObject);
}
const static _ATL_INTMAP_ENTRY * WINAPI _GetEntries()
{
static const _ATL_INTMAP_ENTRY _entries[] =
{
//*********** COM_INTERFACE_ENTRY(ISource) *************
{
// _ComMapClass is defined to be CSource
&__uuidof(ISource),
offsetofclass(ISource, _ComMapClass),
_ATL_SIMPLEMAPENTRY
},
//*********** COM_INTERFACE_ENTRY(IDispatch) *************
{
&__uuidof(IDispatch),
offsetofclass(IDispatch, _ComMapClass),
_ATL_SIMPLEMAPENTRY
},
//*********** END_COM_MAP macro *************
{
NULL, 0, 0
}
};
return _entries;
}
3 _ATL_INTMAP_ENTRY structure
The structure is defined as follow:
struct _ATL_INTMAP_ENTRY
{
const IID * piid; // the interface id (IID)
DWORD dw;
_ATL_CREATORARGFUNC * pFunc; //NULL:end, 1:offset, n:ptr
}; piid is the IID of the interface, which is calculated in the macro by keyword __uuidof, which can retrieve the uuid attached to a type definition.
dw is the offset to the vPtr of a given interface in relate to the coclass¡¯s this pointer, which is calculated in the macro by another macro offsetofclass.
pFunc can either be set to _ATL_SIMPLECOMENTRY or points to one of the CComObjectRootBase helper functions such as _Creator or _Cache. If pFunc is _ATL_SIMPLECOMENTRY, it tells AtlInternalQueryInterface that this interface is an "internal" interface directly inherited by the cocloass, not an aggregated or tear-off interface. Therefore its address can be calculated by the coclass's this pointer plus the provided offset dw:
if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset
{
...
IUnknown* pUnk = (IUnknown*)((int)pThis + pEntries->dw); If pFunc is not _ATL_SIMPLECOMENTRY, it tells AtlInternalQueryInterface that the interface is an aggregation or tear-off interface. Than pFunc is supposed to point to one of the CComObjectRootBase helper functions, and AtlInternalQueryInterface calls that function through pFunc to calculate the interface address:
HRESULT hRes = pEntries->pFunc(pThis, iid, ppvObject, pEntries->dw);
4 vPtr of IUnknown interface
Because all interfaces inherit from IUnknown, the vtables of all interface contains the function pointers of the three IUnknown methods. All of these function pointers points to the same latest/lowest implementations of the three methods. Therefore, you can access all three methods of IUnknown with the vPtr of any interface.
When the client asks for the IUnknown interface of the coclass, AtlInternalQueryInterface can simply get from the array the vPtr of any internal interface and return it to the client. So it chooses the first one in the map. All AtlInternalQueryInterface needs to do is to make sure that this interface is an internal interface, not an aggregated or tear-off interface, by checking that pEntries->pFunc is equal to _ATL_SIMPLECOMENTRY. From the implementation of AtlInternalQueryInterface you can see, the very first thing it does is to assert the above condition, and if it fails, the whole method fails.
For this reason, you must make sure that the first entry in th COM map is represented by one of the four macros, which are all used to declare internal interfaces and all specify pFunc to be _ATL_SIMPLECOMENTRY:
- COM_INTERFACE_ENTRY
- COM_INTERFACE_ENTRY2
- COM_INTERFACE_ENTRY_IID
- COM_INTERFACE_ENTRY2_IID
5 _InternalQueryInterface
An ATL coclass does not provide implementation of IUnknown interface. Instead, a CComObject<> template classe takes the coclass as template parameter and inherits from it, and provides the three IUnknown methods. These methods simply turn around and call the InternalAddRef, InternalRelease and _InternalQueryInterface.
InternalAddRef and InternalRelease are provided by CComObjectRootEx<>, which turn around and call the Increment and Decrement method provided by the threading model class passed as template parameter.
_InternalQueryInterface is provided by the above-shown COM map macro, which turns around and calls InternalQueryInterface provided by CComObjectRootBase, which again turns around and calls ATL API AtlInternalQueryInterface, which makes use of the COM map and searches for an interface with an IID:
ATLINLINE ATLAPI AtlInternalQueryInterface(
void * pThis, // "this" pointer of the coclass
const _ATL_INTMAP_ENTRY * pEntries, // The array
REFIID iid, // Interface IID
void ** ppvObject) // Receiving interface pointer
{
ATLASSERT(pThis != NULL);
// First entry in the com map should be a simple map entry
ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);
if (ppvObject == NULL) // No receiving pointer is passed
return E_POINTER;
*ppvObject = NULL;
if (InlineIsEqualUnknown(iid)) // use the first interface
{
IUnknown * pUnk = (IUnknown *)((int)pThis + pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
while (pEntries->pFunc != NULL)
{
BOOL bBlind = (pEntries->piid == NULL);
if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid))
{
if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) // offset
{
ATLASSERT(!bBlind);
IUnknown* pUnk = (IUnknown*)((int)pThis + pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
else //actual function call
{
HRESULT hRes =
pEntries->pFunc(pThis, iid, ppvObject, pEntries->dw);
if (hRes == S_OK || (!bBlind && FAILED(hRes)))
return hRes;
}
}
pEntries++;
}
return E_NOINTERFACE;
}
6 Interface Method Name Clash
Suppose we have two interfaces IFirst and ISecond, each with a method Hi. As you can see from section ¡°Method Name Clash and Ambiguity in Multiple Inheritance¡± in chapter ¡°Multiple Inheritance¡± in my C++ tutorial, if we want to have to different implementations of method Hi, we must have two wrapper classes implementing the two interfaces, and let the coclass inherit from these two classes.
The two wrapper classes only need to implement the clashing method Hi (therefore they are still abstract classes), leaving non-clashing methods (MethodFirst and MethodSecond) to be implemented by the coclass as usual:
// **************** Test.idl ******************
...
interface IFirst : IUnknown
{
[helpstring("method Hi")] HRESULT Hi();
[helpstring("method MethodFirst")] HRESULT MethodFirst();
};
...
interface ISecond : IUnknown
{
[helpstring("method Hi")] HRESULT Hi();
[helpstring("method MethodSecond")] HRESULT MethodSecond();
};
// ************* Impls.h *******************
struct IFirstImpl : public IFirst
{
STDMETHODIMP Hi()
{
m("Hi from IFirstImpl!");
return S_OK;
}
};
struct ISecondImpl : public ISecond
{
STDMETHODIMP Hi()
{
m("Hi from ISecondImpl!");
return S_OK;
}
};
// ************ CAny.h ***************
#ifndef __ANY_H_
#define __ANY_H_
#include "resource.h" // main symbols
#include "impls.h"
class ATL_NO_VTABLE CAny :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CAny, &CLSID_Any>,
public IFirstImpl,
public ISecondImpl
{
public:
CAny() {}
DECLARE_REGISTRY_RESOURCEID(IDR_ANY)
DECLARE_NOT_AGGREGATABLE(CAny)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CAny)
COM_INTERFACE_ENTRY(IFirst)
COM_INTERFACE_ENTRY(ISecond)
END_COM_MAP()
public:
STDMETHOD(MethodSecond)();
STDMETHOD(MethodFirst)();
//STDMETHOD(Hi)(); // Do not implement!
};
#endif //__ANY_H_ The COM map still gives the client the vPtrs of the two interfaces, not that of the two wrapper classes. Actually you can put the name of the wrapper classes instead of the interfaces in the COM map, so that it gives the client the vPtrs of the wrapper classes. It doesn¡¯t matter, because the function pointers of method Hi in the vtables in both the interface and its wrapper class point to the implementation in the wrapper class. But to use the second option you have to give the IID of the interfacess to their wrapper classes.
7 Solving Inheritance Path Ambiguity with COM Map Macros
Suppose we have two interfaces IAquatic and IMammal which inherit from interface ICreature. Coclass CDelphin implements all three interfaces: ICreature, IAquatic and IMammal. This coclass in ATL will look like
class ATL_NO_VTABLE CDelphin :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CDelphin, &CLSID_Delphin>,
public IAquatic, // Instead of IInterf1
public IMammal // Instead of IInterf2
{
public:
CDelphin() {}
DECLARE_REGISTRY_RESOURCEID(IDR_DELPHIN)
DECLARE_NOT_AGGREGATABLE(CDelphin)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CDelphin)
COM_INTERFACE_ENTRY(ICreature) // Ambiguious!
COM_INTERFACE_ENTRY(IAquatic)
COM_INTERFACE_ENTRY(IMammal)
END_COM_MAP()
}; When compiler casts the coclass¡¯s this pointer to ICreature, it sees two paths, so it will be confused. We have to let the compiler know which path to take. Now suppose we want to take IMammal¡¯s path, we can use one of the following three macros to substitute the basic COM_INTERFACE_ENTRY:
COM_INTERFACE_ENTRY2(ICreature, IMammal) // or
COM_INTERFACE_ENTRY_IID(IID_ICreature, IMammal) // or
COM_INTERFACE_ENTRY2_IID(IID_ICreature, ICreature, IMammal)
8 Tear-off Interface
Because of the nature of COM ? once a coclass supports an interface it must forever support it, with the evolution of the server, there must be some interfaces which are old and have very little chance to be used. If a coclass implements lots of such old interfaces, it will carry some performance and memory overhead ? it takes time and memory to create the vPtrs of the old interfaces.
For each of these old interfaces, we can create a wrapper class to implement it separately from the coclass. The coclass no longer implement the old interface. In its QueryInterface, when this old interface is asked, we simply create a new instance of the wrapper class, cast it and return it to the client. Such an ¡°stand-alone¡± interface is called tear-off interface. The coclass is called owner class.
The wrapper class has the same reference count m_refCount and the same implementation of AddRef and Release as the coclass, so that it can delete itself when not referenced anymore. It holds a pointer to the owner class, so that in its own QueryInterface it can call the owner¡¯s QueryInterface to direct the queries back to the owner.
9 Tear-off interface in raw C++
// ****************** Base.h *********************
#include "unknwn.h"
#include <windows.h>
struct IPopular : public IUnknown
{
STDMETHOD(Hi)() PURE;
};
struct IOld : public IUnknown
{
STDMETHOD(Hello)() PURE;
};
// ******************* Old.h **********************
#include "stdafx.h"
#include "base.h"
#include "owner.h"
class COld : public IOld
{
private:
COwner * m_pOwner;
ULONG m_refCount;
public:
COld(COwner * pOwner);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);
STDMETHODIMP Hello();
};
//******************* Old.cpp **********************
#include "stdafx.h"
#include "Old.h"
#include "iid.h"
COld::COld(COwner * pOwner) : m_refCount(0), m_pOwner(pOwner) {}
STDMETHODIMP_(ULONG) COld::AddRef()
{
return ++m_refCount;
}
STDMETHODIMP_(ULONG) COld::Release()
{
if(--m_refCount == 0)
{
delete this;
return 0;
}
else
return m_refCount;
}
STDMETHODIMP COld::QueryInterface(REFIID riid, void ** ppv)
{
if(riid == IID_IOld)
*ppv = (IOld *)this;
else
return m_pOwner->QueryInterface(riid, ppv);
return S_OK;
}
STDMETHODIMP COld::Hello()
{
printf("Hello from COld!\n");
return S_OK;
}
//**************** Owner.h ******************
#include "stdafx.h"
#include "base.h"
class COwner : public IPopular
{
public:
COwner();
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);
STDMETHODIMP Hi();
private:
ULONG m_refCount;
};
//****************** Owner.cpp *****************
#include "stdafx.h"
#include "owner.h"
#include "Old.h"
#include "iid.h"
COwner::COwner() : m_refCount(0)
{
}
STDMETHODIMP_(ULONG) COwner::AddRef()
{
return ++m_refCount;
}
STDMETHODIMP_(ULONG) COwner::Release()
{
if(--m_refCount == 0)
{
delete this;
return 0;
}
else
return m_refCount;
}
STDMETHODIMP COwner::QueryInterface(REFIID riid, void ** ppv)
{
if(riid == IID_IUnknown)
*ppv = (IUnknown *)this;
else if(riid == IID_IPopular)
*ppv = (IPopular *)this;
else if(riid == IID_IOld)
*ppv = (IOld *)new COld(this);
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
STDMETHODIMP COwner::Hi()
{
printf("Hi from COwner!\n");
return S_OK;
}
//******************* main *********************
int main(int argc, char* argv[])
{
COwner * pOwner = new COwner;
IPopular * pPopular;
HRESULT hr = pOwner->QueryInterface(IID_IPopular, (void **)&pPopular);
pPopular->Hi();
// Querying the old interface from the polular one
IOld * pOld;
hr = pPopular->QueryInterface(IID_IOld, (void **)&pOld);
pOld->Hello();
// Querying the popular interface from the old one
hr = pOld->QueryInterface(IID_IPopular, (void **)&pPopular);
pPopular->Hi();
return 0;
} Output will be:
Hi from COwner!
Hello from COld!
Hi from COwner!
10 Tear-off interface with COM map macros
10.1 CComTearOffObjectBase
In ATL, class CComTearOffObjectBase is provided to be used as a base class of a tear-off class. It inherits from CComObjectRootEx to provide synchronization support to the tear-off class, and added the needed pointer back to the owner class:
template <class Owner, class ThreadModel = CComObjectThreadModel>
class CComTearOffObjectBase : public CComObjectRootEx<ThreadModel>
{
public:
typedef Owner _OwnerClass;
CComObject<Owner>* m_pOwner;
CComTearOffObjectBase() {m_pOwner = NULL;}
};
10.2 ATL tear-off class
An ATL tear-off class inherits from CComTearOffObjectBase and implement the tear-off interface. It looks quite like a normal coclass except that it does not inherit from CComCoClass, which provides class factory and aggregation support:
class COwner;
class ATL_NO_VTABLE COld :
public CComTearOffObjectBase<COwner, CComSingleThreadModel>,
public IOld
{
public:
STDMETHOD(Hi)();
BEGIN_COM_MAP(COld)
COM_INTERFACE_ENTRY(IOld)
END_COM_MAP()
};
10.3 ATL owner class
An ATL owner class looks like:
class ATL_NO_VTABLE COwner :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<COwner, &CLSID_Popular>,
public IPopular
{
public:
COwner()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_POPULAR)
DECLARE_NOT_AGGREGATABLE(COwner)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(COwner)
COM_INTERFACE_ENTRY(IPopular)
COM_INTERFACE_ENTRY_TEAR_OFF(IID_IOld, COld)
END_COM_MAP()
public:
friend class COld;
} It does not inherit from the tear-off interface, and use COM map macro COM_INTERFACE_ENTRY_TEAR_OFF to configure its QueryInterface.
11 Caching the Tear-off Class
There is a slight problem with the above implementation both in raw C++ and in ATL: every time the client queries for the tear-off interface, a new instance of the tear-off class is created. To cache the tear-off class, make the following change on the owner class (tear-off class does not need any change):
- Add macro DECLARE_GET_CONTROLLING_UNKNOWN in the owner class definition, which expends to method GetControllingUnknown which can be called by others to get the IUnknown pointer of the owner class:
- Add a public IUnknown member to the owner class, initialize it to NULL in the constructor;
- Substitute the COM_INTERFACE_ENTRY_TEAR_OFF macro with COM_INTERFACE_ENTRY_CACHED_TEAR_OFF, and pass in the IUnknown member as the third parameter.
- In the FinalRelease method, release the IUnknown pointer.
The owner class will look like:
class ATL_NO_VTABLE COwner :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<COwner, &CLSID_Popular>,
public IPopular
{
public:
COwner() : m_pUnk(NULL) {}
void FinalRelease()
{
if(m_pUnk)
m_pUnk->Release();
}
DECLARE_GET_CONTROLLING_UNKNOWN()
DECLARE_REGISTRY_RESOURCEID(IDR_POPULAR)
DECLARE_NOT_AGGREGATABLE(COwner)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(COwner)
COM_INTERFACE_ENTRY(IPopular)
COM_INTERFACE_ENTRY_CHCHED_TEAR_OFF(IID_IOld, COld, m_pUnk)
END_COM_MAP()
public:
friend class COld;
IUnknown * m_pUnk;
}
12 COM Component Containment
When you are designing a COM component, if you think some work can be done by another COM component so that you can avoid rewriting the code, you can start to use COM component containment. No new techniques is involved. Just invoke that component using basic COM API function calls such as CoCreateInstance and QueryInterface. Nothing needs to be done on the COM map.
A common way to do that is to have some interface pointers of the contained component as your data members, call CoCreateInstance and QueryInterface to initialize them in your FinalConstruct, and release them in your FinalRelease.
13 Making a Coclass Aggregable
In aggregation, the outer component exposes the inner-component interfaces directly to clients, so that from the client¡¯s point of view these inner-component interfaces are fully supported by the outer component. This process is quite complicated and better be done with ATL support.
To be able to server as inner object, a COM coclass must conform to the following conditions:
- It must reside in a DLL not an EXE server.
- If you want the coclass to be able to be aggregated by an EXE server, it must register its proxy/stub DLL server.
- The outer object and the inner object must be in the same threading apartment.
- When you create the coclass using ATL object wizard, you must NOT tick ¡°No¡± as the aggregation option.
Option ¡°No¡± will insert into your coclass definition macro DECLARE_NOT_AGGREGATABLE, which disables the coclass to be aggregated. Option ¡°Yes¡± will insert DECLARE_AGGREGATABLE, which enables the coclass to server as either a stand-alone object or an inner object. Option ¡°Only¡± will insert macro DECLARE_ONLY_AGGREGATABLE, which restrains the coclass to only server as an inner object.
Macro DECLARE_AGGREGATABLE is defined in CComCoClass. It will be discussed together with ATL¡¯s object map.
14 How to Aggregate Other Aggregable Coclasses
There are three ways for a coclass to aggregate other aggregable coclasses:
14.1 Selective Aggregation
Selective aggregation is achieved with COM map macro COM_INTERFACE_ENTRY_AGRREGATE. The outer component selectively exposes some of the inner-component interfaces to clients. Only when clients query for these selected interfaces will the outer-component QueryInterface directly the queries to the inner component.
To selectively aggregate an aggregable inner class, an outer object must do the following things:
- Add macro DECLARE_GET_CONTROLLING_UNKNOWN to the outer-class definition, which defines method GetControllingUnknown, which acquires the IUnknown pointer of the outer class;
- Have a IUnknown pointer as data member;
- In FinalConstruct, create an instance of the inner object by calling CoCreateInstance passing the outer object¡¯s IUnknown pointer as the second parameter, which is acquired by method call GetControllingUnknown. Assign the acquired IUnknown pointer to the member pointer.
- In FinalRelease, release the inner-object IUnknown member;
- Declare each inner-object interface which you would like to expose with macro COM_INTERFACE_ENTRY_AGGREGATE in the COM map, passing the inner-object IUnknown pointer as the second parameter.
- Add the inner-object interfaces into the outer-class IDL class definition.
// ******************** AnyOuter.h *********************
#include "resource.h"
#include "Inner.h"
class ATL_NO_VTABLE CAnyOuter :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CAnyOuter, &CLSID_AnyOuter>,
public IAnyOuter // The outer class¡¯s own interface
{
public:
CAnyOuter() : m_pInnerUnk(NULL) {}
HRESULT FinalConstruct();
void FinalRelease();
DECLARE_GET_CONTROLLING_UNKNOWN()
DECLARE_REGISTRY_RESOURCEID(IDR_ANYOUTER)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CAnyOuter)
COM_INTERFACE_ENTRY(IAnyOuter)
COM_INTERFACE_ENTRY_AGGREGATE(IID_IInterf1, m_pInnerUnk)
COM_INTERFACE_ENTRY_AGGREGATE(IID_IInterf2, m_pInnerUnk)
END_COM_MAP()
public:
STDMETHOD(MethodOuter)(); // Outer class interface¡¯s method
IUnknown * m_pInnerUnk;
};
// ***************** AnyOuter.cpp *******************
#include "stdafx.h"
#include "Outer.h"
#include "AnyOuter.h"
#include "Inner_i.c"
STDMETHODIMP CAnyOuter::MethodOuter()
{
::MessageBox(NULL, "MethodOuter!", "Server Outer", MB_OK);
return S_OK;
}
HRESULT CAnyOuter::FinalConstruct()
{
HRESULT hr;
hr = CoCreateInstance(
CLSID_Any,
GetControllingUnknown(),
CLSCTX_SERVER,
IID_IUnknown,
(void **)&m_pInnerUnk);
return hr;
}
void CAnyOuter::FinalRelease()
{
if(m_pInnerUnk)
m_pInnerUnk->Release();
}
14.2 Blind aggregation
In ATL¡¯s blind aggregation, the outer object will direct direct all queries for unsupported interfaces to the inner object. It is achieved with COM map macro COM_INTERFACE_ENTRY_AGGREGATE_BLIND. The COM map will become
BEGIN_COM_MAP(CAnyOuter)
COM_INTERFACE_ENTRY(IAnyOuter)
COM_INTERFACE_ENTRY_AGGREGATE_BLIND(m_pInnerUnk)
END_COM_MAP()
14.3 Auto aggregation
Auto aggregation is achieved with COM map macro COM_INTERFACE_ENTRY_AUTOAGGREGATE and COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND. The inner object is not created in the FinalConstruct, but by the COM macro, only when client queries for the inner-object interface. This is quite like the purpose of tear-off interface.
The above example is simplified and becomes:
#include "resource.h"
#include "Inner.h"
class ATL_NO_VTABLE CAnyOuter :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CAnyOuter, &CLSID_AnyOuter>,
public IAnyOuter
{
public:
CAnyOuter() : m_pInnerUnk(NULL) {}
void FinalRelease()
{
if(m_pInnerUnk)
m_pInnerUnk->Release();
}
DECLARE_GET_CONTROLLING_UNKNOWN()
DECLARE_REGISTRY_RESOURCEID(IDR_ANYOUTER)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CAnyOuter)
COM_INTERFACE_ENTRY(IAnyOuter)
// Here m_pInnerUnk is used to receive the IUnknown pointer created
// inside the macro
COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IInterf1, m_pInnerUnk, CLSID_Any)
COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IInterf2, m_pInnerUnk, CLSID_Any)
END_COM_MAP()
public:
STDMETHOD(MethodOuter)();
IUnknown * m_pInnerUnk;
}; Just like macro COM_INTERFACE_ENTRY_AGGREGATE_BLIND, macro COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND is used to direct all queries to the inner object.
15 Four Last COM Map Macros
- COM_INTERFACE_ENTRY_BREAK - Using this macro in place of the standard macro COM_INTERFACE_ENTRY, when the interface declared with this macro is queried, the macro will call function DebugBreak to force the debugger to break.
- COM_INTERFACE_ENTRY_NOINTERFACE - This macro returns E_NOINTERFACE to explicitly tell the world that this interface is not supported by this coclass.
- COM_INTERFACE_ENTRY_CHAIN - This macro takes a class name as parameter, and is used to ¡°import¡± the COM map of the base class as part of your own.
- COM_INTERFACE_ENTRY_FUNC & COM_INTERFACE_ENTRY_FUNC_BLIND - COM_INTERFACE_ENTRY_FUNC takes three parameters: the REFIID of the interface as usual, a general-purpose DWORD, and the name of a global customized function which is written by you to replace the COM map macro¡¯s implementation of QueryInterface. The name of the function can be anything, but the signature must be:
HRESULT WINAPI func(
void* pv, // ¡°this¡± pointer of the coclass
REFIID riid,
LPVOID* ppv, // used to receive the found interface pointer
DWORD dw);
When client queries for a REFIID, and this COM_INTERFACE_ENTRY_FUNC macro is the first macro which specifies that REFIID, then this macro entry is entered, and the macro will call that customized function. If the function finds the interface, it will assign it to *ppv, and return S_OK. If it can not find the interface, it should assign NULL to *ppv. If you want to continue searching the rest of the COM map for following standard macros such as COM_INTERFACE_ENTRY which might return the correct interface pointer, the customized function should return S_FALSE. If you want to stop searching for that interface and tell client that this coclass does not support this interface, the function should return E_NOINTERFACE.
Therefore, if you want try your customized function first to search for a particular interface, and if it fails, resort to the standard COM map macro, then you should put the COM_INTERFACE_ENTRY_FUNC macro in front of the standard one:
BEGIN_COM_MAP(CAnyOuter)
COM_INTERFACE_ENTRY(IAnyOuter1)
COM_INTERFACE_ENTRY_FUNC(IID_IAnyOuter2, 123, func)
COM_INTERFACE_ENTRY(IAnyOuter2)
COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IInterf1, m_pInnerUnk, CLSID_Any)
COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IInterf2, m_pInnerUnk, CLSID_Any)
END_COM_MAP() If you put the standard macro in the front, the customized function will never be called.
A good place to put this global customized function is the source file of the coclass.
If you use macro COM_INTERFACE_ENTRY_FUNC_BLIND, then you do not need to concern the location, for all interface queries will go to the customized function first. If it returns S_FALSE, the rest of the COM map will be searched. If it returns E_NOINTERFACE, the coclass will tell client that it does not support the interface:
BEGIN_COM_MAP(CAnyOuter)
COM_INTERFACE_ENTRY(IAnyOuter1)
COM_INTERFACE_ENTRY(IAnyOuter2)
COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IInterf1, m_pInnerUnk, CLSID_Any)
COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IInterf2, m_pInnerUnk, CLSID_Any)
COM_INTERFACE_ENTRY_FUNC_BLIND(123, func)
END_COM_MAP()
SeriousMoin v1 (koMoinMoin 1.0a4 Modified)