Click here to Skip to main content
15,896,111 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 IExample3 COM component (in IExample3.dll).

#include <windows.h>
#include <objbase.h>
#include <stdio.h>
#include "../IExample3/IExample3.h"

int main(int argc, char **argv)
{
	IExample3		*exampleObj;
	IClassFactory	*classFactory;
	HRESULT			hr;

	// We must initialize OLE before we do anything with COM objects. NOTE:
	// some COM components, such as the IE browser engine, require that you
	// use OleInitialize() instead. But our COM component doesn't require this
	if (!CoInitialize(0))
	{
		// Get IExample3.DLL's IClassFactory
		if ((hr = CoGetClassObject(&CLSID_IExample3, CLSCTX_INPROC_SERVER, 0, &IID_IClassFactory, &classFactory)))
			MessageBox(0, "Can't get IClassFactory", "CoGetClassObject error", MB_OK|MB_ICONEXCLAMATION);
		else
		{
			// Create an IExample3 object
			if ((hr = classFactory->lpVtbl->CreateInstance(classFactory, 0, &IID_IExample3, &exampleObj)))
			{
				classFactory->lpVtbl->Release(classFactory);
				MessageBox(0, "Can't create IExample3 object", "CreateInstance error", MB_OK|MB_ICONEXCLAMATION);
			}
			else
			{

				// Release the IClassFactory. We don't need it now that we have the one
				// IExample3 we want
				classFactory->lpVtbl->Release(classFactory);






				//==========================================================================
				// STUDY THIS
				//==========================================================================
				{
				// Get the IDispatch object for the Ports collection, and stuff it into our variable "portsObj".
				// NOTE: What our DLL is really returning is a MyRealICollection object. But this app doesn't
				// know that. All we know here is that this object is an IDispatch. So its VTable has the 3
				// IUnknown functions (QueryInterface, AddRef, and Release) and the 4 IDispatch functions
				// (GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, and Invoke). And that's all as far as we're
				// concerned. If the object has any other functions, we can't call them directly by referencing
				// lpVtbl. We have to call those extra functions indirectly via the Invoke function, as we'll
				// see below
				IDispatch			*portsObj;

				if ((hr = exampleObj->lpVtbl->GetPorts(exampleObj, &portsObj)))
					MessageBox(0, "Can't get the Ports collection (IDispatch) object", "GetPorts error", MB_OK|MB_ICONEXCLAMATION);
				else
				{
					VARIANT			ret;
					ULONG			count, i;
					DISPID			dispid;
					OLECHAR			*funcName;

					funcName = (OLECHAR *)L"Count";

					// In order to call our collection's Count function, we have to do it indirectly by calling our
					// collection's Invoke function. This is a pain in the ass. Thank you, Microsoft's Visual Basic
					// team. First, we need to discover the DISPID (number) associated with the Count function. We
					// do this by calling the collection's GetIDsOfNames function. We pass it a handle to a
					// unicode string of the function name we want (ie, L"Count"). We also pass it a pointer to some
					// variable where it returns the DISPID
					if ((hr = portsObj->lpVtbl->GetIDsOfNames(portsObj, &IID_NULL, &funcName, 1, LOCALE_USER_DEFAULT, &dispid)))
						MessageBox(0, "Can't get Count()'s DISPID", "GetIDsOfNames error", MB_OK|MB_ICONEXCLAMATION);
					else
					{
						// Ok, now we have the DISPID for the Count function. It's stored in our variable "dispid".
						//
						// We can now call Invoke to indirectly call Count. This is going to be more of a pain in
						// the ass because we have to initialize and pass a Visual Basic DISPPARAMS struct. And
						// if the Count function is passed any args, we need to set this DISPPARMS rgvarg member to
						// point to an array of VARIANT structs -- one per each arg we need to pass to Count.
						// Fortunately, the Count function is passed no args, so we can simply zero out the DISPPARAMS
						DISPPARAMS	dspp;

						ZeroMemory(&dspp, sizeof(DISPPARAMS));

						// Now we can actually call Invoke. We pass the DISPID for the Count function. This
						// causes Invoke to call Count. We also pass a VARIANT where we want Count to return
						// whatever our IDL file marks as its [out, retval]. In this case, the VARIANT is
						// going to store a long value which is a count of how many items are in the collection.
						// Because the Count function is marked as [propget] in our IDL, we need to indicate
						// this by specifying the DISPATCH_PROPERTYGET flag. Oh, and we need to initialize the
						// VARIANT first, by calling VariantInit
						VariantInit(&ret);
						if ((hr = portsObj->lpVtbl->Invoke(portsObj, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD|DISPATCH_PROPERTYGET, &dspp, &ret, 0, 0)))
							MessageBox(0, "Can't get a count of items", "Invoke/Count error", MB_OK|MB_ICONEXCLAMATION);
						else
						{
							VARIANT		args[1];

							// Pull the count out of the return VARIANT. To be perfectly safe, we really should
							// check the VARIANT's vt member to make sure it's VT_I4 (ie, a long was stuffed into
							// it). After all, theoretically we could have been returned a VT_BSTR of L"100" (if
							// there were 100 items). Then, we'd have use atoi on the VARIANT's bstrVal member.
							// And after that, we'd have to SysFreeString our VARIANT's bstrVal. But, we're going
							// to assume that the Count function didn't do something totally stupid (even though
							// MS Visual Basic programmers thought this whole thing up, and if one of them had
							// written our COM DLL... well...)
							count = ret.lVal;

							// If the Count function returned a VT_BSTR in our VARIANT, we'd have to SysFreeString
							// our VARIANT's bstrVal. If the Count function returned a VT_UNKNOWN or VT_DISPATCH,
							// we'd have to Release() on the VARIANT's punkVal. A call to VariantClear() does this
							// for us. It also resets the VARIANT's vt member to VT_EMPTY so we can reuse it on
							// the following call to Invoke (when we indirectly call the Item function)
							VariantClear(&ret);

							// Now let's do a loop, calling Item (indirectly via Invoke) to fetch (and display)
							// each port name. Normally, we'd have to first call GetIDsOfNames again, this time
							// passing the string L"Item" to get the DISPID for the Item function. But we know
							// that it must have a DISPID of DISPID_VALUE. So we can simply pass DISPID_VALUE to
							// Invoke in order to indirectly call Item().
							//
							// For passing any args to Item, we need a DISPPARAMS. We'll just reuse the same
							// DISPPARAMS we used above.
							//
							// And we do have to pass 1 arg to Item (a long indicating which item we want
							// fetched), so we need to fill in the array of 1 VARIANT struct. First we set the
							// DISPPARMS cArgs member to 1 to indicate we're passing only one arg. Now
							// we need to set its rgvarg member to point to a VARIANT. And finally, we need
							// to fill in this VARIANT's lVal with the item number we wish to fetch, and set
							// the vt member to VT_I4 (because we're stuffing a long value into the VARIANT).
							VariantInit(&args[0]);
							ZeroMemory(&dspp, sizeof(DISPPARAMS));
							dspp.cArgs = 1;
							dspp.rgvarg = &args[0];
							args[0].vt = VT_I4;

							for (i = 0; i < count; i++)
							{
								// Indicate (to the Item function) which item's port name we want
								args[0].lVal = i;

								// Call Invoke to indirectly call Item. We pass a DISPID of DISPID_VALUE. This
								// causes Invoke to call Item. We also pass a VARIANT where we want Item to return
								// the value of the item we requested. In this case, the VARIANT is going to return
								// a BSTR of that item's port name. Because the Item function is marked as [propget]
								// in our IDL, we need to indicate this by specifying the DISPATCH_PROPERTYGET flag
								if ((hr = portsObj->lpVtbl->Invoke(portsObj, DISPID_VALUE, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD|DISPATCH_PROPERTYGET, &dspp, &ret, 0, 0)))
								{
									MessageBox(0, "Can't get next item's port name", "Invoke/Item error", MB_OK|MB_ICONEXCLAMATION);
									break;
								}
								else
								{
									// Display the port name. NOTE: It's a unicode string
									printf("%S\n", ret.bstrVal);

									// Call to VariantClear() to SysFreeString that BSTR, and also reset the
									// VARIANT's vt member to VT_EMPTY so we can reuse it on the next call
									// to Invoke (when we indirectly call the Item function again)
									VariantClear(&ret);
								}
							}
						}
					}

					// Release the Ports collection IDispatch now that we're done with it
					portsObj->lpVtbl->Release(portsObj);
				}
				}
				//==========================================================================










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

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