Click here to Skip to main content
15,881,898 members
Articles / Programming Languages / C++

COM in plain C, Part 6

Rate me:
Please Sign up or sign in to vote.
4.89/5 (32 votes)
22 Jul 2006CPOL25 min read 103K   2.4K   102  
How to write an ActiveX Script Host in C.
// This file contains functions for managing a list of "Port names".

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

//====================================================================
//======================= Global variables ===========================
//====================================================================

// Import this variable from IExample4.c
extern DWORD OutstandingObjects;

// A linked list of IENUMITEMs for our "Port names"
static IENUMITEM	*PortsList;

// Our ICollection's ITypeInfo. We need only one of these so
// we can make it global
static ITypeInfo	*CollectionTypeInfo;










//====================================================================
//======================= Helper functions ===========================
//====================================================================

// This is just a helper function to free up our PortsList. Called when our DLL unloads.
void freePortsCollection(void)
{
	IENUMITEM *item;

	item = PortsList;

	// Is there another item in the list?
	while ((item = PortsList))
	{
		// Get the next item *before* we delete this one
		PortsList = item->next;

		// If the item's value is an object, we need to Release()
		// it. If it's a BSTR, we need to SysFreeString() it.
		// VariantClear does this for us.
		VariantClear(&item->value);

		// Free the IENUMITEM.
		GlobalFree(item);
	}
}

// This is just a helper function to initialize our Ports list.
// Called when our DLL first loads.
HRESULT initPortsCollection(void)
{
	IENUMITEM *item;

	// Add a "Port 1" IENUMITEM to our list
	if ((PortsList = item = (IENUMITEM *)GlobalAlloc(GMEM_FIXED, sizeof(IENUMITEM))))
	{
		item->next = 0;
		item->value.vt = VT_BSTR;
		if ((item->value.bstrVal = SysAllocString(L"Port 1")))
		{
			// Add a "Port 2" IENUMITEM to our list
			if ((item->next = (IENUMITEM *)GlobalAlloc(GMEM_FIXED, sizeof(IENUMITEM))))
			{
				item = item->next;
				item->value.vt = VT_BSTR;
				if ((item->value.bstrVal = SysAllocString(L"Port 2")))
				{
					// Add a "Port 3" IENUMITEM to our list
					if ((item->next = (IENUMITEM *)GlobalAlloc(GMEM_FIXED, sizeof(IENUMITEM))))
					{
						item = item->next;
						item->next = 0;
						item->value.vt = VT_BSTR;
						if ((item->value.bstrVal = SysAllocString(L"Port 3")))
							return(S_OK);
					}
				}
			}
		}
	}

	// Error
	freePortsCollection();
	return(E_FAIL);
}

// This helper function initializes our ICollection TypeInfo.
// It's called when our DLL is loading.
void initCollectionTypeInfo(void)
{
	// We haven't yet created the ITypeInfo for our ICollection
	CollectionTypeInfo = 0;
}

// This helper function just Release()'s our ICollection TypeInfo.
// It's called when our DLL is unloading.
void freeCollectionTypeInfo(void)
{
	if (CollectionTypeInfo) CollectionTypeInfo->lpVtbl->AddRef(CollectionTypeInfo);
}












static IEnumVARIANT * allocIEnumVARIANT(void);

//==============================================================
//=================== IEnumVARIANT functions ===================
//==============================================================

/************************ AddRef() ***********************
 * Increments the reference count for our IEnumVARIANT
 * object.
 */

static STDMETHODIMP_(ULONG) Enum_AddRef(IEnumVARIANT *this)
{
	return(++((MyRealIEnumVariant *)this)->count);
}

/********************* QueryInterface() **********************
 * Retrieves a pointer to a VTable (interface) associated with
 * our IEnumVARIANT object.
 */

static STDMETHODIMP Enum_QueryInterface(IEnumVARIANT *this, REFIID riid, void **ppvObj)
{
	if (!ppvObj) return(E_POINTER);

	if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IEnumVARIANT))
	{
		*ppvObj = this;
		Enum_AddRef(this);
		return(NOERROR);
	}

	*ppvObj = 0;
	return(E_NOINTERFACE);

}

/********************** Release() ************************
 * Decrements the reference count for our IEnumVARIANT
 * object, and frees the object if the count is 0.
 */

static STDMETHODIMP_(ULONG) Enum_Release(IEnumVARIANT *this)
{
	if (--((MyRealIEnumVariant *)this)->count) return(((MyRealIEnumVariant *)this)->count);

	// NOTE: We don't free our PortsList. That list is freed by freePortsCollection()
	// when we're done with that list (ie, when our DLL terminates).

	// Free the MyRealIEnumVariant
	GlobalFree(this);

	// One less outstanding object
	InterlockedDecrement(&OutstandingObjects);
	
	return(0);
}

/************************ Next() ************************
 * Retrieves the next "numItemsDesired" items in our list
 * of items, and stores them in the caller's "rgVar" array
 * of VARIANTs.
 *
 * numItemsDesired =	How many items the caller wants
 *						returned.
 * rgVar =				An array of VARIANTs where we
 *						return those items.
 * numFetched =			Where we return the count of actual
 *						items returned.
 */

static STDMETHODIMP Next(IEnumVARIANT *this, ULONG numItemsDesired, VARIANT *rgVar, ULONG *numFetched)
{
	HRESULT				hr;
	register DWORD		count;
	register IENUMITEM	*item;

	// Assume we'll return no items, so set array to nothing
	ZeroMemory(&rgVar[0], numItemsDesired * sizeof(VARIANT));

	// Locate to where the caller last left off in PortsList
	item = PortsList;
	count = ((MyRealIEnumVariant *)this)->lastFetched;
	while (item && count--) item = item->next;

	// Assume no items left
	hr = S_FALSE;
	count = 0;

	// Any more items left?
	if (item)
	{
		// Retrieve the next numItemsDesired items
		while (item && numItemsDesired--)
		{
			// If what we're returning to the caller is an object, we must AddRef()
			// it on the caller's behalf. The caller is expected to Release() it
			// when done with it. If what we're returning is a BSTR, then we must
			// SysAllocString a copy of it. Caller is expected to SysFreeString it.
			// Other datatypes are simply copied to the caller's VARIANT as is.
			// VariantCopy() does all this for us
			VariantCopy(&rgVar[count], &item->value);

			// Another item fetched
			++count;
		}

		// Update where the caller has enumerated to within our list
		((MyRealIEnumVariant *)this)->lastFetched += count;

		// Return S_OK
		hr = S_OK;
	}

	// Set count of items retrieved
	if (numFetched) *numFetched = count;

	return(hr);
}

/************************ Skip() ************************
 * Skips over the next "numItems" items in our list
 * of items.
 *
 * numItems =	How many items the caller wants skipped.
 */

static STDMETHODIMP Skip(IEnumVARIANT *this, ULONG numItems)
{
	register DWORD		count;
	register IENUMITEM	*item;

	count = 0;
	item = PortsList;

	// Skip the next numItems items
	while (item && numItems--)
	{
		// Another item skipped
		++count;
	}

	// Update where the caller has enumerated to within our list
	((MyRealIEnumVariant *)this)->lastFetched += count;

	return(S_OK);
}

/************************ Reset() ************************
 * Resets back to the beginning of our list of items.
 */

static STDMETHODIMP Reset(IEnumVARIANT *this)
{
	((MyRealIEnumVariant *)this)->lastFetched = 0;
	return(S_OK);
}

/************************ Clone() ************************
 * Creates a copy of the IEnumVARIANT.
 */

static STDMETHODIMP Clone(IEnumVARIANT *this, IEnumVARIANT **ppEnum)
{
	register IEnumVARIANT	*copy;

	// Allocate a new IENUMVARIANT
	if (!(*ppEnum = copy = allocIEnumVARIANT()))
		return(E_OUTOFMEMORY);

	// Copy the lastFetched position
	((MyRealIEnumVariant *)copy)->lastFetched = ((MyRealIEnumVariant *)this)->lastFetched;

	// Return the new IEnumVARIANT
	*ppEnum = copy;

	return(S_OK);
}

//==============================================================
//============== My IEnumVARIANT Object's VTable ===============
//==============================================================
// Since my MyRealIEnumVariant object is just a standard IEnumVARIANT COM
// object (with some extra fields appended to it), its VTable is
// simply a IEnumVARIANT VTable (already defined for us by Microsoft).
// An IEnumVARIANT's VTable begins with the IUnknown functions (as all
// COM objects do), and then the functions that implement the
// enumeration (ie, Next, Skip, Reset, and Clone)
static const IEnumVARIANTVtbl IEnumVariantTable = {Enum_QueryInterface,
Enum_AddRef,
Enum_Release,
Next,
Skip,
Reset,
Clone};

/************************ allocIEnumVARIANT() ******************
 * This is not a function in my IEnumVARIANT's VTable. It's a
 * helper function that allocates/initializes an IEnumVARIANT
 * (actually an MyRealIEnumVariant) object.
 *
 * RETURNS: Pointer to IEnumVARIANT if success, or 0 if a
 * memory failure.
 */

static IEnumVARIANT * allocIEnumVARIANT(void)
{
	MyRealIEnumVariant	*enumVariant;

	// Allocate an MyRealIEnumVariant object
	if ((enumVariant = (MyRealIEnumVariant *)GlobalAlloc(GMEM_FIXED, sizeof(MyRealIEnumVariant))))
	{
		// Set the IEnumVARIANT VTable
		enumVariant->enumVariant.lpVtbl = (IEnumVARIANTVtbl *)&IEnumVariantTable;

		// AddRef() the IEnumVARIANT for the caller
		enumVariant->count = 1;

		// Initially at the start of the list
		enumVariant->lastFetched = 0;

		// One more outstanding object
		InterlockedIncrement(&OutstandingObjects);
	}

	return((IEnumVARIANT *)enumVariant);
}











//====================================================================
//===================== ICollection functions ========================
//====================================================================

// ICollection's AddRef()
static STDMETHODIMP_(ULONG) Collection_AddRef(ICollection *this)
{
	return(++((MyRealICollection *)this)->count);
}

// ICollection's QueryInterface()
static STDMETHODIMP Collection_QueryInterface(ICollection *this, REFIID riid, void **ppvObj)
{
	if (!ppvObj) return(E_POINTER);

	// It can masquerade as an IUnknown or an IDispatch. Of course, it really has an
	// ICollection VTable, so if anyone passed the GUID we associated with that,
	// then we confirm it. But since no other entity beside our DLL should know
	// anything about an ICollection (ie, we tell the outside world only that it's
	// an IDispatch), we shouldn't need to check for this GUID
	if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IDispatch) /* || IsEqualIID(riid, &IID_ICollection) */)
	{
		*ppvObj = this;
		Collection_AddRef(this);
		return(NOERROR);
	}

	*ppvObj = 0;
	return(E_NOINTERFACE);
}

// ICollection's Release()
static STDMETHODIMP_(ULONG) Collection_Release(ICollection *this)
{
	if (--((MyRealICollection *)this)->count) return(((MyRealICollection *)this)->count);

	// NOTE: We don't free our PortsList. That list is freed by freePortsCollection()
	// when we're done with that list (ie, when our DLL terminates).

	// Free the MyRealICollection
	GlobalFree(this);

	// One less outstanding object
	InterlockedDecrement(&OutstandingObjects);
	
	return(0);
}

// This is just a helper function for the IDispatch functions below
static HRESULT loadCollectionTypeInfo(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 ICollection, by passing that GUID
		if (!(hr = pTypeLib->lpVtbl->GetTypeInfoOfGuid(pTypeLib, &IID_ICollection, &CollectionTypeInfo)))
		{
			// 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
			CollectionTypeInfo->lpVtbl->AddRef(CollectionTypeInfo);
		}
	}

	return(hr);
}

// ICollection's GetTypeInfoCount()
static ULONG STDMETHODCALLTYPE GetTypeInfoCount(ICollection *this, UINT *pCount)
{
	// We do have type library information for our ICollection object, so return 1
	*pCount = 1;
	return(S_OK);
}

// ICollection's GetTypeInfo(). The caller uses this to get ahold of an ITypeInfo
// object that contains information about the extra functions in our ICollection's
// VTable (ie, Count, Item, and _NewEnum).
static ULONG STDMETHODCALLTYPE GetTypeInfo(ICollection *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 ITypeInfo and compare it to what the caller wants.
	// If no match, unloaded the currently created ITypeInfo, and create the correct one. But since
	// we support only one language in our IDL file anyway, we'll ignore this
	else if (CollectionTypeInfo)
	{
		CollectionTypeInfo->lpVtbl->AddRef(CollectionTypeInfo);
		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 = loadCollectionTypeInfo();
	}

	if (!hr) *pTypeInfo = CollectionTypeInfo;

	return(hr);
}

// ICollection's GetIDsOfNames(). This is used to get the DISPID for any
// one of the extra functions in our ICollection's VTable (ie, Count,
// Item, and _NewEnum).
// NOTE: Since we MUST give the DISPID of DISPID_VALUE to the Item()
// function, and we MUST give the DISPID of DISPID_NEWENUM to the
// _NewEnum() function, the app should already know these two DISPIDs.
// Therefore, there's no reason for him to call GetIDsOfNames to retrieve
// the DISPID of "Item". But if he did pass rgszNames = "Item", we're
// return  DISPID_VALUE. But if the host wants to call the Count()
// function, he'll definitely need GetIDsOfNames to retrieve Count()'s
// DISPID. That DISPID could be any positive number.
static ULONG STDMETHODCALLTYPE GetIDsOfNames(ICollection *this, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgdispid)
{
	if (!CollectionTypeInfo)
	{
		register HRESULT	hr;

		if ((hr = loadCollectionTypeInfo())) 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 ICollection
	return(DispGetIDsOfNames(CollectionTypeInfo, rgszNames, cNames, rgdispid));
}

// ICollection's Invoke(). This is used to indirectly call the extra
// functions in our ICollection's VTable (ie, Count, Item, and _NewEnum)
static ULONG STDMETHODCALLTYPE Invoke(ICollection *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);

	if (!CollectionTypeInfo)
	{
		register HRESULT	hr;

		if ((hr = loadCollectionTypeInfo())) 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, CollectionTypeInfo, dispid, wFlags, params, result, pexcepinfo, puArgErr));
}

// ICollection's Count(). Gets a count of items in the collection
static STDMETHODIMP Count(ICollection *this, long *total)
{
	register DWORD		count;
	register IENUMITEM	*item;

	// Count how many items in the list by just walking it all
	// the way to the last IENUMITEM, incrementing a count for
	// each item
	count = 0;
	item = (IENUMITEM *)&PortsList;
	while ((item = item->next)) ++count;

	// Return the total
	*total = count;

	return(S_OK);
}

// ICollection's Item(). Retrieves the (value of the) item at the
// specified (0-based) index within the collection.
// NOTE: Our IDL file must assign this a DISPID of DISPID_VALUE
static STDMETHODIMP Item(ICollection *this, long index, VARIANT *ret)
{
	register IENUMITEM	*item;

	ret->vt = VT_EMPTY;

	// Locate to the item that the caller wants
	item = PortsList;
	while (item && index--) item = item->next;

	// Any more items left?
	if (item)
	{
		// Copy the item's value to the VARIANT that the caller supplied.
		// If what we're returning to the caller is an object, we must AddRef()
		// it on the caller's behalf. The caller is expected to Release() it
		// when done with it. If what we're returning is a BSTR, then we must
		// SysAllocString a copy of it. Caller is expected to SysFreeString it.
		// Other datatypes are simply copied to the caller's VARIANT as is.
		// VariantCopy() does all this for us. It also returns S_OK if all
		// went well
		return(VariantCopy(ret, &item->value));
	}

	// If no more items, return S_FALSE
	return(S_FALSE);
}

// ICollection's _NewEnum(). This allocates/returns an IEnumVARIANT object
// to keep track of the current position within our collection's list.
// NOTE: Our IDL file must assign this a DISPID of DISPID_NEWENUM
static STDMETHODIMP _NewEnum(ICollection *this, IUnknown **enumObj)
{
	register IEnumVARIANT	*enumVariant;

	// Allocate and initialize an IEnumVARIANT. Caller
	// is responsible for Release()'ing it
	if (!(enumVariant = allocIEnumVARIANT())) return(E_OUTOFMEMORY);
	*enumObj = (IUnknown *)enumVariant;
	return(S_OK);
}

// Our ICollection object's VTable. Note that although the Count, Item,
// and _NewEnum functions are in the VTable, we're not going to let
// any app know that. The app knows about only the first 7 (IUnknown and
// IDispatch) functions. To call Count, Item, or _NewEnum, the app will
// have to indirectly call those functions through the Invoke function,
// passing the correct DISPID
static const ICollectionVtbl ICollectionVTable = {Collection_QueryInterface,
Collection_AddRef,
Collection_Release,
GetTypeInfoCount,
GetTypeInfo,
GetIDsOfNames,
Invoke,
Count,
Item,
_NewEnum};









// This is just a helper function to allocate/initialize an IDispatch
// (really, a MyRealICollection) object for our PortsList.
IDispatch * allocPortsCollection(void)
{
	register MyRealICollection	*collection;

	// Allocate an ICollection object (actually, a MyRealICollection)
	if ((collection = (MyRealICollection *)GlobalAlloc(GMEM_FIXED, sizeof(MyRealICollection))))
	{
		// Store its VTable
		collection->lpVtbl = (ICollectionVtbl *)&ICollectionVTable;

		// AddRef it
		collection->count = 1;

		// Indicate another outstanding object since we'll be returning it
		// to an application which is expected to Release() it
		InterlockedIncrement(&OutstandingObjects);
	}

	// Return it as if it were an IDispatch (which it can be used as)
	return((IDispatch *)collection);
}

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