Click here to Skip to main content
15,888,600 members
Articles / Programming Languages / C

COM in plain C, Part 4

Rate me:
Please Sign up or sign in to vote.
4.95/5 (31 votes)
15 May 2006CPOL15 min read 87.1K   2.2K   94  
Make a COM object with multiple interfaces, in C.
// IExampleExe.c demonstrates how to put a COM component in its own
// EXE. In that way, our object runs in a different process (address
// space) as someone who uses our COM component. This allows an extra
// bit of crash protection to that client application.

#include <windows.h>
#include <objbase.h>
#include "IExampleExe.h"

// Comment this out if you want this COM component to continue running
// even after all apps are finished using it
#define ALLOW_UNLOADING



// A count of how many objects our EXE has created (by some
// app calling our IClassFactory object's CreateInstance())
// which have not yet been Release()'d by the app
static DWORD		OutstandingObjects;

// A count of how many apps have locked our EXE via calling our
// IClassFactory object's LockServer()
static DWORD		LockCount;

// Where I store a pointer to my type library's TYPEINFO
static ITypeInfo	*MyTypeInfo;






// The IExampleExe object ////////////////////////////////////////////////////////////

// In our .H file, we use a macro which defines our IExampleExe struct
// as so:
//
// typedef struct {
//    IExampleExeVtbl  *lpVtbl;
// } IExampleExe;
//
// In other words, the .H file defines our IExampleExe to have nothing
// but a pointer to its VTable. And of course, every COM object must
// start with a pointer to its VTable.
//
// But we actually want to add some more members to our IExampleExe.
// We just don't want any app to be able to know about, and directly
// access, those members. So here we'll define a MyRealIExampleExe that
// contains those extra members. The app doesn't know that we're
// really allocating and giving it a MyRealIExampleExe object. We'll
// lie and tell it we're giving a plain old IExampleExe. That's ok
// because a MyRealIExampleExe starts with the same VTable pointer.
//
// We add a DWORD reference count so that this IExampleExe
// can be allocated (which we do in our IClassFactory object's
// CreateInstance()) and later freed. And, we have an extra
// BSTR (pointer) string, which is used by some of the functions we'll
// add to IExampleExe
typedef struct {
	IExampleExeVtbl	*lpVtbl;
	DWORD			count;
	BSTR			string;
} MyRealIExampleExe;

// Here are IExampleExe's functions.
//
// Every COM object's interface must have the 3 functions QueryInterface(),
// AddRef(), and Release().
//
// I also chose to add 2, extra functions to IExampleExe, which a program
// will call with the names GetString and SetString.

// IExampleExe's QueryInterface()
static HRESULT STDMETHODCALLTYPE QueryInterface(IExampleExe *this, REFIID vTableGuid, void **ppv)
{
	// Check if the GUID matches IExampleExe VTable's GUID. We gave the C variable name
	// IID_IExampleExe to our VTable GUID. We can use an OLE function called
	// IsEqualIID to do the comparison for us. Also, if the caller passed a
	// IUnknown GUID, then we'll likewise return the IExampleExe, since it can
	// masquerade as an IUnknown object too. Finally, if the called passed a
	// IDispatch GUID, then we'll return the IExampleExe, since it can masquerade
	// as an IDispatch too
	if (!IsEqualIID(vTableGuid, &IID_IUnknown) && !IsEqualIID(vTableGuid, &IID_IExampleExe) && !IsEqualIID(vTableGuid, &IID_IDispatch))
	{
      // We don't recognize the GUID passed to us. Let the caller know this,
      // by clearing his handle, and returning E_NOINTERFACE.
      *ppv = 0;
      return(E_NOINTERFACE);
	}

	// Fill in the caller's handle
	*ppv = this;

	// Increment the count of callers who have an outstanding pointer to this object
	this->lpVtbl->AddRef(this);

	return(NOERROR);
}

// IExample's AddRef()
static ULONG STDMETHODCALLTYPE AddRef(IExampleExe *this)
{
	// Increment IExampleExe's reference count, and return the updated value.
	// NOTE: We have to typecast to gain access to any data members. These
	// members are not defined in our .H file (so that an app can't directly
	// access them). Rather they are defined only above in our MyRealIExampleExe
	// struct. So typecast to that in order to access those data members
	return(++((MyRealIExampleExe *)this)->count);
}

// IExample's Release()
static ULONG STDMETHODCALLTYPE Release(IExampleExe *this)
{
	// Decrement IExampleExe's reference count. If 0, then we can safely free
	// this IExampleExe now
	if (--((MyRealIExampleExe *)this)->count == 0)
	{
		if (((MyRealIExampleExe *)this)->string) SysFreeString(((MyRealIExampleExe *)this)->string);
		GlobalFree(this);

		// Decrement the count of outstanding objects
		InterlockedDecrement(&OutstandingObjects);

		// If we can unload this EXE now, then post a WM_QUIT message so WinMain
		// will drop out of the loop and this EXE terminates
		if (!DllCanUnloadNow()) PostMessage(0, WM_QUIT, 0, 0);

		return(0);
	}
	return(((MyRealIExampleExe *)this)->count);
}

// ================== The standard IDispatch functions

// This is just a helper function for the IDispatch functions below
static HRESULT loadMyTypeInfo(void)
{
	register HRESULT	hr;
	LPTYPELIB			pTypeLib;

	// Load our type library and get a ptr to its TYPELIB. Note: This does an
	// implicit pTypeLib->lpVtbl->AddRef(pTypeLib)
	if (!(hr = LoadRegTypeLib(&CLSID_TypeLib, 1, 0, 0, &pTypeLib)))
	{
		// Get Microsoft's generic ITypeInfo, giving it our loaded type library. We only
		// need one of these, and we'll store it in a global Tell Microsoft this is for
		// our IExampleExe's VTable, by passing that VTable's GUID
		if (!(hr = pTypeLib->lpVtbl->GetTypeInfoOfGuid(pTypeLib, &IID_IExampleExe, &MyTypeInfo)))
		{
			// We no longer need the ptr to the TYPELIB now that we've given it
			// to Microsoft's generic ITypeInfo. Note: The generic ITypeInfo has done
			// a pTypeLib->lpVtbl->AddRef(pTypeLib), so this TYPELIB ain't going away
			// until the generic ITypeInfo does a pTypeLib->lpVtbl->Release too
			pTypeLib->lpVtbl->Release(pTypeLib);

			// Since caller wants us to return our ITypeInfo pointer,
			// we need to increment its reference count. Caller is
			// expected to Release() it when done
			MyTypeInfo->lpVtbl->AddRef(MyTypeInfo);
		}
	}

	return(hr);
}

// IExampleExe's GetTypeInfoCount()
static ULONG STDMETHODCALLTYPE GetTypeInfoCount(IExampleExe *this, UINT *pCount)
{
	*pCount = 1;
	return(S_OK);
}

// IExampleExe's GetTypeInfo()
static ULONG STDMETHODCALLTYPE GetTypeInfo(IExampleExe *this, UINT itinfo, LCID lcid, ITypeInfo **pTypeInfo)
{
	register HRESULT	hr;

	// Assume an error
	*pTypeInfo = 0;
	
	if (itinfo)
		hr = ResultFromScode(DISP_E_BADINDEX);

	// If our ITypeInfo is already created, just increment its ref count. NOTE: We really should
	// store the LCID of the currently created TYPEINFO and compare it to what the caller wants.
	// If no match, unloaded the currently created TYPEINFO, and create the correct one. But since
	// we support only one language in our IDL file anyway, we'll ignore this
	else if (MyTypeInfo)
	{
		MyTypeInfo->lpVtbl->AddRef(MyTypeInfo);
		hr = 0;
	}
	else
	{
		// Load our type library and get Microsoft's generic ITypeInfo object. NOTE: We really
		// should pass the LCID to match, but since we support only one language in our IDL
		// file anyway, we'll ignore this
		hr = loadMyTypeInfo();
	}

	if (!hr) *pTypeInfo = MyTypeInfo;

	return(hr);
}

// IExampleExe's GetIDsOfNames()
static ULONG STDMETHODCALLTYPE GetIDsOfNames(IExampleExe *this, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgdispid)
{
	if (!MyTypeInfo)
	{
		register HRESULT	hr;

		if ((hr = loadMyTypeInfo())) return(hr);
	}
	
	// Let OLE32.DLL's DispGetIDsOfNames() do all the real work of using our type
	// library to look up the DISPID of the requested function in our object
	return(DispGetIDsOfNames(MyTypeInfo, rgszNames, cNames, rgdispid));
}

// IExampleExe's Invoke()
static ULONG STDMETHODCALLTYPE Invoke(IExampleExe *this, DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *params, VARIANT *result, EXCEPINFO *pexcepinfo, UINT *puArgErr)
{
   // We implement only a "default" interface
   if (!IsEqualIID(riid, &IID_NULL))
      return(DISP_E_UNKNOWNINTERFACE);

	// We need our type lib's TYPEINFO (to pass to DispInvoke)
	if (!MyTypeInfo)
	{
		register HRESULT	hr;

		if ((hr = loadMyTypeInfo())) return(hr);
	}

	// Let OLE32.DLL's DispInvoke() do all the real work of calling the appropriate
	// function in our object, and massaging the passed args into the correct format
	return(DispInvoke(this, MyTypeInfo, dispid, wFlags, params, result, pexcepinfo, puArgErr));
}

// ================== The following are my own extra functions added to IExample

// IExampleExe's SetString(). This copies the passed string to IExampleExe's
// string[]
static HRESULT STDMETHODCALLTYPE SetString(IExampleExe *this, BSTR str)
{
	// Make sure that caller passed a buffer
	if (!str) return(E_POINTER);

   // First, free any old BSTR we allocated
   if (((MyRealIExampleExe *)this)->string) SysFreeString(((MyRealIExampleExe *)this)->string);

   // Make a copy of the caller's string and save this BSTR
   if (!(((MyRealIExampleExe *)this)->string = SysAllocStringLen(str, SysStringLen(str))))
		return(E_OUTOFMEMORY);

	return(NOERROR);
}

// IExampleExe's GetString(). This retrieves IExampleExe's string[],
// and stores its contents in a buffer passed by the caller
static HRESULT STDMETHODCALLTYPE GetString(IExampleExe *this, BSTR *buffer)
{
	// Make sure that caller passed a handle
	if (!buffer) return(E_POINTER);

	// Create a copy of our buffer. The caller is responsible for freeing it
	if (!(*buffer = SysAllocString(((MyRealIExampleExe *)this)->string)))
		return(E_OUTOFMEMORY);

	return(NOERROR);
}

// Here's IExampleExe's VTable. It never changes so we can declare it
// static
static const IExampleExeVtbl IExampleExe_Vtbl = {QueryInterface,
AddRef,
Release,
GetTypeInfoCount,
GetTypeInfo,
GetIDsOfNames,
Invoke,
SetString,
GetString};











// The IClassFactory object ///////////////////////////////////////////////////////

// Since we only ever need one IClassFactory object, we declare
// it static. The only requirement is that we ensure any
// access to its members is thread-safe
static IClassFactory	MyIClassFactoryObj;

// IClassFactory's AddRef()
static ULONG STDMETHODCALLTYPE classAddRef(IClassFactory *this)
{
	// Someone is obtaining my IClassFactory, so inc the count of
	// pointers that I've returned which some app needs to Release()
	InterlockedIncrement(&OutstandingObjects);

	// Since we never actually allocate/free an IClassFactory (ie, we
	// use just 1 static one), we don't need to maintain a separate
	// reference count for our IClassFactory. We'll just tell the caller
	// that there's at least one of our IClassFactory objects in existance
	return(1);
}

