ATL Architecture
This section explains how a generic ATL project works under the hood. The following
discussion assumes that we have an ATL DLL Server exposing two apartment-threaded objects as
follows:

Object Maps, Creation & Registration
Consider the following typical code generated by the ATL wizard for an ATL
DLL server. This part of the ATL project is responsible for creating and registering the
server (i.e., the outer shell ) that houses COM objects.
CComModule _Module;
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_TestIDL, CTestIDL)
END_OBJECT_MAP()
// DLL Entry Point
extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
_Module.Init(ObjectMap, hInstance, &LIBID_IDLLib);
DisableThreadLibraryCalls(hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
_Module.Term();
return TRUE; // ok
}
// Used to determine whether the DLL can be unloaded by OLE
STDAPI DllCanUnloadNow(void)
{
return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}
// Returns a class factory to create an object of the requested type
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
return _Module.GetClassObject(rclsid, riid, ppv);
}
// DllRegisterServer - Adds entries to the system registry
STDAPI DllRegisterServer(void)
{
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(TRUE);
}
// DllUnregisterServer - Removes entries from the system registry
STDAPI DllUnregisterServer(void)
{
return _Module.UnregisterServer(TRUE);
}
OBJECT MAP
The object map controls the class factories and registration for all coclasses in the server.
To understand the object map presented at the top of this code segment consider the following defines -
#define BEGIN_OBJECT_MAP(x) static
_ATL_OBJMAP_ENTRY x[] = {
#define OBJECT_ENTRY(clsid, class) \
{&clsid, \
&class::UpdateRegistry,\
&class::_ClassFactoryCreatorClass::CreateInstance,\
&class::_CreatorClass::CreateInstance, \
NULL, \
0, \
&class::GetObjectDescription, \
class::GetCategoryMap, \
class::ObjectMain },
#define END_OBJECT_MAP() {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};
Each OBJECT_ENTRY()
statement adds an entry to the _ATL_OBJMAP_ENTRY
structure which is described below -
struct _ATL_OBJMAP_ENTRY
{
const CLSID* pclsid;
HRESULT (WINAPI *pfnUpdateRegistry)(BOOL bRegister);
_ATL_CREATORFUNC* pfnGetClassObject;
_ATL_CREATORFUNC* pfnCreateInstance;
IUnknown* pCF;
DWORD dwRegister;
_ATL_DESCRIPTIONFUNC* pfnGetObjectDescription;
_ATL_CATMAPFUNC* pfnGetCategoryMap;
// These two functions used to register the module. Straight forward.
HRESULT WINAPI RevokeClassObject()
{
return CoRevokeClassObject(dwRegister);
}
HRESULT WINAPI RegisterClassObject(DWORD dwClsContext, DWORD dwFlags)
{
IUnknown* p = NULL;
if (pfnGetClassObject == NULL)
return S_OK;
HRESULT hRes = pfnGetClassObject(pfnCreateInstance,
IID_IUnknown,
(LPVOID*) &p);
if (SUCCEEDED(hRes))
hRes = CoRegisterClassObject(*pclsid, p, dwClsContext, dwFlags, &dwRegister);
if (p != NULL)
p->Release();
return hRes;
}
// Added in ATL 3.0
void (WINAPI *pfnObjectMain)(bool bStarting);
};
OBJECT_ENTRY()
adds values for all of the structure's data members. Understanding these
structures and how they work explains much of the architecture of ATL objects
Consider OBJECT_ENTRY(CLSID_Test1,
CTest1) in the following analysis:
- pclsid
= &CLSID_Test1
pclsid holds
the address of the CLSID structure for CTest1 class
- pfnUpdateRegistry = &CTest1::UpdateRegistry
pfnUpdateRegistry is the address of
CTest1::UpdateRegistry,
which is declared in the DECLARE_REGISTRY_xxx() macros. Look
at the #defines in <atlcom.h> for
DECLARE_NO_REGISTRY()
- Disables registration
DECLARE_REGISTRY()
- Directly manipulates registry. No .RGS file is used
DECLARE_REGISTRY_RESOURCE()
- Specify .RGS file by name
DELCARE_REGISTRY_RESOUECEID()
- Specify .RGS file by ID
For example,
#define DECLARE_REGISTRY_RESOURCEID(x)\
static HRESULT WINAPI UpdateRegistry(BOOL bRegister)\
{\
return _Module.UpdateRegistryFromResource(x, bRegister);\
}
- pfnGetClassObject = &CTest1::_ClassFactoryCreatorClass::CreateInstance
_ClassFactoryCreatorClass is a typedef usually defined by one of
DECLARE_CLASSFACTORY_xxx() macros. This macro is present in the
CComCoClass<> declaration from which CTest1 derives. For example -
#define DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(CComClassFactory)
#if defined(_WINDLL) | defined(_USRDLL)
#define DECLARE_CLASSFACTORY_EX(cf) \
typedef CComCreator< CComObjectCached< cf > > _ClassFactoryCreatorClass;
#else
// don't let class factory refcount influence lock count
#define DECLARE_CLASSFACTORY_EX(cf) \
typedef CComCreator< CComObjectNoLock< cf > > _ClassFactoryCreatorClass;
#endif
So, if we were building a DLL,
_ClassFactoryCreatorClass is CComCreator<CComObjectCached<CComClassFactory> >
If we were building an EXE
_ClassFactoryCreatorClass is CComCreator<CComObjectNoLock<CComClassFactory> >
CComCreator<>is an internal ATL class used to build objects. If the server is a DLL, then when building the class object
(class factory) it will be built as a CComObjectCached object. Otherwise
if the server is an EXE, it will be built as a CComObjectNoLock
Quick Note: All CComObjectxxx() classes are used to provide the
IUnknown implementation specific to a certain situation. For example,
CTest1 which is the object class is an abstract base class since it does not provide an
implementation to IUnknown members. Eventually,
CTest1 will be derived from CComObject<>
which will then provide the correct IUnknown implementation
- pfnCreateInstance = &CTest1::_CreatorClass::CreateInstance
_CreatorClassis a typedef defined by one of
the DECLARE_xxxAGGREGATABLE() macros. This macro is present in the
CComCoClass<> from which CTest1 derives. For example -
#define DECLARE_AGGREGATABLE(x) public:\
typedef CComCreator2< CComCreator< CComObject< x > >, \
CComCreator< CComAggObject< x > > > _CreatorClass;
CComCreator2<> is a very simple class with only one function,
CreateInstance(). If the object is not aggregated, the object
is created from the first argument, CComCreator< CComObject< x >
>, if it is aggregated, the object is created from the second argument,
CComCreator< CComAggObject< x > >
IMPORTANT NOTE: Our class, CTest1 , is an ABSTRACT BASE CLASS. It does not
implement IUnknown methods. The COM object that gets created by the class
factory is not an instance of CTest1 , but an instance of
CComObjectxxx<CTest1>. CComObjectxxx<> provide the
IUnknown implementation which will differ based
on the type of the object (aggregated, non-aggregated, class factory object,
etc.)
- pCF = NULL
- dwRegister = 0
pCFis the IUnknown obtained from the class factory object and it is assigned
once the class factory has been created and registered. dwRegister
is assigned the cookie returned by CoRegisterClassObject()
- pfnGetObjectDescription = &CTest1::GetObjectDescription()
This function is inherited from CComCoClass. Read its documentation for some
important notes.
The object map can be accessed DIRECTLY using CComModule::m_pObjMap data
member.
OBJECT_ENTRY_NON_CREATABLE macro allows you to register and initialize an object, but disallows creating it using
CoCreateInstance(). This macro is used to implement object that are created or accessed via method calls on an object
that is createable
OBJECT CREATION
How are COM objects created in ATL? An object is created by creating its class factory. Once the class factory has been created (i.e., obtained an
IClassFactory interface pointer), IClassFactory::CreateInstance() is called to
create the requested COM object and obtain the requested interface pointer.
Class factories are created in different ways depending on whether the module is
EXE or DLL. Once the class factories are created, the remaining code is identical.
Class Factory creation in a DLL
- When a client requests an interface on a COM object, COM run time callsl
the global DLLGetClassObject() (HINT: put a breakpoint there to
trace object creation)
- DLLGetClassObject() calls CComModule::GetClassObject() which in turn calls
AtlModuleGetClassObject()
AtlModuleGetClassObject() is defined in atlbase.h. Essentially, it loops
through the object map comparing the pclsid member of each element in the _ATL_OBJMAP_ENTRY array with the CLSID passed to
DLLGetClassObject(). When
it finds a match, it uses pCF to get the requested object interface. See code in
<atlbase.h>
Class Factory creation in an EXE
- When the EXE starts up, it creates ALL the available class factories and registers them. This process begins with a call in
_tWinMain() to CComModule::RegisterClassObjects()
- CComModule::RegisterClassObjects() calls AtlModuleRegisterClassObjects().
- AtlModuleRegisterClassObjects() is defined in
<atlbase.h>. Essentially it loops
through the object map calling RegisterClassObject() on each entry.
RegisterClassObject() is defined in the OBJECT_MAP
macro - see definition above.
It basically creates a class factory for the object and then it calls CoRegisterClassObject() on the class factory
Object creation in DLLs and EXEs
- Once a pointer on the class factory has been obtained, the object can be created by using
IClassFactory::CreateInstance() which calls CComClassFactory::CreateInstance() (HINT: put a breakpoint in the object's
constructor or in the object's FinalConstruct() )
- CComClassFactory::CreateInstance() uses pfnCreateInstance in _ATL_OBJMAP_ENTRY to create the object. Recall the
that pfnCreateInstance is &CTest1::_CreatorClass::CreateInstance which is expanded
to CComCreator2< T1, T2>::CreateInstance() where T1 = CComCreator<CComObject<CTest1> > and
T2 = CComCreator<CComAggObject<CTest1> >
template <class T1, class T2>
class CComCreator2
{
public:
static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
{
ATLASSERT(*ppv == NULL);
return (pv == NULL) ?
T1::CreateInstance(NULL, riid, ppv) :
T2::CreateInstance(pv, riid, ppv);
}
};
This means that CComCreator<>::CreateInstance is ultimately called to construct
either a CComObject<> or a CComAggObject<> :
template <class T1>
class CComCreator
{
public:
static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
{
ATLASSERT(*ppv == NULL);
HRESULT hRes = E_OUTOFMEMORY;
T1* p = NULL;
ATLTRY(p = new T1(pv))
if (p != NULL)
{
p->SetVoid(pv);
p->InternalFinalConstructAddRef();
hRes = p->FinalConstruct();
p->InternalFinalConstructRelease();
if (hRes == S_OK)
hRes = p->QueryInterface(riid, ppv);
if (hRes != S_OK)
delete p;
}
return hRes;
}
};
Note that FinalConstuct() is protected by a pair of matching
AddRef()/Release() calls. These should always be implemented be using the DECLARE_PROTECT_FINAL_CONSTRUCT() macro in the class declaration.
Class Factories in EXEs and DLLs
- When a class factory is created in an EXE during registration of class factories,
i.e., during RegisterClassObject(), COM puts a reference to each class factory
inside a table maintained by COM (Running Object Table -
ROT). It's this table
that is queried when a client wants to create an instance of an object. In DLLs,
whenever a client want to create an object, the DLL will create the class factory
and then create the object and then destryy the class factory.
Class Factories & Locking in DLLs
- A server implemented as a DLL is loaded into the client's address and should be
unloaded when the DLL is not serving any obejcts. Only the DLL knows when
it should be unloaded. Therefore, it should keep a count on the number of active
objects. This count is incremented when an object is CREATED and decremented
when an object is DESTROYED. Assume our server is a DLL and implements two COM objects,
one with IX interface and the other with IY interface -
long gl_ComponentCount = 0;
class CMyCom : public IX
{
CMyCom(
{
gl_ComponentCount++;
}
~CMyCom()
{
gl_ComponentCount--;
}
...
};
class CMyOtherCom : public IY
{
CMyOtherCom()
{
gl_ComponentCount++;
}
~CMyOtherCom()
{
gl_ComponentCount--;
}
...
};
Usually when a client asks for an interface, its class factory creates the
object using new ( CMyCom ob = new CMyCom;). This calls the CMyCom constructor
and increments the active object count by one. Right now we would have one active
component (gl_ComponentCount = 1). When the interface is released, its Release()
will call 'delete this;' which will result in the object being deleted and calls
its destructor. gl_ComponentCount is now zero. This means the DLL is not serving
any objects. Usually, a client would call CoFreeUnuseLibraries() periodially
(say during idle time) which calls DLLCanUnLoadNow().
DLLCanUnLoadNow() sees
that the ACTIVE OBJECTS count (global gl_ComponentCount) is zero and returns S_OK
to indicate that the DLL can unload by returning S_OK. This is a normal situation.
There is a special situation that is the main reason behind having a LockServer()
function. Assume the active object count is zero and CoFreeUnuseLibraries()
have
been called during idle time; it will get the OK to unload the server. But right
before CoFreeUnuseLibraries() gets the OK to unload the server, an interface is
requested on the object. It's too late. The DLL will be unloaded and our interface
pointer will point to invalid memory. Solution: LockServer() provides the client with a
way to keep the server in memory until it is finished with it. LockServer(TRUE)
will increment the gl_ComponentCount by one, and LockServer(FALSE)
will decrement
the gl_ComponentCount by one (the lock count and the count of active objects
are combined into one. We could maintain a separate count for active objects and
another count for locks. DllCanUnloadNow() would return S_OK only if BOTH counts
is zero.)
So, Call LockServer(TRUE), get interface pointers, then call
LockServer(FALSE) when done.
Class Factories & Locking in EXEs
- EXEs are proactive. A DLLs is told to unload, whereas an EXE unloads itself.
- For EXEs, class factories are created and registered (added to ROT) when
an EXE is started. Class factories are destroyed and unregistered when the
EXE shuts down. In order for an EXE to shut down, the RULES is that its active
object count and its lock count must be zero. Only then can an EXE shut down.
If the class factories objects was included in the active object count, then
the server will never shut down (since shut down is initiated by having an object
count of zero.)
- When an EXE see that its lock count and active object count is zero, it will
attempt to shut itself down. But the client may still want to create objects
on the server. Therefore, to prevent the server from shutting down, we use LockServer()
REGISTRATION
Registration is managed using two mechanisms, type libraries and registry
scripts. There are four main entities that need to be registered for a COM server.
| CLSID |
Using a registry script ,*.RGS |
| PROG ID |
Using a registry script, *.RGS |
| IID |
Using the type library, *.TLB |
| APP ID |
Using a registry script, *.RGS (only for EXE servers) |
DLL Registration
- regsvr32.exe calls DLLRegisterServer()
- DLLRegisterServer() calls CComModule::RegisterServer()
- CComModule::RegisterServer() calls AtlModuleRegisterServer() (defined in
<atlbase.h> )- AtlModuleRegisterServer()
goes through every object in the object map and
calls the function pointed to by pfnUpdateRegistry. From the discussion above, pfnUpdateRegistry maps to &class::UpdateRegistry where UpdateRegistry
is defined by one of the DECLARE_REGISTRY() macros. This will cause each object to be registered using its
.RGS file
- Once all objects have been registered, component categories are registered if present.
- Then, the type library for the module is registered by calling AtlModuleRegisterTypeLib().
See AtlModuleRegisterTypeLib() in <atlbase.h>.
EXE Registration
- EXE server checks command line for -RegServer or -UnregServer to perform registration
- If registration required, EXE server code calls CComModule::RegisterServer()
which calls AtlModuleRegisterServer(),
defined in <atlbase.h>. The sequence is exactly the same as for a DLL registration. However, the only difference
is that prior to calling CComModule::RegisterServer(), the EXE server code calls
CComModule::UpdateRegistryFromResouce() to register the APP ID which is defined in a separate
.RGS file
Type Library
- Type library-registration is used mainly to register the IIDs. It will not add
entries for coclasses. Only [oleautomation] interfaces are
registered by the type
library (dual interfaces deriving from IDispatch).
- Registering [oleautomation] interfaces using the type library means that these
interfaces will use the Universal Marshaler (oleaut32.dll) and the type library
for marshaling (look in the registry for the IID.)
Registrar
- The registrar is a COM object defined in ATL.DLL that parses registry scripts
and updates the registry accordingly.
- A .RGS file to register APPID is only created for EXEs in order to specify
security settings. A DLL does not need it since it will run in the security context of the client. But if we needed to isolate the DLL in its own security
context, we can assign the DLL an APPID which in turn will assign a surrogate
to run the DLL.
Test1 Object
- Test1 object is a generic object used to explain general aspects of ATL
- A COM object may be used in different ways; it may be created on the heap, on the stack, or it may be aggregated.
Each of these situations requires that IUnknown be implemented
differently.
- To keep the object's implementation the same, ATL encapsulates
these situations
in a set of CComObjectxx<> classes. This allows us to implement the COM object
without limiting them to being used in a particular way -
| Create the object on the stack |
CComObjectStack< CMyATL Class> |
| Create the object on the heap |
CComObject< CMyATL Class> |
| Allow the object to be aggregated |
CComAggObject< CMyATLClass > |
- Therefore, IUnknown methods are implemented by
CComObjectxxx<>. Wherever
an IUnknown method is called, it is called in
CComObjectxxx<> which delegates
it upwards :


- ATL uses Multiple Inheritance. This means that a single reference count is maintained for all interfaces -
class ISimple1 : public IUnknown { // virtual methods for ISimple1 };
class ISimple2 : public IUnknown { // virtual methods for ISimple2 };
class MyCoClass : public ISimple1,
public ISimple2
{
// IUnknwon implementations
QueryInterface() { ... }
AddRef() { ... }
Release() { ... }
// ISimple1 implementations
...
// ISimple1 implementations
...
};
- The following illustrates a sample code for a typical COM object
class ATL_NO_VTABLE CTest1 :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CTest1, &CLSID_Test1>,
public ISupportErrorInfo,
public IDispatchImpl<ITest1, &IID_ITest1, &LIBID_ARCHITECTLib>
{
public:
CTest1() {}
DECLARE_REGISTRY_RESOURCEID(IDR_TEST1)
DECLARE_PROTECT_FINAL_CONSTRUCT()
/* COM MAPS
These macros implement a static array where each item is a struct and each
struct holds information about each supported interface.
COM_INTERFACE_ENTRY adds entries to the static array.
BEGIN_COM_MAP and END_COM_MAP macros implement _InternalQueryInterface() which
obtains a pointer to the array (using _GetEntries()) and passes it to
InternalQueryInterface() (see figure below) which in turn passes it to
AtlInternalQueryInterface(). The following map generates the following array
entries (each entry is a struct of type _ATL_INTMAP_ENTRY) -
{&IID_ITest1, 0, _ATL_SIMPLEMAPENTRY},
{&IID_ISupportErrorInfo, 4, _ATL_SIMPLEMAPENTRY},
{0,0,0}
See end of class declaration for how these macros expand
If this COM object supports more than one dual interface, we must indicate
where IDispatch should come from:
COM_INTERFACE_ENTRY( IDispatch, ITest2 ) */
BEGIN_COM_MAP(CTest1)
COM_INTERFACE_ENTRY( ITest1 ) // Can use
COM_INTERFACE_ENTRY( IID_ITest1, ITest1)
COM_INTERFACE_ENTRY( IDispatch )
COM_INTERFACE_ENTRY( ISupportErrorInfo )
END_COM_MAP()
// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
// ITest1
public:
STDMETHOD(GetName)(/*[out,retval]*/ BSTR* pbstrName);
// Implementations
void Test_offsetofclassMacro();
};
#endif //__TEST1_H_
/* This section illustrates how COM maps expand
BEGIN_COM_MAP(CTest1)
COM_INTERFACE_ENTRY(ITest1)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
// BEGIN_COM_MAP
public:
typedef CTest1 _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 );
}
// #define _ATL_DECLARE_GET_UNKNOWN(x) IUnknown* GetUnknown() {return _GetRawUnknown();}
_ATL_DECLARE_GET_UNKNOWN(x)
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[] = { DEBUG_QI_ENTRY(CTest1)
// COM_INTERFACE_ENTRY macros
{&_ATL_IIDOF(ITest1), offsetofclass(ITest1, _ComMapClass), _ATL_SIMPLEMAPENTRY},
{&_ATL_IIDOF(IDispatch), offsetofclass(IDispatch, _ComMapClass), _ATL_SIMPLEMAPENTRY},
{&_ATL_IIDOF(ISupportErrorInfo), offsetofclass(ISupportErrorInfo, _ComMapClass),
_ATL_SIMPLEMAPENTRY},
// END_COM_MAP
{NULL, 0, 0}};
return _entries;
}
virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;
virtual ULONG STDMETHODCALLTYPE Release( void) = 0;
STDMETHOD(QueryInterface)(REFIID, void**) = 0;
// End of END_COM_MAP macro
*/