Click here to Skip to main content
11,577,240 members (60,301 online)
Click here to Skip to main content
Add your own
alternative version

COM in plain C, Part 6

, 22 Jul 2006 CPOL 65.7K 1.5K 94
How to write an ActiveX Script Host in C.
com_in_c6_src.zip
iexample
IExample.def
IExample.dsp
IExample.dsw
iexample2
IExample2.def
IEXAMPLE2.dsp
IEXAMPLE2.dsw
iexample3
IExample3.def
IEXAMPLE3.dsp
IEXAMPLE3.dsw
iexample3app
IEXAMPLE3APP.dsp
IEXAMPLE3APP.dsw
iexample4
IExample4.def
IEXAMPLE4.dsp
IEXAMPLE4.dsw
iexample4app
IEXAMPLE4APP.dsp
IEXAMPLE4APP.dsw
iexample5
IExample5.def
IEXAMPLE5.dsp
IEXAMPLE5.dsw
iexampleapp
IExampleApp.dsp
IExampleApp.dsw
iexampleapp2
IEXAMPLEAPP2.dsp
IEXAMPLEAPP2.dsw
iexamplecplusapp
IEXAMPLECPLUSAPP.dsp
IEXAMPLECPLUSAPP.dsw
iexampleevts
IExampleEvts.def
IExampleEvts.dsp
IExampleEvts.dsw
iexampleevts2
IExampleEvts2.def
IExampleEvts2.dsp
IExampleEvts2.dsw
iexampleevts2app
IExampleEvts2App.dsp
IExampleEvts2App.dsw
iexampleevtsapp
IExampleEvtsApp.dsp
IExampleEvtsApp.dsw
iexampleexe
IEXAMPLEEXE.dsp
IEXAMPLEEXE.dsw
imultinterface
IMultInterface.def
IMultInterface.dsp
IMultInterface.dsw
imultinterfaceapp
IMultInterfaceApp.dsp
IMultInterfaceApp.dsw
isort
ISort.def
ISORT.dsp
ISORT.dsw
isortapp
ISortApp.dsp
ISortApp.dsw
regiexample
RegIExample.dsp
RegIExample.dsw
regiexample2
REGIEXAMPLE2.dsp
REGIEXAMPLE2.dsw
regiexample3
REGIEXAMPLE3.dsp
REGIEXAMPLE3.dsw
regiexampleexe
REGIEXAMPLEEXE.dsp
REGIEXAMPLEEXE.dsw
scripthost
ScriptHost.dsp
ScriptHost.dsw
scripthost2
ScriptHost.dsp
ScriptHost.dsw
server1.gif
unregiexample
UnregIExample.dsp
UnregIExample.dsw
unregiexample2
UNREGIEXAMPLE2.dsp
UNREGIEXAMPLE2.dsw
scripthost3
SCRIPTHOST3.dsp
SCRIPTHOST3.dsw
// C source code to a simple COM object, compiled into an ordinary
// dynamic link library (DLL). This demonstrates adding
// IConnectionPointContainer and IConnectionPoint sub-objects to
// our IExampleEvts object for the purpose of letting an app
// supply some callback functions (for IExampleEvts' extra functions
// to call)

#include <windows.h>
#include <objbase.h>
#include <activscp.h>
#include <olectl.h>
#include <stddef.h>
#include "IExampleEvts.h"






static DWORD		OutstandingObjects;
static DWORD		LockCount;









// Our IExampleEvts object ////////////////////////////////////////////////////////////

// This is our IExampleEvts object. It can "source events" which just
// means that we allow an app to give us some object that has callback
// functions that IExampleEvts' functions can call for whatever purpose
// we deem. We define a IFeedback object that an app can give us
// (containing the callback functions). This IFeedback's extra functions
// must be exactly as we've defined in IExampleEvts.h.
//
// Because our IExampleEvts sources events, it must have an
// IConnectionPointContainer sub-object. (ie, Our MyRealIExampleEvts
// has multiple interfaces). The app uses our IConnectionPointContainer
// sub-object to get a IConnectionPoint sub-object that allows him to
// give us his IFeedback object. To make this easy, we'll just embed
// our IConnectionPointContainer sub-object right inside of our
// MyRealIExampleEvts.
//
// We also embed an IConnectionPoint sub-object. We're going to
// support allowing the app only one IFeedback object per each of
// our IExampleEvts objects.
//
// Because we support only one IFeedback per IExampleEvts, we'll
// store a pointer to the app's IFeedback in our IExampleEvts.
typedef struct {
	IExampleEvtsVtbl			*lpVtbl;
	DWORD						count;
	IConnectionPointContainer	container;
	IConnectionPoint			point;
	IFeedback					*feedback;
} MyRealIExampleEvts;

static HRESULT STDMETHODCALLTYPE QueryInterface(IExampleEvts *this, REFIID vTableGuid, void **ppv)
{
	// Because our IExampleEvts sources events, we must return an
	// IConnectionPointContainer sub-object if the app asks for one. Because we've
	// embedded our IConnectionPointContainer object inside of our MyRealIExampleEvts,
	// we can get that sub-object very easily using pointer arithmetic
	if (IsEqualIID(vTableGuid, &IID_IConnectionPointContainer))
		*ppv = ((unsigned char *)this + offsetof(MyRealIExampleEvts, container));

	else if (IsEqualIID(vTableGuid, &IID_IUnknown) || IsEqualIID(vTableGuid, &IID_IExampleEvts))
		*ppv = this;

	else
	{
		*ppv = 0;
		return(E_NOINTERFACE);
	}

	this->lpVtbl->AddRef(this);

	return(NOERROR);
}

static ULONG STDMETHODCALLTYPE AddRef(IExampleEvts *this)
{
	return(++((MyRealIExampleEvts *)this)->count);
}

static ULONG STDMETHODCALLTYPE Release(IExampleEvts *this)
{
	// Note: This count includes any outstanding IConnectionPoint
	// and IConnectionPointContainer objects that the app is still
	// holding onto. So we don't actually free our IExampleEvts
	// until all of those are released too
	if (--((MyRealIExampleEvts *)this)->count == 0)
	{
		GlobalFree(this);
		InterlockedDecrement(&OutstandingObjects);
		return(0);
	}
	return(((MyRealIExampleEvts *)this)->count);
}


// Our IFeedback has 5, extra (callback) functions that we can call.
#define MAX_CALLBACK_FUNCS	5

// Initially, DoSomething() calls IFeedback's first callback function
static DWORD FuncNumber = 0;

// Our extra function for IExampleEvts. DoSomething() is a totally contrived
// example of using the app's IFeedback. Every time the app calls DoSomething,
// we'll call another one of IFeedback's callback functions in turn
static HRESULT STDMETHODCALLTYPE DoSomething(IExampleEvts *this)
{
	register IFeedback	*feedback;

	// Get the app's IFeedback object. We stored a pointer to it in
	// our MyRealIExampleEvts->feedback member. NOTE: If this member is 0,
	// then the app has not yet gotten our IConnectionPoint object,
	// and called its Advise() function to give us the app's IFeedback.
	// Or perhaps, the app has called our IConnectionPoint->Unadvise() to
	// tell us to Release(), and no longer use, its IFeedback
	if ((feedback = ((MyRealIExampleEvts *)this)->feedback))
	{
		// Ok, the app has already given us its IFeedback.

		// Let's just cause a different IFeedback function to be called
		// each time the app calls our DoSomething routine. We'll simply
		// increment a global that tells which function to call (and roll
		// it over when necessary)
		++FuncNumber;
		if (FuncNumber > MAX_CALLBACK_FUNCS) FuncNumber = 1;

		switch (FuncNumber)
		{
			case 1:
				feedback->lpVtbl->Callback1(feedback);
				break;
			case 2:
				feedback->lpVtbl->Callback2(feedback);
				break;
			case 3:
				feedback->lpVtbl->Callback3(feedback);
				break;
			case 4:
				feedback->lpVtbl->Callback4(feedback);
				break;
			case 5:
				feedback->lpVtbl->Callback5(feedback);
		}
	}

	return(S_OK);
}


static const IExampleEvtsVtbl IExampleEvts_Vtbl = {QueryInterface,
AddRef,
Release,
DoSomething};




















// Our IConnectionPointContainer sub-object (for IExampleEvts) ////////////////////////

static STDMETHODIMP QueryInterface_Connect(IConnectionPointContainer *this, REFIID vTableGuid, void **ppv)
{
	// Because this is a sub-object of our IExampleEvts (ie, MyRealIExampleEvts) object,
	// we delegate to IExampleEvts' QueryInterface. And because we embedded the
	// IConnectionPointContainer directly inside of MyRealIExampleEvts, all we need
	// is a little pointer arithmetic to get our IExampleEvts
	return(QueryInterface((IExampleEvts *)((char *)this - offsetof(MyRealIExampleEvts, container)), vTableGuid, ppv));
}

static STDMETHODIMP_(ULONG) AddRef_Connect(IConnectionPointContainer *this)
{
	// Because we're a sub-object of IExampleEvts, delegate to its AddRef()
	// in order to increment IExampleEvts' reference count
	return(AddRef((IExampleEvts *)((char *)this - offsetof(MyRealIExampleEvts, container))));
}

static STDMETHODIMP_(ULONG) Release_Connect(IConnectionPointContainer *this)
{
	// Because we're a sub-object of IExampleEvts, delegate to its Release()
	// in order to decrement IExampleEvts' reference count
	return(Release((IExampleEvts *)((char *)this - offsetof(MyRealIExampleEvts, container))));
}

static STDMETHODIMP EnumConnectionPoints(IConnectionPointContainer *this, IEnumConnectionPoints **enumPoints)
{
	// The app had better know the GUIDs of whatever objects our
	// IExampleEvts supports for callbacks (ie, an IFeedback), because
	// we're not going to bother providing him with an object to
	// enumerate the VTable GUIDs of all those supported objects
	*enumPoints = 0;
	return(E_NOTIMPL);
}
 
static STDMETHODIMP FindConnectionPoint(IConnectionPointContainer *this, REFIID vTableGuid, IConnectionPoint **ppv) 
{
	// Is the app asking us to return an IConnectionPoint object it can use
	// to give us its IFeedback object? The app asks this by passing us
	// IFeedback VTable's GUID (which we defined in IExampleEvts.h)
	if (IsEqualIID(vTableGuid, &DIID_IFeedback))
	{
		register MyRealIExampleEvts		*iExample;

		// The app obviously wants to connect its IFeedback object
		// to IExampleEvts. In order to do that, we need to give the app a
		// standard IConnectionPoint, so the app can call its Advise function
		// to give us its IFeedback. This is easy to do since we embedded both
		// our IConnectionPointContainer and IConnectionPoint inside of our 
		// IExampleEvts. All we need is a little pointer arithmetic
		iExample = (MyRealIExampleEvts *)((char *)this - offsetof(MyRealIExampleEvts, container));
		*ppv = &iExample->point;

		// Because we're giving the app a pointer to our IConnectionPoint, and
		// our IConnectionPoint is a sub-object of IExampleEvts, we need to
		// increment IExampleEvts reference count. The easiest way to do this is to call
		// our IConnectionPointContainer's AddRef, because all we do there is delegate
		// to our IExampleEvts's AddRef
		AddRef_Connect(this);

		return(S_OK);
	}

	// We don't support any other app objects connecting to IExampleEvts
	// events. All we've defined, and support, is an IFeedback object. Tell
	// the app we don't know anything about the GUID he passed to us, and
	// do not give him any IConnectPoint object
	*ppv = 0;
	return(E_NOINTERFACE);
}


static const IConnectionPointContainerVtbl IConnectionPointContainer_Vtbl = {QueryInterface_Connect,
AddRef_Connect,
Release_Connect,
EnumConnectionPoints,
FindConnectionPoint};













// Our IConnectionPoint sub-object (for IExampleEvts) ////////////////////////////

static STDMETHODIMP QueryInterface_Point(IConnectionPoint *this, REFIID vTableGuid, void **ppv)
{
	// Because this is a sub-object of our IExampleEvts (ie, MyRealIExampleEvts) object,
	// we delegate to IExampleEvts' QueryInterface. And because we embedded the
	// IConnectionPoint directly inside of MyRealIExampleEvts, all we need
	// is a little pointer arithmetic to get our IExampleEvts
	return(QueryInterface((IExampleEvts *)((char *)this - offsetof(MyRealIExampleEvts, point)), vTableGuid, ppv));
}

static STDMETHODIMP_(ULONG) AddRef_Point(IConnectionPoint *this)
{
	// Because we're a sub-object of IExampleEvts, delegate to its AddRef()
	// in order to increment IExampleEvts' reference count
	return(AddRef((IExampleEvts *)((char *)this - offsetof(MyRealIExampleEvts, point))));
}

static STDMETHODIMP_(ULONG) Release_Point(IConnectionPoint *this)
{
	// Because we're a sub-object of IExampleEvts, delegate to its Release()
	// in order to decrement IExampleEvts' reference count
	return(Release((IExampleEvts *)((char *)this - offsetof(MyRealIExampleEvts, point))));
}

// Called by the app to get our IFeedback VTable's GUID (which we defined in IExampleEvts.h).
// The app would call GetConnectionInterface() if it didn't link with IExampleEvts.h, and
// therefore doesn't know our IFeedback VTable's GUID. The app needs to know this GUID
// because our Advise function below is going to pass this same GUID to some app object's
// QueryInterface. The app's QueryInterface had better recognize this GUID if it intends
// to honor our request to give us its IFeedback object
static STDMETHODIMP GetConnectionInterface(IConnectionPoint *this, IID *vTableGuid) 
{
	// Tell the app to recognize our IFeedback VTable GUID (defined as
	// DIID_IFeedback in IExampleEvts.h) when our Advise function calls
	// some app QueryInterface function
	CopyMemory(vTableGuid, &DIID_IFeedback, sizeof(GUID));
	return(S_OK);
}
 
// Called by the app to get the IConnectionPointContainer sub-object for our
// IExampleEvts object.
static STDMETHODIMP GetConnectionPointContainer(IConnectionPoint *this, IConnectionPointContainer **ppv) 
{
	register MyRealIExampleEvts	*iExample;

	// Get the MyRealIExampleEvts that this IConnectionPoint sub-object belongs
	// to. Because this IConnectPoint sub-object is embedded directly inside its
	// MyRealIExampleEvts, all we need is a little pointer arithmetic
	iExample = (MyRealIExampleEvts *)((char *)this - offsetof(MyRealIExampleEvts, point));

	// Because the IConnectionPointContainer sub-object is also embedded right inside
	// the same MyRealIExampleEvts, we can get a pointer to it easily as so
	*ppv = &iExample->container;

	// Because we're giving the app a pointer to our IConnectionPointContainer, and
	// our IConnectionPointContainer is a sub-object of IExampleEvts, we need to
	// increment IExampleEvts reference count. The easiest way to do this is to call
	// our IConnectionPoint's AddRef, because all we do there is delegate
	// to our IExampleEvts's AddRef
	AddRef_Point(this);

	return(S_OK);
}

// Called by the app to give us its IFeedback object. Actually, the app doesn't
// just give us its IFeedback. Rather, the app calls our Advise, passing us some
// app object from which we can request the app to give us its IFeedback. All of
// this convoluted stuff is a combination of poor pre-planning by Microsoft
// programmers when they designed this stuff, as well as the colossal blunder of
// designing COM to accomodate the limitations of early, primitive editions of
// Visual Basic.
//
// The second arg passed here is some app object whose QueryInterface function
// we call to request the app's IFeedback. We pass the GUID DIID_IFeedback to
// this QueryInterface in order to tell the app to give us its IFeedback
static STDMETHODIMP Advise(IConnectionPoint *this, IUnknown *obj, DWORD *cookie) 
{
	register HRESULT			hr;
	register MyRealIExampleEvts	*iExample;

	// Get the MyRealIExampleEvts that this IConnectionPoint sub-object belongs
	// to. Because this IConnectPoint sub-object is embedded directly inside its
	// MyRealIExampleEvts, all we need is a little pointer arithmetic
	iExample = (MyRealIExampleEvts *)((char *)this - offsetof(MyRealIExampleEvts, point));

	// We allow only one IFeedback for our IExampleEvts, so see if the app already
	// called our Advise(), and we got one. If so, let the app know that it is trying
	// to give us more IFeedbacks than we allow
	if (iExample->feedback) return(CONNECT_E_ADVISELIMIT);
 
	// Ok, we haven't yet gotten the one IFeedback we allow from the app. Get the app's 
	// IFeedback object. We do this by calling the QueryInterface function of the
	// app object passed to us. We pass IFeedback VTable's GUID (which we defined
	// in IExampleEvts.h).
	//
	// Save the app's IFeedback pointer in our IExampleEvts  feedback member, so we
	// can get it when we need it
	hr = obj->lpVtbl->QueryInterface(obj, &DIID_IFeedback, (void **)&iExample->feedback);

	// We need to return (to the app) some value that will clue our Unadvise() function
	// below how to locate this app IFeedback. The simpliest thing is to just use the
	// app's IFeedback pointer as that returned value
	*cookie = (DWORD)iExample->feedback;

	return(hr);
}

// Called by the app to tell us to stop using, and Release(), its IFeedback object.
// The second arg passed here is the value our Advise() function above returned when
// we got the IFeedback from the app. This value should help us locate wherever we
// stored that IFeedback pointer we got in Advise()
static STDMETHODIMP Unadvise(IConnectionPoint *this, DWORD cookie) 
{
	register MyRealIExampleEvts	*iExample;

	// Get the MyRealIExampleEvts that this IConnectionPoint sub-object belongs
	// to. Because this IConnectPoint sub-object is embedded directly inside its
	// MyRealIExampleEvts, all we need is a little pointer arithmetic
	iExample = (MyRealIExampleEvts *)((char *)this - offsetof(MyRealIExampleEvts, point));

	// Use the passed value to find wherever we stored his IFeedback pointer.
	// Well, since we allow only one IFeedback for our IExampleEvts, we already
	// know we stored it in our IExampleEvts->feedback member. And Advise()
	// returned that pointer as the "cookie" value. So we already got the
	// IFeedback right now.
	//		
	// Let's just make sure the cookie he passed is really the pointer we expect
	if (cookie && (IFeedback *)cookie == iExample->feedback)
	{
		// Release the app's IFeedback
		((IFeedback *)cookie)->lpVtbl->Release((IFeedback *)cookie);

		// We no longer have the app's IFeedback, so clear the IExampleEvts
		// feedback member
		iExample->feedback = 0;

		return(S_OK);
	}
	return(CONNECT_E_NOCONNECTION);
}

static STDMETHODIMP EnumConnections(IConnectionPoint *this, IEnumConnections **enumConnects)
{
	*enumConnects = 0; 
	return(E_NOTIMPL);
}


static const IConnectionPointVtbl IConnectionPoint_Vtbl = {
QueryInterface_Point,
AddRef_Point,
Release_Point,
GetConnectionInterface,
GetConnectionPointContainer,
Advise,
Unadvise,
EnumConnections};

















// Our IClassFactory object ///////////////////////////////////////////////////////

static IClassFactory	MyIClassFactoryObj;

static ULONG STDMETHODCALLTYPE classAddRef(IClassFactory *this)
{
	InterlockedIncrement(&OutstandingObjects);
	return(1);
}

static HRESULT STDMETHODCALLTYPE classQueryInterface(IClassFactory *this, REFIID factoryGuid, void **ppv)
{
	if (IsEqualIID(factoryGuid, &IID_IUnknown) || IsEqualIID(factoryGuid, &IID_IClassFactory))
	{
		this->lpVtbl->AddRef(this);

		*ppv = this;

		return(NOERROR);
	}

	*ppv = 0;
	return(E_NOINTERFACE);
}

static ULONG STDMETHODCALLTYPE classRelease(IClassFactory *this)
{
	return(InterlockedDecrement(&OutstandingObjects));
}

static HRESULT STDMETHODCALLTYPE classCreateInstance(IClassFactory *this, IUnknown *punkOuter, REFIID vTableGuid, void **objHandle)
{
	HRESULT						hr;
	register MyRealIExampleEvts	*thisobj;

	*objHandle = 0;

	if (punkOuter)
		hr = CLASS_E_NOAGGREGATION;
	else
	{
		if (!(thisobj = (MyRealIExampleEvts *)GlobalAlloc(GMEM_FIXED, sizeof(MyRealIExampleEvts))))
			hr = E_OUTOFMEMORY;
		else
		{
			thisobj->lpVtbl = (IExampleEvtsVtbl *)&IExampleEvts_Vtbl;
			thisobj->count = 1;

			// Our MyRealIExampleEvts is a multiple interface object. It has an
			// IConnectionPointContainer sub-object embedded directly inside of
			// it. And we just allocated it when we allocated the MyRealIExampleEvts
			// above. Now we need to set its VTable into its lpVtbl member and
			// we're done initializing this sub-object
			thisobj->container.lpVtbl = (IConnectionPointContainerVtbl *)&IConnectionPointContainer_Vtbl;

			// Our MyRealIExampleEvts also has an IConnectionPoint sub-object
			// embedded directly inside of it. And we just allocated it when we
			// allocated the MyRealIExampleEvts above. Now we need to set its
			// VTable into its lpVtbl member and we're done initializing this sub-object
			thisobj->point.lpVtbl = (IConnectionPointVtbl *)&IConnectionPoint_Vtbl;

			// We don't have an app IFeedback object yet
			thisobj->feedback = 0;

			hr = IExampleEvts_Vtbl.QueryInterface((IExampleEvts *)thisobj, vTableGuid, objHandle);
			IExampleEvts_Vtbl.Release((IExampleEvts *)thisobj);
			if (!hr) InterlockedIncrement(&OutstandingObjects);
		}
	}

	return(hr);
}

static HRESULT STDMETHODCALLTYPE classLockServer(IClassFactory *this, BOOL flock)
{
	if (flock) InterlockedIncrement(&LockCount);
	else InterlockedDecrement(&LockCount);

	return(NOERROR);
}

static const IClassFactoryVtbl IClassFactory_Vtbl = {classQueryInterface,
classAddRef,
classRelease,
classCreateInstance,
classLockServer};














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

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

	if (IsEqualCLSID(objGuid, &CLSID_IExampleEvts))
		hr = classQueryInterface(&MyIClassFactoryObj, factoryGuid, factoryHandle);
	else
	{
		*factoryHandle = 0;
		hr = CLASS_E_CLASSNOTAVAILABLE;
	}

	return(hr);
}

HRESULT PASCAL DllCanUnloadNow(void)
{
	return((OutstandingObjects | LockCount) ? S_FALSE : S_OK);
}

BOOL WINAPI DllMain(HINSTANCE instance, DWORD fdwReason, LPVOID lpvReserved)
{
	switch (fdwReason)
	{
		case DLL_PROCESS_ATTACH:
		{
			OutstandingObjects = LockCount = 0;

			MyIClassFactoryObj.lpVtbl = (IClassFactoryVtbl *)&IClassFactory_Vtbl;

			DisableThreadLibraryCalls(instance);
		}
	}

	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)

Share

About the Author

Jeff Glatt
United States United States
No Biography provided

You may also be interested in...

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150603.1 | Last Updated 23 Jul 2006
Article Copyright 2006 by Jeff Glatt
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid