Click here to Skip to main content
15,886,724 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 87K   2.2K   94  
Make a COM object with multiple interfaces, in C.
// C source code to a simple COM object, compiled into an ordinary
// dynamic link library (DLL). This demonstrates adding IDispatch
// functions to our object (so that it can be used by scripting
// languages like VBscript and JScript).

#include <windows.h>
#include <objbase.h>
#include "IExample4.h"
#include "PortNames.h"





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

// A count of how many apps have locked our DLL 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 IExample4 object ////////////////////////////////////////////////////////////

// In our .H file, we use a macro which defines our IExample4 struct
// as so:
//
// typedef struct {
//    IExample4Vtbl  *lpVtbl;
// } IExample4;
//
// In other words, the .H file defines our IExample4 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 IExample4.
// We just don't want any app to be able to know about, and directly
// access, those members. So here we'll define a MyRealIExample4 that
// contains those extra members. The app doesn't know that we're
// really allocating and giving it a MyRealIExample4 object. We'll
// lie and tell it we're giving a plain old IExample4. That's ok
// because a MyRealIExample4 starts with the same VTable pointer.
//
// We add a DWORD reference count so that this IExample4
// 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 IExample4
typedef struct {
	IExample4Vtbl	*lpVtbl;
	DWORD			count;
	BSTR			string;
} MyRealIExample4;





// Here are IExample4'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 IExample4, which a program
// will call with the names GetString and SetString.

// IExample4's QueryInterface()
static HRESULT STDMETHODCALLTYPE QueryInterface(IExample4 *this, REFIID vTableGuid, void **ppv)
{
	// Check if the GUID matches IExample4 VTable's GUID. We gave the C variable name
	// IID_IExample4 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 IExample4, since it can
	// masquerade as an IUnknown object too. Finally, if the called passed a
	// IDispatch GUID, then we'll return the IExample4, since it can masquerade
	// as an IDispatch too
	if (!IsEqualIID(vTableGuid, &IID_IUnknown) && !IsEqualIID(vTableGuid, &IID_IExample4) && !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(IExample4 *this)
{
	// Increment IExample4'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 MyRealIExample4
	// struct. So typecast to that in order to access those data members
	return(++((MyRealIExample4 *)this)->count);
}

// IExample's Release()
static ULONG STDMETHODCALLTYPE Release(IExample4 *this)
{
	// Decrement IExample4's reference count. If 0, then we can safely free
	// this IExample4 now
	if (--((MyRealIExample4 *)this)->count == 0)
	{
		if (((MyRealIExample4 *)this)->string) SysFreeString(((MyRealIExample4 *)this)->string);
		GlobalFree(this);
		InterlockedDecrement(&OutstandingObjects);
		return(0);
	}
	return(((MyRealIExample4 *)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 IExample4's VTable, by passing that VTable's GUID
		if (!(hr = pTypeLib->lpVtbl->GetTypeInfoOfGuid(pTypeLib, &IID_IExample4, &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);
}

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

// IExample4's GetTypeInfo()
static ULONG STDMETHODCALLTYPE GetTypeInfo(IExample4 *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);
}

// IExample4's GetIDsOfNames()
static ULONG STDMETHODCALLTYPE GetIDsOfNames(IExample4 *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));
}

// IExample4's Invoke()
static ULONG STDMETHODCALLTYPE Invoke(IExample4 *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

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

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

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

	return(NOERROR);
}

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

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

	return(NOERROR);
}

// IExample4's GetPorts(). This creates and returns an IDispatch to allow
// a host to enumerate our port names,
static HRESULT STDMETHODCALLTYPE GetPorts(IExample4 *this, IDispatch **portsObj)
{
	// Make sure that caller passed a handle
	if (!portsObj) return(E_POINTER);

	// Create an IDispatch to enumerate our port names. Caller is responsible for Release()'ing
	// it. NOTE: We're really returning a MyRealICollection, but the caller doesn't know that.
	// He thinks we're returning an IDispatch, which is ok because a MyRealICollection's
	// VTable starts with the 3 IUnknown functions followed by the 4 IDispatch functions, just
	// like a real IDispatch object's VTable
	if (!(*portsObj = allocPortsCollection()))
		return(E_OUTOFMEMORY);

	return(NOERROR);
}


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











// 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)
{
	// One less object that an app has not yet Release()'ed
	return(InterlockedDecrement(&OutstandingObjects));
}

// 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 IExample4
static HRESULT STDMETHODCALLTYPE classCreateInstance(IClassFactory *this, IUnknown *punkOuter, REFIID vTableGuid, void **objHandle)
{
	HRESULT				hr;
	register IExample4	*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 IExample4 object (actually a MyRealIExample4)
		if (!(thisobj = (IExample4 *)GlobalAlloc(GMEM_FIXED, sizeof(MyRealIExample4))))
			hr = E_OUTOFMEMORY;
		else
		{
			// Store IExample4's VTable in the object
			thisobj->lpVtbl = (IExample4Vtbl *)&IExample4_Vtbl;

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

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

			// Fill in the caller's handle with a pointer to the IExample we just
			// allocated above. We'll let IExample4'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 = IExample4_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)
			IExample4_Vtbl.Release(thisobj);

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

	return(hr);
}

// IClassFactory's LockServer(). It is called by someone
// who wants to lock this DLL 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 DLL's IClassFactory object 
 * (and return it to someone who wants to use it to get ahold of one
 * of our IExample objects). Our IClassFactory's CreateInstance() can
 * be used to allocate/retrieve our IExample 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 IExample GUID. That's the
	// only object our DLL implements
	if (IsEqualCLSID(objGuid, &CLSID_IExample4))
	{
		// 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 DLL.
		// 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 DLL 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 DLL. Also, if someone has us locked, return
	// S_FALSE
	return((OutstandingObjects | LockCount) ? S_FALSE : S_OK);
}





/************************** DllMain() **************************
 * Called by OS when this DLL is loaded or unloaded.
 */

BOOL WINAPI DllMain(HINSTANCE instance, DWORD fdwReason, LPVOID lpvReserved)
{
	switch (fdwReason)
	{
		case DLL_PROCESS_ATTACH:
		{
			// No TypeInfo yet loaded
			MyTypeInfo = 0;

			// Initialize our ICollection stuff
			initCollectionTypeInfo();

			// Initialize our Ports list
			if (initPortsCollection())
			{
				MessageBox(0, "Can't allocate the PortsList", "ERROR", MB_OK);
				return(0);
			}

			// Clear global counts
			OutstandingObjects = LockCount = 0;

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

			// We don't need to do any thread initialization
			DisableThreadLibraryCalls(instance);
			break;
		}

		case DLL_PROCESS_DETACH:
		{
			// Free our Ports list
			freePortsCollection();

			// Free our ICollection stuff
			freeCollectionTypeInfo();

			// Release any TYPEINFO that my IDispatch functions got
			if (MyTypeInfo) MyTypeInfo->lpVtbl->Release(MyTypeInfo);
		}
	}

	return(1);
}

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