Click here to Skip to main content
15,886,873 members
Articles / Programming Languages / C

COM in plain C, Part 7

Rate me:
Please Sign up or sign in to vote.
5.00/5 (15 votes)
8 Aug 2006CPOL13 min read 93.4K   2K   83  
An ActiveX Script Host with custom COM objects. This allows a script to call C functions in your app.
// This file contains the functions for our IENUMVARIANT (an IEnumVARIANT)
// object. This object allows an app to enumerate through a linked list of
// IENUMITEM items. It also contains the functions for our ICollection
// object which simply wraps the IENUMVARIANT and linked list of IENUMITEMs.

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


static IEnumVARIANT * allocIENUMVARIANT(MyRealICollection *);








//==============================================================
//========== Functions to create/delete IENUMITEMs =============
//==============================================================

/************************ allocIENUMITEM() ******************
 * Allocates/initializes an IENUMITEM struct.
 *
 * value =	The value to be stored by the IENUMITEM. NOTE: We
 *			do not make a copy of this VARIANT's value, but
 *			rather, copy it verbatim.
 *
 * RETURNS: Pointer to IENUMITEM if success, or 0 if a
 * memory failure.
 *
 * NOTE: If the passed VARIANT is storing an object, it must
 * have been AddRef()'ed. If a BSTR, it must have been allocated
 * with one of the SysAlloc* functions.
 */

IENUMITEM * allocIENUMITEM(VARIANT *value)
{
	register IENUMITEM	*enumItem;

	// Allocate an IENUMITEM object
	if ((enumItem = (IENUMITEM *)GlobalAlloc(GMEM_FIXED, sizeof(IENUMITEM))))
	{
		enumItem->next = 0;

		// Store the value
		CopyMemory(&enumItem->value, value, sizeof(VARIANT));
	}

	return(enumItem);
}

/************************ freeOneItem() ******************
 * Frees the specified IENUMITEM from the specified list.
 *
 * removeItem =	The IENUMITEM to remove and free.
 * itemList =	Head of the list.
 */

void freeOneItem(IENUMITEM *removeItem, IENUMITEM **itemList)
{
	register IENUMITEM	*item;
	register IENUMITEM	*parent;

	parent = (IENUMITEM *)itemList;		// This works because the "next"
										// member is first in an IENUMITEM
	while ((item = parent->next))
	{
		// Is this the item we want to remove?
		if (item == removeItem)
		{
			// Free the item's value. If the item is storing an object, we
			// must Release() it. If the item is storing a BSTR, we must
			// SysFreeString() it. VariantClear does these things
			VariantClear(&item->value);

			// Unlink it from the list
			parent->next = item->next;

			// Free the IENUMITEM
			GlobalFree(item);

			// Done
			break;
		}

		// Keep searching the items
		parent = item;
	}
}

/************************ freeAllItems() ******************
 * Frees the list of IENUMITEMs.
 *
 * itemList =	Head of the list.
 */

void freeAllItems(IENUMITEM **itemList)
{
	register IENUMITEM	*item;
	register IENUMITEM	*nextitem;

	nextitem = *itemList;

	// Zero out the list
	*itemList = 0;

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

		VariantClear(&item->value);

		// NOTE: We don't bother unlinking from the list,
		// because we already zero'ed out the list above

		GlobalFree(item);
	}
}










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

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

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

/********************* 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 (--((IENUMVARIANT *)this)->refCount) return(((IENUMVARIANT *)this)->refCount);

	// NOTE: We don't free the list referenced by this object. That list
	// is freed by some other function when we're done with that list. For
	// example, when we're done with our list of port names, we'll call
	// freePortsCollection().

	// Free the IENUMVARIANT
	GlobalFree(this);

	// One less outstanding object
	decOutstandingObjects();
	
	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 return count and array to nothing
    if (numFetched) *numFetched = 0;
 	for (count = 0; count < numItemsDesired; count++) rgVar[count].vt = VT_EMPTY;

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

	// Assume no items left
	hr = S_FALSE;

	// Any more items left?
	if (item)
	{
		count = 0;

		// 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;
		}

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

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

		// If more items left, then return S_OK
		if (item) hr = S_OK;
	}

	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 = *(((IENUMVARIANT *)this)->collection->list);

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

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

	return(S_OK);
}

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

static STDMETHODIMP Reset(IEnumVARIANT *this)
{
	((IENUMVARIANT *)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(((IENUMVARIANT *)this)->collection)))
		return(E_OUTOFMEMORY);

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

	// Return the new IEnumVARIANT
	*ppEnum = copy;

	return(S_OK);
}

//==============================================================
//============== My IENUMVARIANT Object's VTable ===============
//==============================================================
// Since my IENUMVARIANT 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 object. It's a
 * helper function that allocates/initializes an IENUMVARIANT
 * object.
 *
 * RETURNS: Pointer to IENUMVARIANT if success, or 0 if a
 * memory failure.
 */

static IEnumVARIANT * allocIENUMVARIANT(MyRealICollection *collection)
{
	IENUMVARIANT	*enumVariant;

	// Allocate an IENUMVARIANT object
	if ((enumVariant = (IENUMVARIANT *)GlobalAlloc(GMEM_FIXED, sizeof(IENUMVARIANT))))
	{
		ZeroMemory(enumVariant, sizeof(IENUMVARIANT));

		// Set the IEnumVARIANT VTable
		enumVariant->enumVariant.lpVtbl = (IEnumVARIANTVtbl *)&IEnumVariantTable;

		// Store the MyRealICollection to which this IENUMVARIANT belongs
		enumVariant->collection = collection;

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

		// One more outstanding object
		incOutstandingObjects();
	}

	return((IEnumVARIANT *)enumVariant);
}














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

// 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);
}



//====================================================================
//===================== 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 the list referenced by this object. That list
	// is freed by some other function when we're done with that list. For
	// example, when we're done with our list of port names, we'll call
	// freePortsCollection().

	// Free the MyRealICollection
	GlobalFree(this);

	// One less outstanding object
	decOutstandingObjects();
	
	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 *)(((MyRealICollection *)this)->list);
	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 = *(((MyRealICollection *)this)->list);
	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((MyRealICollection *)this))) 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
const ICollectionVtbl ICollectionVTable = {Collection_QueryInterface,
Collection_AddRef,
Collection_Release,
GetTypeInfoCount,
GetTypeInfo,
GetIDsOfNames,
Invoke,
Count,
Item,
_NewEnum};


// This helper function allocates an ICollection (actually a
// MyRealICollection) and stores the passed list handle in it.
//
// RETURNS: Pointer to the ICollection (masquerading as an
// IDispatch) if success, or 0 if a memory failure.

IDispatch * allocICollection(IENUMITEM **list)
{
	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;

		// Store a handle to the list
		collection->list = list;

		// One more outstanding object
		incOutstandingObjects();
	}

	// 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