Some Advanced ATL Development Skills for Visual C++ Developers - CodeProject

:

Introduction

In this paper, we will discuss some advanced skills for ATL COM development. We give a method for how to implement a COM Object using ATL Library which has only-one runtime instance, how to implement a COM Object which does not register System Registry.

How to Design an ATL COM Object Which Has Only One Instance for One Process

At first, we create an ATL COM Dynamic Link Library Project Using Visual Studio 2013 Application Wizard. In this paper, this project is named “AdvATLProj”.

https://www.codeproject.com/KB/atl/886674/image001.png

After this project was created, we use wizard to add one COM Object to this project, see the picture below:

https://www.codeproject.com/KB/atl/886674/image002.png

Be sure the “ProgID” must be filled. Press “Finish” button. We obtained an ATL Project which includes one COM Object. The definition of class CObjManager is as below:

// CObjManager

class ATL_NO_VTABLE CObjManager :
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CObjManager, &CLSID_ObjManager>,
	public IDispatchImpl<IObjManager, &IID_IObjManager,
		&LIBID_AdvATLProjLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
	CObjManager()
	{
	}
DECLARE_REGISTRY_RESOURCEID(IDR_OBJMANAGER)
BEGIN_COM_MAP(CObjManager)
	COM_INTERFACE_ENTRY(IObjManager)
	COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()

	HRESULT FinalConstruct()
	{
		return S_OK;
	}

	void FinalRelease()
	{
	}

public:

}

OBJECT_ENTRY_AUTO(__uuidof(ObjManager), CObjManager)

The code above was generated by standard wizard. In Visual Studio IDE, click mouse on “OBJECT_ENTRY_AUTO” and then press “F12”, we go to the definition of “OBJECT_ENTRY_AUTO”:

#define OBJECT_ENTRY_AUTO(clsid, class) \
	__declspec(selectany) ATL::_ATL_OBJMAP_CACHE __objCache__##class = { NULL, 0 }; \
	const ATL::_ATL_OBJMAP_ENTRY_EX __objMap_##class = {&clsid, class::UpdateRegistry,
	class::_ClassFactoryCreatorClass::CreateInstance, class::_CreatorClass::CreateInstance,
	&__objCache__##class, class::GetObjectDescription, class::GetCategoryMap, class::ObjectMain }; \
	extern "C" __declspec(allocate("ATL$__m"))
	__declspec(selectany) const ATL::_ATL_OBJMAP_ENTRY_EX* const __pobjMap_##class = &__objMap_##class; \
	OBJECT_ENTRY_PRAGMA(class)

From ATL source code, the structure ATL_OBJMAP_ENTRY_EX is defined as below:

typedef _ATL_OBJMAP_ENTRY110 _ATL_OBJMAP_ENTRY_EX;
struct _ATL_OBJMAP_ENTRY110
{
	const CLSID* pclsid;
	HRESULT (WINAPI *pfnUpdateRegistry)(_In_ BOOL bRegister);
	_ATL_CREATORFUNC* pfnGetClassObject;
	_ATL_CREATORFUNC* pfnCreateInstance;
	_ATL_OBJMAP_CACHE* pCache;
	_ATL_DESCRIPTIONFUNC* pfnGetObjectDescription;
	_ATL_CATMAPFUNC* pfnGetCategoryMap;
	HRESULT WINAPI RevokeClassObject()
	{
		ATLASSUME(pCache != NULL);
		if (pCache->dwRegister == 0)
			return S_OK;

		return CoRevokeClassObject(pCache->dwRegister);
	}

	HRESULT WINAPI RegisterClassObject(
		_In_ DWORD dwClsContext,
		 _In_ DWORD dwFlags)
	{
		 ATLASSUME(pCache != NULL);
		 IUnknown* p = NULL;
 		 if (pfnGetClassObject == NULL)
 			return S_OK;

		 HRESULT hRes = pfnGetClassObject(pfnCreateInstance, __uuidof(IUnknown), (LPVOID*) &p);
 		 if (SUCCEEDED(hRes))
			hRes = CoRegisterClassObject(*pclsid, p, dwClsContext, dwFlags, &pCache->dwRegister);
		 if (p != NULL)
			 p->Release();
		 return hRes;

	}
// Added in ATL 3.0
	void (WINAPI *pfnObjectMain)(_In_ bool bStarting);
};

So, from the definition of the MACRO OBJECT_ENTRY_AUTO, we only need to add a static member function of class CObjManager:

static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv);

and replace the following codes of the MACRO OBJECT_ENTRY_AUTO:

const ATL::_ATL_OBJMAP_ENTRY_EX __objMap_##class =
{&clsid, class::UpdateRegistry, class::_ClassFactoryCreatorClass::CreateInstance,
class::_CreatorClass::CreateInstance, &__objCache__##class, class::GetObjectDescription,
class::GetCategoryMap, class::ObjectMain }; \

with:

const ATL::_ATL_OBJMAP_ENTRY_EX __objMap_##class =
{&clsid, class::UpdateRegistry, class::_ClassFactoryCreatorClass::CreateInstance,
class::CreateInstance, &__objCache__##class, class::GetObjectDescription,
class::GetCategoryMap, class::ObjectMain }; \

Thus, we define:

#define TANGRAM_OBJECT_ENTRY_AUTO(clsid, class) \
	__declspec(selectany) ATL::_ATL_OBJMAP_CACHE __objCache__##class = { NULL, 0 }; \
	const ATL::_ATL_OBJMAP_ENTRY_EX __objMap_##class = {&clsid, class::UpdateRegistry,
	class::_ClassFactoryCreatorClass::CreateInstance, class::CreateInstance,
	&__objCache__##class, class::GetObjectDescription,
	class::GetCategoryMap, class::ObjectMain }; \
	extern "C" __declspec(allocate("ATL$__m")) __declspec(selectany)
	const ATL::_ATL_OBJMAP_ENTRY_EX* const __pobjMap_##class = &__objMap_##class; \
	OBJECT_ENTRY_PRAGMA(class)

And then use:

TANGRAM_OBJECT_ENTRY_AUTO(__uuidof(ObjManager), CObjManager)

Replace:

OBJECT_ENTRY_AUTO(__uuidof(ObjManager), CObjManager)

at the bottom of file “ObjManager.h”.

Listed below are the complete steps:

Step 1: Open file “stdafx.h”, add the following code block at the bottom of this file:

#include <atlstr.h>
#include "AdvATLProj_i.h"

#define TANGRAM_OBJECT_ENTRY_AUTO(clsid, class) \
	__declspec(selectany) ATL::_ATL_OBJMAP_CACHE __objCache__##class = { NULL, 0 }; \
	const ATL::_ATL_OBJMAP_ENTRY_EX __objMap_##class = {&clsid, class::UpdateRegistry,
	class::_ClassFactoryCreatorClass::CreateInstance, class::CreateInstance,
	&__objCache__##class, class::GetObjectDescription, class::GetCategoryMap, class::ObjectMain }; \
	extern "C" __declspec(allocate("ATL$__m"))
	__declspec(selectany) const ATL::_ATL_OBJMAP_ENTRY_EX* const __pobjMap_##class = &__objMap_##class; \
	OBJECT_ENTRY_PRAGMA(class)

Step 2: Open file “dllmain.h”, add the following code:

public :
	CAdvATLProjModule();
	IObjManager*    m_pObjManager;

to the class CObjManager.

Step 3: Open “dllmain.cpp”, add the following code:

CAdvATLProjModule::CAdvATLProjModule()
{
	m_pObjManager = NULL;
}	

Step 4: Open file “ObjManager.h”, add a public static member function to class CObjManager:

static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv);

at the bottom of file “ObjManager.h”, comment the following line:

OBJECT_ENTRY_AUTO(__uuidof(ObjManager), CObjManager)

and then add the following line:

TANGRAM_OBJECT_ENTRY_AUTO(__uuidof(ObjManager), CObjManager)

Step 5: Open file “ObjManager.cpp”, add the implement of function "CreateInstance" as follows:

HRESULT CObjManager::CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
{
	HRESULT hr = S_OK;
	if (_AtlModule.m_pObjManager == NULL)
		hr = _CreatorClass::CreateInstance(pv, riid, (void**)&_AtlModule.m_pObjManager);
	return _AtlModule.m_pObjManager->QueryInterface(riid, (void**)ppv);
}	

When Step 5 is finished, we have finished all the steps.

Test of Object “ObjManager”

We assume that you have finished all the steps above and you have built the project “AdvATLProj”. “Test” project is a very simple MFC Dialog Application, when you press the button “Test ObjManager”, the following code will be executed:

void CTestDlg::OnBnClickedButton1()
{
	CComPtr<IDispatch> pObjManager1;
	pObjManager1.CoCreateInstance(L"AdvAtlProj.ObjManager.1");
	CComPtr<IDispatch> pObjManager2;
	pObjManager2.CoCreateInstance(L"AdvAtlProj.ObjManager.1");
	ASSERT(pObjManager1.p == pObjManager2.p);
}	

From this sample, you can see the COM Object “ObjManager” always has only one instance.

Use “ObjManager” as Object Manager

When we add an ATL COM Object to “AdvATLProj” or other ATL project, a COM Object was defined, some object information will be add to IDL file, in default case, a rgs file will be generated and added to project, when you build this project, new Object Information will be written to System Registry, in a large software system, there may be many COM Objects, if every COM Object must register to System Registry, the System Registry will become “very heavily”, this is certainly not what we want.

Add a simple ATL Object by using wizard, see below:

https://www.codeproject.com/KB/atl/886674/image003.png

https://www.codeproject.com/KB/atl/886674/image004.png

https://www.codeproject.com/KB/atl/886674/image005.png

In Resource View, find resource “IDR_TESTATLObj”, and delete this resource, Open file “TestAtlObj.h”, delete the following lines:

public CComCoClass<CTestAtlObj, &CLSID_TestAtlObj>,

DECLARE_REGISTRY_RESOURCEID(IDR_TESTATLOBJ)

OBJECT_ENTRY_AUTO(__uuidof(TestAtlObj), CTestAtlObj)

Open file “AdvAtlProj.idl”, delete the following block:

[
	uuid(5A2F3F65-1D44-4DFA-B01C-59E0E2C0E74B
]
coclass TestAtlObj
{
	[default] interface ITestAtlObj;
}

Then the class CTestAtlObj becomes like below:

// CTestAtlObj

class ATL_NO_VTABLE CTestAtlObj :
	public CComObjectRootEx<CComSingleThreadModel>,
	public IDispatchImpl<ITestAtlObj, &IID_ITestAtlObj,
		&LIBID_AdvATLProjLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
	CTestAtlObj()
	{
	}
BEGIN_COM_MAP(CTestAtlObj)
	COM_INTERFACE_ENTRY(IObjManager)
	COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()

	HRESULT FinalConstruct()
	{
		return S_OK;
	}

	void FinalRelease()
	{
	}

public:

}

Because we have deleted the following line:

DECLARE_REGISTRY_RESOURCEID(IDR_TESTATLOBJ)

this Object will not register to System Registry, so we can’t create this object using regular COM Method. Then how can we create an instance of object “TestAtlObj”?

We do this work as follows:

Step 1: Define an enum in file “AdvAtlProj.idl”:

typedef
[
	helpstring("")
]
enum AdvAtlObjType
{
	TestAtlObj = 0x00000000,
	OtherAtlObj = 0x00000001,

}AdvAtlObjType;

Step 2: Add method to “ObjManager” by using ATL Wizard:

https://www.codeproject.com/KB/atl/886674/image006.png

Step 3: Open file “ObjManager.cpp”, we find method implementation:

STDMETHODIMP CObjManager::CreateObject(AdvAtlObjType nType, IDispatch** ppRetObj)
{
	return S_OK;
}

Modify this method as below:

STDMETHODIMP CObjManager::CreateObject(AdvAtlObjType nType, IDispatch** ppRetObj)
{
	switch (nType)
	{
	case AdvAtlObjType::TestAtlObj:
		{
			CTestAtlObj* pObj = new CComObject<CTestAtlObj>();
			*ppRetObj = pObj;
			(*ppRetObj)->AddRef();
		}
		break;
	case AdvAtlObjType::OtherAtlObj:
  		break;
	}
	return S_OK;
}

Similarly, other Object can treated as CTestAtlObj, you can modify the definition of enum AdvAtlObjType” to support more than one Objects.

Test of Object “CTestAtlObj”

In project “AdvAtlProj”, find file "AdvATLProj_i.h", and copy this file to the “Test” Project, build this project and run it. Press button “Test TestAtlObj”, the following code will be executed:

void CTestDlg::OnBnClickedButton2()
{
	CComPtr<IObjManager> pObjManager1;
	pObjManager1.CoCreateInstance(L"AdvAtlProj.ObjManager.1");
	if (pObjManager1)
	{
		CComPtr<IDispatch> pTestObj;
		pObjManager1->CreateObject(AdvAtlObjType::TestAtlObj,&pTestObj);
		if (pTestObj)
		{
			CComQIPtr<ITestAtlObj> pObj(pTestObj);
			if (pObj)
				pObj->test();
		}
	}
}

A Complete Example of “Only-One Instance” ATL COM Object

We give such an example here.