// IClassFactory's QueryInterface()
static HRESULT STDMETHODCALLTYPE classQueryInterface(IClassFactory *this, REFIID factoryGuid, void **ppv)
{
	// Make sure the caller wants either an IUnknown or an IClassFactory.
	// In either case, we return the same IClassFactory pointer passed to
	// us since it can also masquerade as an IUnknown
	if (IsEqualIID(factoryGuid, &IID_IUnknown) || IsEqualIID(factoryGuid, &IID_IClassFactory))
	{
		// Call my IClassFactory's AddRef
		this->lpVtbl->AddRef(this);

		// Return (to the caller) a ptr to my IClassFactory
		*ppv = this;

		return(NOERROR);
	}

	// We don't know about any other GUIDs
	*ppv = 0;
	return(E_NOINTERFACE);
}

// IClassFactory's Release()
static ULONG STDMETHODCALLTYPE classRelease(IClassFactory *this)
{
	ULONG	count;

	// One less object that an app has not yet Release()'ed
	count = InterlockedDecrement(&OutstandingObjects);

	// If we can unload this EXE now, then post a WM_QUIT message so WinMain
	// will drop out of the loop and this EXE terminates
	if (!DllCanUnloadNow()) PostMessage(0, WM_QUIT, 0, 0);

	return(count);
}

// IClassFactory's CreateInstance() function. It is called by
// someone who has a pointer to our IClassFactory object and now
// wants to create and retrieve a pointer to our IExampleExe
static HRESULT STDMETHODCALLTYPE classCreateInstance(IClassFactory *this, IUnknown *punkOuter, REFIID vTableGuid, void **objHandle)
{
	HRESULT					hr;
	register IExampleExe	*thisobj;

	// Assume an error by clearing caller's handle
	*objHandle = 0;

	// We don't support aggregation in this example
	if (punkOuter)
		hr = CLASS_E_NOAGGREGATION;
	else
	{
		// Allocate our IExampleExe object (actually a MyRealIExampleExe)
		if (!(thisobj = (IExampleExe *)GlobalAlloc(GMEM_FIXED, sizeof(MyRealIExampleExe))))
			hr = E_OUTOFMEMORY;
		else
		{
			// Store IExampleExe's VTable in the object
			thisobj->lpVtbl = (IExampleExeVtbl *)&IExampleExe_Vtbl;

			// Increment the reference count so we can call Release() below and
			// it will deallocate only if there is an error with QueryInterface()
			((MyRealIExampleExe *)thisobj)->count = 1;

			// Initialize any other members we added to the IExampleExe. We added
			// a string member
			((MyRealIExampleExe *)thisobj)->string = 0;

			// Fill in the caller's handle with a pointer to the IExample we just
			// allocated above. We'll let IExampleExe's QueryInterface do that, because
			// it also checks the GUID the caller passed, and also increments the
			// reference count (to 2) if all goes well
			hr = IExampleExe_Vtbl.QueryInterface(thisobj, vTableGuid, objHandle);

			// Decrement reference count. NOTE: If there was an error in QueryInterface()
			// then Release() will be decrementing the count back to 0 and will free the
			// IExample for us. One error that may occur is that the caller is asking for
			// some sort of object that we don't support (ie, it's a GUID we don't recognize)
			IExampleExe_Vtbl.Release(thisobj);

			// If success, inc static object count to keep this EXE loaded
			if (!hr) InterlockedIncrement(&OutstandingObjects);
		}
	}

	return(hr);
}

// IClassFactory's LockServer(). It is called by someone
// who wants to lock this EXE in memory
static HRESULT STDMETHODCALLTYPE classLockServer(IClassFactory *this, BOOL flock)
{
	if (flock) InterlockedIncrement(&LockCount);
	else InterlockedDecrement(&LockCount);

	return(NOERROR);
}

// IClassFactory's VTable
static const IClassFactoryVtbl IClassFactory_Vtbl = {classQueryInterface,
classAddRef,
classRelease,
classCreateInstance,
classLockServer};














// Miscellaneous functions ///////////////////////////////////////////////////////

/************************ DllGetClassObject() ***********************
 * This is called by the OLE functions CoGetClassObject() or
 * CoCreateInstance() in order to get our EXE's IClassFactory object 
 * (and return it to someone who wants to use it to get ahold of one
 * of our IExampleExe objects). Our IClassFactory's CreateInstance() can
 * be used to allocate/retrieve our IExampleExe object.
 *
 * NOTE: After we return the pointer to our IClassFactory, the caller
 * will typically call its CreateInstance() function.
 */

HRESULT PASCAL DllGetClassObject(REFCLSID objGuid, REFIID factoryGuid, void **factoryHandle)
{
	register HRESULT		hr;

	// Check that the caller is passing our IExampleExe GUID. That's the
	// only object our EXE implements
	if (IsEqualCLSID(objGuid, &CLSID_IExampleExe))
	{
		// Fill in the caller's handle with a pointer to our IClassFactory object.
		// We'll let our IClassFactory's QueryInterface do that, because it also
		// checks the IClassFactory GUID and does other book-keeping
		hr = classQueryInterface(&MyIClassFactoryObj, factoryGuid, factoryHandle);
	}
	else
	{
		// We don't understand this GUID. It's obviously not for our EXE.
		// Let the caller know this by clearing his handle and returning
		// CLASS_E_CLASSNOTAVAILABLE
		*factoryHandle = 0;
		hr = CLASS_E_CLASSNOTAVAILABLE;
	}

	return(hr);
}





/************************ DllCanUnloadNow() ***********************
 * This is called by some OLE function in order to determine
 * whether it is safe to unload our EXE from memory.
 *
 * RETURNS: S_OK if safe to unload, or S_FALSE if not.
 */

HRESULT PASCAL DllCanUnloadNow(void)
{
	// If someone has retrieved pointers to any of our objects, and
	// not yet Release()'ed them, then we return S_FALSE to indicate
	// not to unload this EXE. Also, if someone has us locked, return
	// S_FALSE
	return((OutstandingObjects | LockCount) ? S_FALSE : S_OK);
}





/************************ WinMain() **************************
 * Our EXE's entry point. This is called by Windows when our
 * EXE first starts up.
 */

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	DWORD	nToken;

	// No TypeInfo yet loaded
	MyTypeInfo = 0;

	// Clear global lock count
	LockCount = 0;

	// Initialize my IClassFactory with the pointer to its vtable
	MyIClassFactoryObj.lpVtbl = (IClassFactoryVtbl *)&IClassFactory_Vtbl;

	// Initialize COM
	CoInitialize(0);

	{
	HRESULT		hr;

#define ALLOW_UNLOADING
	// Because CoRegisterClassObject will call our IClassFactory AddRef() and
	// then not call its Release until we do CoRevokeClassObject, let's initialize
	// OutstandingObjects to -1 so that we don't really count this first AddRef
	// on our IClassFactory. That way OutstandingObjects represents only those
	// AddRef's caused by an app using this EXE
	OutstandingObjects = (DWORD)-1;
#else
	OutstandingObjects = 0;
#endif

	// Add this EXE to COM's Running Task table. We pass our IExampleExe object's GUID, and a pointer to
	// our IClassFactory object (we use only 1). Save the token that CoRegisterClassObject returns so we
	// can later remove this EXE from COM's task table
	hr = CoRegisterClassObject(&CLSID_IExampleExe, (IUnknown *)&MyIClassFactoryObj, CLSCTX_SERVER, REGCLS_MULTIPLEUSE, &nToken);
	if (hr)
	{
		MessageBox(0, "Can't add this EXE to COM's running task list!", "Error", MB_OK|MB_ICONEXCLAMATION);
		return(-1);
	}
	}

	// Do a message until WM_QUIT message is received. Our objects post this when they see that all of
	// our objects have been released
	{
	MSG msg;
	while (GetMessage(&msg, 0, 0, 0) > 0) 
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	}		

	// Release any ITypeInfo we got
	if (MyTypeInfo) MyTypeInfo->lpVtbl->Release(MyTypeInfo);

	// Remove this EXE from COM's Running task table
	CoRevokeClassObject(nToken);

	// Free up COM
	CoUninitialize();

	return 0;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions