Creating a Template Interface in IDL with its Template Implementation in C++

Having the possibility of creating a template object, like a "CMap", with a COM interface for accessing remotly objects might come handy. Although, far from being trivial, it's possible to achieve with ATL. The "anti-VARIANT" programmers will cheer this opportunity. Here follow step-by-step instructions to untrangle this use-to-be problem.

I will use an object with one simple method as an exemple. This object is fully implemented in the source code. The method will return the minimum of two objects.

The interface will look like this:


interface IMinumumTYPE : IUnknown
{
	HRESULT Min( TYPE a, TYPE b, TYPE *pMim );
}

The steps to create this object are: 1) Implementing the object template. 2) Defining the interface in IDL using preprocessor macros. 3) Adding entry in the main object map.

Implementing the object template.


template<class TYPE, class T, const CLSID *pclsid, WCHAR wszModulename[] >
class CMinimumImpl : public CComObjectRootEx<CComMultiThreadModel>,
		     public CComCoClass< CMinimumImpl<TYPE,T,pclsid,wszModulename>, pclsid >,
		     public T
{

public:
	HRESULT __stdcall Min( TYPE a, TYPE b, TYPE *pMin )
	{
		*pMin = (a < b ? a : b );
		return S_OK;
	}
BEGIN_COM_MAP(CMinimumImpl)
	COM_INTERFACE_ENTRY_IID( __uuidof(T), T )
END_COM_MAP()

	static HRESULT WINAPI UpdateRegistry(BOOL bRegister)
	{	
		OLECHAR wszCLSID[45];
		StringFromGUID2( *pclsid, wszCLSID, 45 );

		_ATL_REGMAP_ENTRY regMap[] = { { L"ObjName", wszModulename },
																		{ L"ObjCLSID", wszCLSID },
																		{ NULL, NULL } };
		
		return _Module.UpdateRegistryFromResourceD( IDR_POLYREG, bRegister, regMap );
	}

};

There is only one tricky part: the registration of the object in the registry. To achieve this, we are using replacable parameters in the registry resource file (.RGS). That file is included with the project. Every instance of %ObjName% and %ObjCLSID% will be replaced with the content of the vars wszModulename and wszCLISD. The object support only one interface, it could support more but not needed in that case. The interface supported is given as a template parameter. We will see now how to define that interface in your IDL file.

Using prepocessor macros to define the interface in IDL.

Four information is needed in the template object to be able to create the interface ant register it. The name of the object, the object entry map (CLSID, object implementation), the interface definition and the library associated with the interface.

We define the interface in IDL as:



#define CPP_QUOTE( name ) cpp_quote( #name )

#define MINIMUM_INTERFACE( RIID, TYPE, NAME )						\
			[								\
				uuid( RIID),						\
				pointer_default(unique)					\
			]								\
			interface I##NAME : IUnknown					\
			{								\
				HRESULT Min( [in]TYPE a, [in]TYPE b, [out]TYPE *pMin );	\
			};								\
				cpp_quote( "#ifdef __cplusplus" )			\
				cpp_quote( "#ifdef __MINIMUM_H_" )			\
				CPP_QUOTE( extern OLECHAR g_wsz##NAME[]; )		\
				CPP_QUOTE( EXTERN_C const CLSID CLSID_##NAME; )		\
				CPP_QUOTE( typedef CMinimumImpl<TYPE )			\
				cpp_quote( "," )					\
				CPP_QUOTE( I##NAME )					\
				cpp_quote( "," )					\
				CPP_QUOTE( &CLSID_##NAME )				\
				cpp_quote( "," )					\
				CPP_QUOTE( g_wsz##NAME )				\
				CPP_QUOTE( > C##NAME;	)				\
				cpp_quote( "#endif" )					\
				cpp_quote( "#endif" )

The first part is the declaration of the interface, the second part write a typedef of the template implementation in the generated header file. Because the preprocessor will think the "," as argument separator, we must use multiple lines. You can see we are passing the TYPE, the Interface, the CLSID and the NAME of the object as template argument. The name is an extern var, it will be defined later, the interface is made as I appended to the name.

We can declare the interface for int and double as follow:


MINIMUM_INTERFACE( A91EBC05-87C0-11d2-9947-C26F54DDB3BE, int, MinimumInt )
MINIMUM_INTERFACE( A91EBC09-87C0-11d2-9947-C26F54DDB3BE, double, MinimumDouble )

The interfaces generated will be: IMinimumDouble and IMinimumInt.

But it's not over, we must declare the name which will be written in the registry.

Here are the macros:


#define BEGIN_NAME_MAP() cpp_quote( "#define NAME_MAP() \\" )
#define END_NAME_MAP()    cpp_quote(" ")
#define NAME_ENTRY(NAME)  CPP_QUOTE( OLECHAR g_wsz##NAME[] = L#NAME;\\ )

The macro BEGIN_NAME_MAP define a macro in the generated header file. Calling that macro once in a .cpp file will define the name for all your objects in the map. So we must add:


BEGIN_NAME_MAP()
	NAME_ENTRY( MinimumInt )
	NAME_ENTRY( MinimumDouble )
END_NAME_MAP()

Now we will generate another macro in the genarated header file to add all objects in the object map.


#define BEGIN_OBJECT_MAP()	cpp_quote( "#define OBJECT_MAP() \\" )
#define END_OBJECT_MAP()	cpp_quote(" " )
#define OBJECT_ENTRY(NAME)	CPP_QUOTE( OBJECT_ENTRY( CLSID_##NAME, C##NAME ) \\ )

We use it in the IDL file like that:


BEGIN_OBJECT_MAP()
	OBJECT_ENTRY( MinimumInt )
	OBJECT_ENTRY( MinimumDouble )
END_OBJECT_MAP()

The last step is the library declaration:


#define BEGIN_LIBRARY()								\
			[							\
				uuid(0968E542-87B7-11D2-9947-C26F54DDB3BE),	\
				version(1.0),					\
				helpstring("codeguru 1.0 Type Library")		\
			]							\
			library CODEGURULib					\
			{							\
				importlib("stdole32.tlb");			\
				importlib("stdole2.tlb");


#define END_LIBRARY() };

#define LIBRARY_ENTRY( CLSID, NAME )				\
			[					\
				uuid(CLSID)			\
			]					\
			coclass NAME				\
			{					\
				[default] interface I##NAME;	\
			};

It look like this for our two objects:


BEGIN_LIBRARY()
	LIBRARY_ENTRY( A91EBC0E-87C0-11d2-9947-C26F54DDB3BE, MinimumInt )
	LIBRARY_ENTRY( A91EBC11-87C0-11d2-9947-C26F54DDB3BE, MinimumDouble )
END_LIBRARY()

Adding entry in the main object map.

In your main project file, you must add two lines:


NAME_MAP()  //a macro we have generated 

BEGIN_OBJECT_MAP(ObjectMap)
	OBJECT_MAP()  another macro we have generated
END_OBJECT_MAP()

Conclusion.

The concept is kind is simple viewing by this angle. But be careful, as more extragavancies usage of the problem the complexity rise as well. Fortunatly for us, in the end, the versality usage of the whole would be much greater.

Download source - 12KB



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds