//
you're reading...
COM, Programming Issues/Tips

Noncreatable COM Objects – Part 3.

1. Introduction.

1.1 In part 1 of this series of articles, I expounded the technique of developing a noncreatable COM coclass by omitting the use of the OBJECT_ENTRY_AUTO() macro.

1.2 In part 2 I wrote about using an alternative technique of using the OBJECT_ENTRY_NON_CREATEABLE_EX_AUTO() macro.

1.3 Next in line is a non-standard technique that I personally suggest : by using a customized class factory.

2. A Customized Class Factory.

2.1 In ATL, a custom class factory can be created by creating a class which derives from CComClassFactory as a base class and overriding the CreateInstance() method.

2.2 The ATL class must then declare the DECLARE_CLASSFACTORY_EX() to indicate its intension to use such a customized class factory.

2.3 An ATL class which declares the DECLARE_CLASSFACTORY_EX() macro must also declare the OBJECT_ENTRY_AUTO() macro so that information of the class is registered as per normal and that its class factory information be recorded as part of an _ATL_OBJMAP_ENTRY record in the Object Map.

2.4 Under normal circumstances, where the ATL class is creatable, the DECLARE_CLASSFACTORY_EX() and the OBJECT_ENTRY_AUTO() macros ensure that the custom class factory’s CreateInstance() method is called whenever a standard API like CoCreateInstance() is used to instantiate a coclass.

3. General Strategy for Developing a Noncreatable Coclass which uses a Custom Class Factory.

3.1 Towards the development of a noncreatable coclass which uses a custom class factory, the following is the general strategy :

  • The overall objective is to develop a coclass which can only be created via an object creator.
  • We want the coclass’ apartment model to not be dependent on the apartment model of the object creator.
  • Information on the coclass is to be registered into the registry. This will make publicly available information on the apartment model used by the coclass.
  • The purpose of the object creator is to create an instance of the coclass by standard COM creation APIs (e.g. CoCreateInstance()). The COM sub-system will thus ensure that the coclass is created in the appropriate apartment.
  • The custom class factory is used to determine that it is the object creator which is attempting to create an instance of the coclass and hence to proceed with the instantiation.
  • If the custom class factory determines that the object creator was not used to create an instance of the coclass, it will not perform the instantiation and will return an error code.

4. Sample Custom ATL Class Factory.

4.1 Shown below is an IDL listing for a coclass named NonCreatableClass03 and its associated default interface INonCreatableClass03 :

[
	object,
	uuid(15900E1D-322B-497C-96B1-1C83B5CA1A0B),
	dual,
	nonextensible,
	helpstring("INonCreatableClass03 Interface"),
	pointer_default(unique)
]
interface INonCreatableClass03 : IDispatch
{
};

[
	uuid(C5B2DD02-F668-49F4-8848-0D88E1372DB8),
	helpstring("NonCreatableClass03 Class")
]
coclass NonCreatableClass03
{
	[default] interface INonCreatableClass03;
};

The definitions are very simple. In fact, INonCreatableClass03 does not even expose any properties or methods. These are not important for the demonstration of coclass instantiation.

4.2 Shown below is a sample ATL class (CNonCreatableClass03, the ATL implementation for the NonCreatableClass03 coclass) which uses a custom class factory (CNonCreatableClass03CF) :

// NonCreatableClass03.h : Declaration of the CNonCreatableClass03

#pragma once
#include "resource.h"       // main symbols

#include "NonCreatableObjectsServer_i.h"
#include "ObjectCreator_NonCreatableClass03.h"

#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the \
Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA \
to force ATL to support creating single-thread COM object's and allow use of it's single-threaded \
COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only \
threading model supported in non DCOM Windows CE platforms."
#endif

// Forward declaration.
class CNonCreatableClass03CF;

// CNonCreatableClass03

class ATL_NO_VTABLE CNonCreatableClass03 :
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CNonCreatableClass03, &CLSID_NonCreatableClass03>,
	public IDispatchImpl<INonCreatableClass03, &IID_INonCreatableClass03, &LIBID_NonCreatableObjectsServerLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
	CNonCreatableClass03()
	{
	}

	~CNonCreatableClass03()
	{
	}

DECLARE_REGISTRY_RESOURCEID(IDR_NONCREATABLECLASS03)

// Declare a private class factory.
DECLARE_CLASSFACTORY_EX(CNonCreatableClass03CF)

BEGIN_COM_MAP(CNonCreatableClass03)
	COM_INTERFACE_ENTRY(INonCreatableClass03)
	COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

	DECLARE_PROTECT_FINAL_CONSTRUCT()

	HRESULT FinalConstruct()
	{
		return S_OK;
	}

	void FinalRelease()
	{
	}

public:
};

OBJECT_ENTRY_AUTO(__uuidof(NonCreatableClass03), CNonCreatableClass03)

class CNonCreatableClass03CF : public CComClassFactory
{
public :

	// IClassFactor implementation.
	STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid, void** ppvObj)
	{
		if (CObjectCreator_NonCreatableClass03::ObjectCreatorInUse())
		{
		  CComObject<CNonCreatableClass03>* pCNonCreatableClass03 = NULL;

		  CComObject<CNonCreatableClass03>::CreateInstance(&pCNonCreatableClass03);

		  if (pCNonCreatableClass03)
		  {
			  return pCNonCreatableClass03 -> QueryInterface(riid, ppvObj);
		  }
		}

		return CLASS_E_CLASSNOTAVAILABLE;
	}
};

The following are significant points regarding the CNonCreatableClass03 and the CNonCreatableClass03CF classes :

  • The CNonCreatableClass03 class uses the DECLARE_CLASSFACTORY_EX() macro to declare that it uses the CNonCreatableClass03CF class as its class factory class.
  • The OBJECT_ENTRY_AUTO() macro is declared for the CNonCreatableClass03 class as per normal.
  • The CNonCreatableClass03CF class derives from CComClassFactory and overrides the CreateInstance() method.
  • The CNonCreatableClass03CF::CreateInstance() code uses a public static method of another class CObjectCreator_NonCreatableClass03 which will be touched on next.

4.3 Shown below is an IDL listing for a ObjectCreator_NonCreatableClass03 coclass with its default interface IObjectCreator_NonCreatableClass03 :

[
	object,
	uuid(F69EFC5A-6765-41F7-BC51-1BE6121CC4E9),
	dual,
	nonextensible,
	helpstring("IObjectCreator_NonCreatableClass03 Interface"),
	pointer_default(unique)
]
interface IObjectCreator_NonCreatableClass03 : IDispatch
{
	[id(1), helpstring("method CreateObject")] HRESULT CreateObject([out,retval] IUnknown** ppUnkReceiver);
};	

[
	uuid(C15E95E2-446B-4747-806F-6B57E05A9D64),
	helpstring("ObjectCreator_NonCreatableClass03 Class")
]
coclass ObjectCreator_NonCreatableClass03
{
	[default] interface IObjectCreator_NonCreatableClass03;
};

The ObjectCreator_NonCreatableClass03 coclass is used to instantiate the NonCreatableClass03 coclass. It exposes the IObjectCreator_NonCreatableClass03 interface which contains only one method named CreateObject() the purpose of which is to perform the instantiation of the NonCreatableClass03 coclass and then return a pointer to its IUnknown interface.

4.4 Shown below is the full ATL class code for CObjectCreator_NonCreatableClass03, the

// ObjectCreator_NonCreatableClass03.h : Declaration of the CObjectCreator_NonCreatableClass03

#pragma once
#include "resource.h"       // main symbols

#include "NonCreatableObjectsServer_i.h"

#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Single-threaded COM objects are not properly supported on Windows CE platform, \
such as the Windows Mobile platforms that do not include full DCOM support. \
Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating \
single-thread COM object's and allow use of it's single-threaded COM object implementations. \
The threading model in your rgs file was set to 'Free' as that is the only threading model \
supported in non DCOM Windows CE platforms."
#endif

// CObjectCreator_NonCreatableClass03

class ATL_NO_VTABLE CObjectCreator_NonCreatableClass03 :
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CObjectCreator_NonCreatableClass03, &CLSID_ObjectCreator_NonCreatableClass03>,
	public IDispatchImpl<IObjectCreator_NonCreatableClass03,
		&IID_IObjectCreator_NonCreatableClass03,
		&LIBID_NonCreatableObjectsServerLib,
		/*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
	CObjectCreator_NonCreatableClass03()
	{
	}

	~CObjectCreator_NonCreatableClass03()
	{
	}

DECLARE_REGISTRY_RESOURCEID(IDR_OBJECTCREATOR_NONCREATABLECLASS03)

BEGIN_COM_MAP(CObjectCreator_NonCreatableClass03)
	COM_INTERFACE_ENTRY(IObjectCreator_NonCreatableClass03)
	COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

	DECLARE_PROTECT_FINAL_CONSTRUCT()

	HRESULT FinalConstruct()
	{
		return S_OK;
	}

	void FinalRelease()
	{
	}

public:

	STDMETHOD(CreateObject)(IUnknown** ppUnkReceiver)
	{
		EnterCriticalSection(&m_csLockObjectCreator);
		m_bObjectCreatorInUse = true;

		CComPtr<INonCreatableClass03>	spINonCreatableClass03 = NULL;
		HRESULT							hrRet = S_OK;

		hrRet = ::CoCreateInstance
			(
				CLSID_NonCreatableClass03,
				NULL,
				CLSCTX_INPROC_SERVER,
				IID_INonCreatableClass03,
				(LPVOID*)&spINonCreatableClass03
			);

		if (!SUCCEEDED(hrRet))
		{
			m_bObjectCreatorInUse = false;
			LeaveCriticalSection(&m_csLockObjectCreator);
			return hrRet;
		}

		hrRet = spINonCreatableClass03 -> QueryInterface(IID_IUnknown, (LPVOID*)ppUnkReceiver);

		m_bObjectCreatorInUse = false;
		LeaveCriticalSection(&m_csLockObjectCreator);

		return hrRet;
	}

	static void WINAPI ObjectMain(bool bStarting)
	{
		if (bStarting)
		{
			InitializeCriticalSection(&m_csLockObjectCreator);
			m_bObjectCreatorInUse = false;
		}
		else
		{
			DeleteCriticalSection(&m_csLockObjectCreator);
		}
	}

	static bool ObjectCreatorInUse()
	{
		return m_bObjectCreatorInUse;
	}

	static CRITICAL_SECTION	m_csLockObjectCreator;
	static bool				m_bObjectCreatorInUse;
};

OBJECT_ENTRY_AUTO(__uuidof(ObjectCreator_NonCreatableClass03), CObjectCreator_NonCreatableClass03)

The following are the important points summarizing the CObjectCreator_NonCreatableClass03 class :

  • It declares 2 static objects m_csLockObjectCreator (a CRITICAL_SECTION object) and m_bObjectCreatorInUse (a boolean).
  • These static variables together control the instantiation of the CNonCreatableClass03 class.
  • At runtime, there could be multiple instances of the ObjectCreator_NonCreatableClass03 coclass. Each instance could be used to call the CreateObject() method.
  • The CreateObject() method internally uses the standard CoCreateInstance() API to perform the instantiation of the NonCreatableClass03 coclass.
  • When CoCreateInstance() is used, CNonCreatableClass03CF::CreateInstance() will eventually be called.
  • CNonCreatableClass03CF being the class factory for the NonCreatableClass03 coclass, will be used whenever CoCreateInstance() is called whether or not ObjectCreator_NonCreatableClass03 was used to perform the creation process.
  • This being the case, CNonCreatableClass03CF needs to cooperate with CObjectCreator_NonCreatableClass03 to ensure that whenever CNonCreatableClass03CF::CreateInstance() is called, there is a way to confirm that CObjectCreator_NonCreatableClass03::CreateObject() was concurrently being called.
  • This is where the CObjectCreator_NonCreatableClass03::m_bObjectCreatorInUse boolean variable comes into play. It is set to true when CObjectCreator_NonCreatableClass03::CreateObject() is called and then set to false just before the function exits.
  • The public static CObjectCreator_NonCreatableClass03::ObjectCreatorInUse() method returns the current value for m_bObjectCreatorInUse and it is used by the CNonCreatableClass03CF::CreateInstance() method to determine whether the CNonCreatableClass03 class is to be instantiated.
  • Now, because multiple CObjectCreator_NonCreatableClass03 objects could be concurrently running each of which is able to set/reset the value of the static (and hence global) m_bObjectCreatorInUse boolean variable, we use the static CObjectCreator_NonCreatableClass03::m_csLockObjectCreator CRITICAL_SECTION object to control write-access to m_bObjectCreatorInUse.
  • The 2 static variables ensure that whenever IObjectCreator_NonCreatableClass03::CreateObject() method was used to perform instantiation of the NonCreatableClass03 coclass, the instantiation process will be successful. An error code of CLASS_E_CLASSNOTAVAILABLE will be returned otherwise.

4.5 As a good use case for the ObjectMain() method, the CObjectCreator_NonCreatableClass03 class provides a definition :

	static void WINAPI ObjectMain(bool bStarting)
	{
		if (bStarting)
		{
			InitializeCriticalSection(&m_csLockObjectCreator);
			m_bObjectCreatorInUse = false;
		}
		else
		{
			DeleteCriticalSection(&m_csLockObjectCreator);
		}
	}

It initializes the m_csLockObjectCreator CRITICAL_SECTION object when the class is being initialized and deletes it when the class is being uninitialized.

5. In Conclusion.

5.1 I believe that the use of a custom class factory in conjunction with an object creator to control the creation process of a noncreatable coclass is by far the best option.

5.2 At the price of a small amount of complexity, the apartment model of the noncreatable coclass (as intended by design) is enforced and its ObjectMain() method, if supplied, will be invoked.

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: