Click here to Skip to main content
15,894,180 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.6K   2K   83  
An ActiveX Script Host with custom COM objects. This allows a script to call C functions in your app.
// This is a C example that tests the IExampleEvts2 COM component (in IExampleEvts2.dll).
//
// IExampleEvts2.DLL has a COM component known as an IExampleEvts2. This component
// also has an IConnectionPointContainer sub-object. So IExampleEvts2 supports us
// giving it some object that wraps some of our callback functions. Specifically,
// IExampleEvts2.h defines an IFeedback2 object we can create to wrap our callback
// functions. IFeedback2 contains one callback functions we can write. IFeedback2's
// VTable uses the name "Compare" for that function's pointer in the VTable.
// We'll use the same name for the function itself.

#include <windows.h>
#include <objbase.h>
#include <activscp.h>
#include <stdio.h>
#include "../IExampleEvts2/IExampleEvts2.h"


// For holding the ITypeInfo for IFeedback2's VTable
static ITypeInfo	*MyTypeInfo;


// IExampleEvts2 supports us giving it only 1 IFeedback2 object, so for simplicity, we
// can just declare it as a global. That way, we don't need to allocate it, free it,
// nor maintain a reference count for it. Here's our 1 IFeedback2 object.
static IFeedback2		MyCompare2;


static HRESULT STDMETHODCALLTYPE QueryInterface(IFeedback2 *this, REFIID vTableGuid, void **ppv)
{
	// This is an IFeedback2 object, so we must recognize IFeedback2 VTable's GUID,
	// which is defined for us in IExampleEvts2.h. Our IFeedback2 can also masquerade
	// as an IUnknown
	if (!IsEqualIID(vTableGuid, &IID_IUnknown) && !IsEqualIID(vTableGuid, &DIID_IFeedback2))
	{
		*ppv = 0;
		return(E_NOINTERFACE);
	}

	*ppv = this;

	// Normally, we'd call our AddRef function here. But since our IFeedback2 isn't
	// allocated, we don't need to bother with reference counting
	return(NOERROR);
}

static ULONG STDMETHODCALLTYPE AddRef(IFeedback2 *this)
{
	// Our one and only IFeedback2 isn't allocated. Instead, it is statically
	// declared above (MyCompare2). So we'll just return a 1
	return(1);
}

static ULONG STDMETHODCALLTYPE Release(IFeedback2 *this)
{
	return(1);
}


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

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

	// Load IExampleEvts2 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 the loaded type library. We only
		// need one of these, and we'll store it in a global Tell Microsoft this is for
		// our IFeedback2's VTable, by passing that VTable's GUID
		if (!(hr = pTypeLib->lpVtbl->GetTypeInfoOfGuid(pTypeLib, &DIID_IFeedback2, &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);
}

// IFeedback2's GetTypeInfoCount()
static ULONG STDMETHODCALLTYPE GetTypeInfoCount(IFeedback2 *this, UINT *pCount)
{
	// Nobody should be calling our GetTypeInfoCount function, because this
	// is a callback object. The only person that will be calling our
	// IFeedback2's functions is the IExampleEvts2 object in IExampleEvts2.DLL.
	// And it ought to know what functions are in our IFeedback2 (since it
	// defined the VTable). So it doesn't need to get ahold of a ITypeInfo
	// for its own IFeedback2 VTable definition
	*pCount = 0;
	return(S_OK);
}

// IFeedback2's GetTypeInfo()
static ULONG STDMETHODCALLTYPE GetTypeInfo(IFeedback2 *this, UINT itinfo, LCID lcid, ITypeInfo **pTypeInfo)
{
	// Nobody should be calling our GetTypeInfo function, because this
	// is a callback object. The only person that will be calling our
	// IFeedback2's functions is the IExampleEvts2 object in IExampleEvts2.DLL.
	// And it ought to know what functions are in our IFeedback2 (since it
	// defined the VTable). So it doesn't need to get ahold of a ITypeInfo
	// for its own IFeedback2 VTable definition
	*pTypeInfo = 0;
	return(E_NOTIMPL);
}

// IFeedback2's GetIDsOfNames()
static ULONG STDMETHODCALLTYPE GetIDsOfNames(IFeedback2 *this, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgdispid)
{
	// Nobody should be calling our GetIDsOfNames function, because this
	// is a callback object. The only person that will be calling our
	// IFeedback2's functions is the IExampleEvts2 object in IExampleEvts2.DLL.
	// And it ought to know what functions are in our IFeedback2 (since it
	// defined the VTable), and what are the DISPIDs of those functions.
	// So it doesn't need us to return the DISPID for one of IFeedback2's
	// function names
	return(E_NOTIMPL);
}

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

// ================== Extra (callback) functions

static HRESULT STDMETHODCALLTYPE Callback1(IFeedback2 *this)
{
	printf("Callback1 is called.\n");
	return(S_OK);
}

static HRESULT STDMETHODCALLTYPE Callback2(IFeedback2 *this)
{
	printf("Callback2 is called.\n");
	return(S_OK);
}

static HRESULT STDMETHODCALLTYPE Callback3(IFeedback2 *this)
{
	printf("Callback3 is called.\n");
	return(S_OK);
}

static HRESULT STDMETHODCALLTYPE Callback4(IFeedback2 *this)
{
	printf("Callback4 is called.\n");
	return(S_OK);
}

static HRESULT STDMETHODCALLTYPE Callback5(IFeedback2 *this)
{
	printf("Callback5 is called.\n");
	return(S_OK);
}

// Our IFeedback2 VTable. We need only one of these, so we can
// declare it static
static const IFeedback2Vtbl IFeedback2_Vtbl = {QueryInterface,
AddRef,
Release,
GetTypeInfoCount,
GetTypeInfo,
GetIDsOfNames,
Invoke,
// Although IExampleEvts2.IDL does not use the "dual" keyword on the
// IFeedback2 VTable, we're nevertheless going to include the extra
// functions in our VTable (as if it were "dual"). In the way, we
// can use DispInvoke for our Invoke function above
Callback1,
Callback2,
Callback3,
Callback4,
Callback5};




int main(int argc, char **argv)
{
	IExampleEvts2				*exampleObj;
	IConnectionPointContainer	*container;
	IConnectionPoint			*point;
	HRESULT						hr;
	DWORD						cookie;

	// Although we don't have to allocate our IFeedback2, we do need to
	// initialize it, by storing its VTable in its lpVtbl member
	MyCompare2.lpVtbl = (IFeedback2Vtbl *)&IFeedback2_Vtbl;

	MyTypeInfo = 0;

	if (!CoInitialize(0))
	{
		// Get an IExampleEvts2 object
		if ((hr = CoCreateInstance(&CLSID_IExampleEvts2, 0, CLSCTX_ALL, &IID_IExampleEvts2, &exampleObj)))
			MessageBox(0, "Can't create IExampleEvts2 object", "CreateInstance error", MB_OK|MB_ICONEXCLAMATION);
		else
		{
			// Get IExampleEvts2' IConnectionPointContainer sub-object. We do this by calling
			// IExampleEvts2' QueryInterface, and pass it the standard GUID for an
			// IConnectionPointContainer VTable
			if ((hr = exampleObj->lpVtbl->QueryInterface(exampleObj, &IID_IConnectionPointContainer, &container)))
				MessageBox(0, "Can't get IConnectionPointContainer object", "QueryInterface error", MB_OK|MB_ICONEXCLAMATION);
			else
			{
				// Get IExampleEvts2's IConnectionPoint sub-object for specifically giving
				// IExampleEvts2 our IFeedback2. We do this by calling IConnectionPointContainer's
				// FindConnectionPoint, and pass it IFeedback2 VTable's GUID (defined for us in
				// IExampleEvts2.h)
				hr = container->lpVtbl->FindConnectionPoint(container, &DIID_IFeedback2, &point);

				// We don't need the IConnectionPointContainer, now that we got the one IConnectionPoint
				// we want (ie, the one we use to give IExampleEvts2 our IFeedback2)
				container->lpVtbl->Release(container);

				if (hr)
					MessageBox(0, "Can't get IConnectionPoint object", "FindConnectionPoint error", MB_OK|MB_ICONEXCLAMATION);
				else
				{
					// Now call the IConnectionPoint's Advise function, giving it some object
					// whose QueryInterface it will call to get our IFeedback2 object. Let's
					// just give it our IFeedback2 object. So Advise will call our IFeedback2's
					// QueryInterface to tell us to give it a pointer to our IFeedback2. Dumb?
					// You bet. 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.
					//
					// Advise() gives us back a "cookie" value that we'll need to later pass to
					// Unadvise() (when we want to tell IExampleEvts2 to stop using our IFeedback2)
					if ((hr = point->lpVtbl->Advise(point, (IUnknown *)&MyCompare2, &cookie)))
						MessageBox(0, "Can't set our IFeedback2 object", "Advise error", MB_OK|MB_ICONEXCLAMATION);
					else
					{
						DWORD	i;

						// Call IExampleEvts2' DoSomething() function 10 times.
						for (i=0; i < 10; i++)
							exampleObj->lpVtbl->DoSomething(exampleObj);

						// Tell IExampleEvts2 to stop using our IFeedback2 (and Release it). We pass
						// the cookie value we got from Advise() above
						if ((hr = point->lpVtbl->Unadvise(point, cookie)))
							MessageBox(0, "Can't unset our IFeedback2 object", "Unadvise error", MB_OK|MB_ICONEXCLAMATION);

						// Release the IConnectionPoint now that we're done with it (and
						// we've told IExampleEvts2 to stop calling our IFeedback2 functions)
						point->lpVtbl->Release(point);
					}
				}

			}

			// Release the IExampleEvts2 now that we're done with it
			exampleObj->lpVtbl->Release(exampleObj);
		}

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

		// When finally done with OLE, free it
		CoUninitialize();
	}
	else
		MessageBox(0, "Can't initialize COM", "CoInitialize error", MB_OK|MB_ICONEXCLAMATION);

	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