from
http://progtutorials.tripod.com/COM.htm
- ATL¡¯s Object Map and Component Housing
- CComModule in DLL Server
- CExeModule in EXE server
- Object Map
- Beginning and end of an object map
- Object map entries
- Registering Server and its Coclasses
- When is this function pointer called
- Four macros defining UpdateRegistry
- Creating Class Factory
- When is this function pointer called
- Definition of _ClassFactoryCreatorClass
- CComCreator<>
- CComObjectCached and CComObjectNoLock
- CComClassFactory
- High-level abstraction
- Customizing your class factory
- Singleton class factory
- Coclass Creation with DECLARE_AGGREGATABLE Macros
- DECLARE_AGGREGATABLE
- DECLARE_NOT_AGGREGATABLE
- DECLARE_ONLY_AGGREGATABLE
- COM Category
- Non-Creatable Coclass
1 ATL¡¯s Object Map and Component Housing
The functionality of a component housing (of either a DLL server or an EXE server) is provided mainly by CComModule or its derived class CExeModule. A CComModule or CExeModule knows nothing about individual coclasses in the server. So an object map is provided to carry specific information about each coclass, so that CComModule or CExeModule can make use of. This is just like a COM map is designed to carry specific information about each interface (their vPtrs), so that AtlInternalQueryInterface can make use of.
2 CComModule in DLL Server
A DLL server has a global variable CComModule _Module which provides the functionality of a component housing, including checking object lock, creating class factories, and registering/unregistering the coclasses. When one of the DLL¡¯s exported functions is called, it simply delegates the call to _Module:
CComModule _Module;
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_Account, CAccount)
END_OBJECT_MAP()
extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
_Module.Init(ObjectMap, hInstance, &LIBID_BANKATLLib);
DisableThreadLibraryCalls(hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
_Module.Term();
return TRUE; // ok
}
STDAPI DllCanUnloadNow(void)
{
return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
return _Module.GetClassObject(rclsid, riid, ppv);
}
STDAPI DllRegisterServer(void)
{
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(TRUE);
}
STDAPI DllUnregisterServer(void)
{
return _Module.UnregisterServer(TRUE);
}
3 CExeModule in EXE server
An EXE server makes similar use of CComModule. ATL insert into your project¡¯s stdafx.h the definition of a new class called CExeModule, which inherits from CComModule and adds a bit EXE-specific code. So an EXE server has a global variable _Module of class CExeModule. Then function _tWinMain will call its methods to do all necessary jobs such as registering information into the System Registry and registering the class objects into the Running Object Table.
4 Object Map
_Module need specific information about each coclass contained in the server to do its jobs. Because each coclass has its own set of ¡°housing¡± methods to register itself into the System Registry, to create its own class object, to create an instance of itself, etc, we only need to inform _Module of the names of all these methods.
ATL stores the names of all ¡°housing¡± methods of each coclass in a structure called _ATL_OBJMAP_ENTRY. It stores the structures of all the server¡¯s coclasses in a static array, and tell _Module the name of this array when initializing _Module. Each server (DLL and EXE alike) has one such array.
Some _Module¡¯s method such as UpdateRegistryFromResource (when called with parameter
This array is defined with the help of object map macros:
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_Any1, CAny1)
OBJECT_ENTRY(CLSID_Any2, CAny2)
OBJECT_ENTRY(CLSID_Any2, CAny2)
END_OBJECT_MAP()
4.1 Beginning and end of an object map
BEGIN_OBJECT_MAP defines the beginning of a static _ATL_OBJMAP_ENTRY array:
#define BEGIN_OBJECT_MAP(x) static _ATL_OBJMAP_ENTRY x[] = { END_OBJECT_MAP defines an array element with all NULL values to mark the end of the array:
#define END_OBJECT_MAP() {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}}; Structure _ATL_OBJMAP_ENTRY is defined as:
struct _ATL_OBJMAP_ENTRY
{
const CLSID * pclsid;
_ATL_CREATORFUNC * pfnGetClassObject;
_ATL_CREATORFUNC * pfnCreateInstance;
IUnknown * pCF;
DWORD dwRegister;
_ATL_DESCRIPTIONFUNC* pfnGetObjectDescription;
_ATL_CATMAPFUNC * pfnGetCategoryMap;
HRESULT (WINAPI * pfnUpdateRegistry)(BOOL bRegister);
void (WINAPI * pfnObjectMain)(bool bStarting);
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;
}
}; As you can see, it mainly contains data members of function pointers.
4.2 Object map entries
Each coclass in a server must have an OBJECT_ENTRY entry in the object map. Each OBJECT_ENTRY macro defines one _ATL_OBJMAP_ENTRY structure element of the array:
#define OBJECT_ENTRY(clsid, class)
{
&clsid,
class::UpdateRegistry,
class::_ClassFactoryCreatorClass::CreateInstance,
class::_CreatorClass::CreateInstance,
NULL,
0,
class::GetObjectDescription,
class::GetCategoryMap,
class::ObjectMain
}, You can see that this macro assigns the function pointers of the structure with the methods of the corresponding coclass. Therefore all needed methods of all coclasses in a server are regisered in this _ATL_OBJMAP_ENTRY array, ready to be used by _Module.
Now we can tell the whole picture:
- When COM calls the exported functions of a DLL server, they simply delegate the calls to the _Module. When an EXE server is loaded, its _tWinMain function will also call _Module¡¯s methods to do all the jobs.
- When _Module is initialized, it is told the name of the object map;
- When one of _Module¡¯s methods is called, it goes through the _ATL_OBJMAP_ENTRY array, calling one of the registered methods of each coclass to do the job.
There is another object map entry macro OBJECT_ENTRY_NON_CREATABLE that will be discussed later.
5 Registering Server and its Coclasses
As we can see, macro OBJECT_ENTRY points function pointer pfnUpdateRegistry in structure _ATL_OBJMAP_ENTRY to the coclass¡¯s UpdateRegistry method.
class::UpdateRegistry,
5.1 When is this function pointer called
A DLL delegates a call to its DllRegsiterServer or DllUnregisterServer to _Module¡¯s RegisterServer and UnregisterServer.
An EXE server calls the same methods as a DLL in its _tWinMain:
if (lstrcmpi(lpszToken, _T("UnregServer"))==0)
{
_Module.UpdateRegistryFromResource(IDR_Test1, FALSE);
nRet = _Module.UnregisterServer(TRUE);
bRun = FALSE;
break;
}
if (lstrcmpi(lpszToken, _T("RegServer"))==0)
{
// register server itself
_Module.UpdateRegistryFromResource(IDR_Test1, TRUE);
// go through the object map and register all coclasses
nRet = _Module.RegisterServer(TRUE);
bRun = FALSE;
break;
} Here _tWinMain first calls _Module¡¯s UpdateRegistryFromResource directly with the resource ID of the server¡¯s own RGS file, to register information about the server itself. Then it calls _Module¡¯s method RegisterServer and UnregisterServer to register each coclass.
RegisterServer and UnregsiterServer goes through the object map and calls each coclass¡¯s function pointer pfnUpdateRegistry, which points to the coclass¡¯s UpdateRegistry method, which is defined by some macros.
5.2 Four macros defining UpdateRegistry
A coclass¡¯s UpdateRegistry method is defined by one of four macros.
First one is the default macro that ATL will adopt for your project, which uses the resource ID of the coclass¡¯s binary RGS file to call _Module¡¯s method UpdateRegistryFromResource:
#define DECLARE_REGISTRY_RESOURCEID(x)\
static HRESULT WINAPI UpdateRegistry(BOOL bRegister)\
{\
return _Module.UpdateRegistryFromResource(x, bRegister);\
} The second macro takes the string name of the coclass¡¯s RGS file and use it to call another overloaded _Module method UpdateRegistryFromResource:
#define DECLARE_REGISTRY_RESOURCE(x)\
static HRESULT WINAPI UpdateRegistry(BOOL bRegister)\
{\
return _Module.UpdateRegistryFromResource(_T(#x), bRegister);\
} The third macro doesn¡¯t do anything:
#define DECLARE_NO_REGISTRY()\
static HRESULT WINAPI UpdateRegistry(BOOL /*bRegister*/)\
{return S_OK;} The fourth macro do not use the coclass¡¯s RGS file. It only put some basic information into System Registry:
#define DECLARE_REGISTRY(class, pid, vpid, nid, flags)\
static HRESULT WINAPI UpdateRegistry(BOOL bRegister)\
{\
return _Module.UpdateRegistryClass(GetObjectCLSID(), pid, vpid, nid,\
flags, bRegister);\
}
6 Creating Class Factory
Not like in the raw C++ code example where a separate class factory class is defined for a coclass, in ATL some template classes are designed to wrap your coclasses and used as class factory classes.
For DLL and EXE server, different template classes are used. Instead of having different macros that uses different template classes, ATL defines only one macro DECLARE_CLASSFACTORY and makes use of an #if structure to provide different options for DLL and EXE servers.
As we can see, macro OBJECT_ENTRY points function pointer pfnGetClassObject in structure _ATL_OBJMAP_ENTRY to the coclass¡¯s inner class _ClassFactoryCreatorClass¡¯s CreateInstance method:
class::_ClassFactoryCreatorClass::CreateInstance,
Note that this CreateInstance method is used to create an instance of the class factory, not the coclass. It returns an IClassFactory pointer to the caller, who will then call that interface¡¯s CreateInstance to actually create the coclass instance.
6.1 When is this function pointer called
For a DLL server, when COM calls its exported DllGetClassObject, the following calls happens:
DllGetClassObject => _Module.GetClassObject => AtlModuleGetClassObject
AtlModuleGetClassObject will search the object map for the _ATL_OBJMAP_ENTRY structure of the queried coclass and call its pfnGetClassObject pointer. So _Module doesn¡¯t provide service for DllGetClassObject.
An EXE server calls _Module::RegisterClassObjects in its _tWinMain function, which goes through the object map and calls each _ATL_OBJMAP_ENTRY structure¡¯s pfnGetClassObject pointer.
In either a DLL or an EXE¡¯s case, when pfnGetClassObject is called, it is passed another field of the structure, pfnCreateInstance. You will see why later.
6.2 Definition of _ClassFactoryCreatorClass
_ClassFactoryCreatorClass is defined in CComCoClass by macro DECLARE_CLASSFACTORY:
#if defined(_WINDLL) | defined(_USRDLL)
#define DECLARE_CLASSFACTORY_EX(cf)
typedef CComCreator< CComObjectCached<cf> > _ClassFactoryCreatorClass;
#else
#define DECLARE_CLASSFACTORY_EX(cf)
typedef CComCreator< CComObjectNoLock<cf> > _ClassFactoryCreatorClass;
#endif
#define DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(CComClassFactory) You can see that the definition of _ClassFactoryCreatorClass depends on whether _WINDLL or _USRDLL is defined. If your ATL project is a DLL project, in ¡°Project¡± | ¡°Settings¡± | ¡°C\C++¡± with category ¡°Preprocessor¡±, you will see _USRDLL defined.
Therefore, for a DLL project, _ClassFactoryCreatorClass becomes
CComCreator< CComObjectCached<CComClassFactory> >
and for an EXE project, _ClassFactoryCreatorClass becomes
CComCreator< CComObjectNoLock<CComClassFactory> >
6.3 CComCreator<>
Template class CComCreator has only one method CreateInstance (the one pointed by the pfnGetClassObject pointer in _ATL_OBJMAP_ENTRY structure in the object map). It is used to create an instance of the template parameter class. It has the same signature as IClassFactory::CreateInstance. It simply creates an instance of the parameter class and calls its QueryInterface with the passed IID and interface pointer. So CComCreator is a very general-purpose class.
When CComCreator<>::CreateInstance is called by a DLL or EXE through object map structure field pfnGetClassObject, another field of the structure pfnCreateInstance pointing to the coclass-creating function is passed as the first void * parameter. CComCreator<>::CreateInstance calls the class factory¡¯s SetVoid to pass this pointer to it.
Method SetVoid is defined in CComObjectRootBase as one of the three ATL creator hook methods (the other two is InternalFinalConstructAddRef and InternalFinalConstructRelease). It is also a very general-purpose method which is used to pass the first void * parameter to the parameter class.
template <class T1>
class CComCreator
{
public:
// pv is function pointer pfnCreateInstance
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;
}
};
6.4 CComObjectCached and CComObjectNoLock
Template class CComObjectCached and CComObjectNoLock are fully functional class factories. CComCreator::CreateInstance creates an instance of them and calls their QueryInterface for an interface pointer which is supposed to be IClassFactory. They provide different implementation for the three IUnknown methods themselves, and inherit from CComClassFactory the standard class factory functionality.
The reason to use two different classes as class factories for DLL and EXE server is that class factories in EXE do not affect any reference count, while class factories in DLL affects both their own and the server¡¯s reference count.
6.5 CComClassFactory
class CComClassFactory :
public IClassFactory,
public CComObjectRootEx<CComGlobalsThreadModel>
{
public:
BEGIN_COM_MAP(CComClassFactory)
COM_INTERFACE_ENTRY(IClassFactory)
END_COM_MAP()
// Implementation of IClassFactory::CreateInstance
STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid, void** ppvObj)
{
ATLASSERT(m_pfnCreateInstance != NULL);
HRESULT hRes = E_POINTER;
if (ppvObj != NULL)
{
*ppvObj = NULL;
// can't ask for anything other than IUnknown when aggregating
if ((pUnkOuter != NULL) && !InlineIsEqualUnknown(riid))
{
ATLTRACE2(atlTraceCOM, 0,
_T("Asking for non IUnknown interface"));
hRes = CLASS_E_NOAGGREGATION;
}
else
hRes = m_pfnCreateInstance(pUnkOuter, riid, ppvObj);
}
return hRes;
}
STDMETHOD(LockServer)(BOOL fLock)
{
if (fLock)
_Module.Lock();
else
_Module.Unlock();
return S_OK;
}
void SetVoid(void* pv) // defined in CComObjectRootEx
{
m_pfnCreateInstance = (_ATL_CREATORFUNC*)pv;
}
_ATL_CREATORFUNC * m_pfnCreateInstance;
}; Notice that CComClassFactory::CreateInstance actually does not create the coclass instance by itself. It simply calls the stored function pointer m_pfnCreateInstance.
6.6 High-level abstraction
You can see that all the above-discussed class-factory-related classes ? CComCreator, CComObjectCached, CComObjectNoLoc and CComClassFactory are totally abstracted from details on how to create the coclass instance, which is handled by another coclass method pointed by another function pointer in the coclass¡¯s corresponding _ATL_OBJMAP_ENTRY structure entry in the object map.
A sophisticated class factory which knows NOTHING about how to assemble the coclass instance, sounds funny, isn¡¯t? But it is just because of such different levels of abstraction a design is made portable.
6.7 Customizing your class factory
From the definition of DECLARE_CLASSFACTORY you can see, if you use DECLARE_CLASSFACTORY_EX, you can replace the CComClassFactory with the name of a customized class factory which inherits from CComClassFactory.
Especially, the customized class factory can be template class CComClassFactory2<>, which takes a license-validating class as template parameter, so that the client can not create an instance of your coclass (such as an ActiveX) using this class factory until you supply a license file (*.lic).
To add one more level of abstraction, macro DECLARE_CLASSFACTORY2 does the same job:
#define DECLARE_CLASSFACTORY2(lic)
DECLARE_CLASSFACTORY_EX(CComClassFactory2<lic>)
6.8 Singleton class factory
If you replace the standard DECLARE_CLASSFACTORY macro with macro DECLARE_CLASSFACTORY_SINGLETON passing the name of your coclass, then your coclass will only have one instance created in spite of how many clients there are.
Remember if the server is a DLL the coclass instance is only unique within the same process. If the server is an EXE the coclass instance is only unique in the same machine.
7 Coclass Creation with DECLARE_AGGREGATABLE Macros
We have known that the ATL coclass created by the ATL Object Wizard is an abstract class which leaves out the IUnknown implementation. Now you can see that it is also abstracted from how aggregation support is provided. Some template classes are used to wrap around your coclass to add the aggregation support.
As we said before, CComCreator<>::CreateInstance returns an IClassFactory pointer to a class factory, whose CreateInstance calls another structure field pfnCreateInstance to create the coclass instance. Now function pointer pfnCreateInstance is filled by macro OBJECT_ENTRY with
class::_CreatorClass::CreateInstance,
There are three macros which defines _CreatorClass in three different ways. The default one DECLARE_AGGREGATABLE is defined in CComCoClass. If you choose ¡°Yes¡± as aggregation option in ATL object wizard this macro is what you get. If you choose ¡°No¡±, ATL will insert a DECLARE_NOT_AGGREGATABLE macro in your coclass definition, which will override the default one. If you choose ¡°Only¡±, DECLARE_ONLY_AGGREGATABLE will be inserted.
If you want to change the aggregation behaviour of your coclass after it is designed, simply change the macro.
7.1 DECLARE_AGGREGATABLE
DECLARE_AGGREGATABLE extends to
#define DECLARE_AGGREGATABLE(x) public:\
typedef CComCreator2< \
CComCreator< CComObject< x > >, CComCreator< CComAggObject< x > > \
> _CreatorClass;
The coclass to be created is passed as template parameter x. CComCreator< CComObject<x> > is used when the coclass is used as an aggregating outer object, while CComCreator< CComAggObject<x> > is used when the coclass is used as an inner object. Both of them are passed into CComCreator2 which can create either an outer or an inner object.
DllGetClassObject or CComModule::RegisterClassObjects calls CComCreator<>::CreateInstance for an IClassFactory pointer. Then the caller will call IClassFactory::CreateInstance, which is implemented by CComClassFactory, which simply directs the call to pfnCreateInstance:
hRes = m_pfnCreateInstance(pUnkOuter, riid, ppvObj);
Depending on whether the pUnkOuter is NULL, CComCreator2 will decide whether to use CComCreator< CComObject<x> > or CComCreator< CComAggObject<x> >.
7.2 DECLARE_NOT_AGGREGATABLE
#define DECLARE_NOT_AGGREGATABLE(x) public:\
typedef CComCreator2<
CComCreator< CComObject< x > >, CComFailCreator<CLASS_E_NOAGGREGATION>
> _CreatorClass;
7.3 DECLARE_ONLY_AGGREGATABLE
#define DECLARE_ONLY_AGGREGATABLE(x) public:\
typedef CComCreator2<
CComFailCreator<E_FAIL>, CComCreator< CComAggObject< x > > > _CreatorClass;
8 COM Category
A coclass can declare itself to belong to a COM category. A COM category contains a group of coclasses that have similar characteristics in some aspect. Each COM category has a GUID, and all categories are registered under System Registry entry HKCR/CLSID.
As we already know, one of the object map structure¡¯s filed is pfnGetCategoryMap, which is filled by macro OBJECT_ENTRY with
class::GetCategoryMap, \
During the registration process, _Module will call each coclass¡¯s GetCategoryMap to acquire all COM categories that this coclass supports, and put them under the coclass¡¯s HKCR\CLSID\<guid> entry.
CComCoClass<> defines a default implementation of this method which simply returns NULL, indicating that this coclass does not support any COM category. If your coclass supports one or more categories, you should declare each of them in a category map which should be placed in your coclass¡¯s class definition:
class ATL_NO_VTABLE CAny
{
...
BEGIN_CATEGORY_MAP(CAny)
IMPLEMENTED_CATEGORY(CATID_USELESS)
REQUIRED_CATEGORY(CATID_JUST_KIDDING)
END_CATEGORY_MAP()
...
}; These macros defines the implementation of method GetCategoryMap, and inside this method an array of _ATL_CATMAP_ENTRY structures, which contains two fields indicating the type and guid of the supported category:
// <atlcom.h>
#define BEGIN_CATEGORY_MAP(x)\
static const struct _ATL_CATMAP_ENTRY * GetCategoryMap() {\
static const struct _ATL_CATMAP_ENTRY pMap[] = {
#define IMPLEMENTED_CATEGORY( catid ) \
{ _ATL_CATMAP_ENTRY_IMPLEMENTED, &catid },
#define REQUIRED_CATEGORY( catid ) \
{ _ATL_CATMAP_ENTRY_REQUIRED, &catid },
#define END_CATEGORY_MAP()\
{ _ATL_CATMAP_ENTRY_END, NULL } };\
return( pMap ); }
// <atlbase.h>
#define _ATL_CATMAP_ENTRY_END 0
#define _ATL_CATMAP_ENTRY_IMPLEMENTED 1
#define _ATL_CATMAP_ENTRY_REQUIRED 2
struct _ATL_CATMAP_ENTRY
{
int iType;
const CATID* pcatid;
}; To register a COM category, you must do the following two things:
- Define the guid of the category in a header file, and include that header file wherever it is referred to:
// {BE3875E1-093B-11d6-9867-EF070508BA30}
static const GUID CATID_USELESS =
{ 0xbe3875e1, 0x93b, 0x11d6, { 0x98, 0x67, 0xef, 0x7, 0x5, 0x8, 0xba, 0x30 } };
// {FCFA7081-093B-11d6-9867-EF070508BA30}
static const GUID CATID_JUST_KIDDING =
{ 0xfcfa7081, 0x93b, 0x11d6, { 0x98, 0x67, 0xef, 0x7, 0x5, 0x8, 0xba, 0x30 } }; - Add the following lines in a server¡¯s RGS file. When the server is compiled and registered, the new entry for the category will be added into the System Registry:
HKCR
{
...
NoRemove ¡®Component Categories¡¯
{
{ BE3875E1-093B-11d6-9867-EF070508BA30 }
{
val 409 = s ¡®Useless Objects¡¯
}
...
}
9 Non-Creatable Coclass
Sometimes you have a coclass that you want the clients know about (it means that the coclass should have its registry entries), but do not want clients to directly create (it means that the pfnGetClassObject and pfnCreateInstance fields of the object map entry structure should be NULL). You declare such a coclass in the object map using macro OBJECT_ENTRY_NOT_CREATABLE instead of OBJECT_ENTRY:
#define OBJECT_ENTRY_NON_CREATEABLE(class)
{
&CLSID_NULL,
class::UpdateRegistry,
NULL,
NULL,
NULL,
0,
NULL,
class::GetCategoryMap,
class::ObjectMain
},
SeriousMoin v1 (koMoinMoin 1.0a4 Modified)