Creating the COM Client in C

Back to the Main Page

The good news is that creating the client in C is simple, in comparison with creating the server. A basic outline of what to do is:

  1. Create the WinMain Function
  2. Call the OleInitialize Function
  3. Call CoCreateInstance
  4. Call interface methods
  5. Call the Release method
  6. Call OleUninitialize()
Files Used in this Tutorial Purpose
MikeClient.cContains code for steps 1 - 6

1) Create the (Win)Main function

Since we'll be creating a stand-alone executable, the first step is to create a project. Pick either "Windows application", or "Windows console application". Next, define either

int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // pointer to command line
int nCmdShow // show state of window
);

(for the Windows application), or

void main( void )

(for the console applications)

Since we're building a pretty simple client, we won't need any other functions

2) Call OleInitialize()/CoInitialize()

Since we're going to be using OLE/COM function calls, we need to call OleInitialize to set everything up for us. Note that there's also a function called CoInitialize, which initializes COM. OleInitialize calls CoInitialize, so in effect it is a superset of CoInitialize. Notice that the in-proc server doesn't need to call either of these, because our server object was created by a process that must have already called CoInitialize or OleInitialize. There must be a call made to OleInitialize/CoInitialize before using OLE/COM in any way. Each call to OleInitialize/CoInitialize must be matched by a call to OleUninitialize/CoUninitialize sometime after the program has finished using COM, and before it exits. The function calls look like:

WINOLEAPI OleInitialize( LPVOID pvReserved ); /* pvReserved must be NULL for 32 OLE */

HRESULT CoInitialize( LPVOID pvReserved /*pvReserved must be NULL*/);

Both return HRESULTs, and the error cases are pretty self-explainatory

3) Call CoCreateInstance()

Once you're got a program that's initialized the COM facilities, you can create COMponents. Assuming that the server object has been properly written, registered, and placed in the appropriate directory, the client calls CoCreateInstance() to create a single instance of the appropriate object. Looking in the VC++ on-line help, we see that the fuction looks like:

STDAPI CoCreateInstance(
REFCLSID rclsid, /*Class identifier (CLSID) of the object */
LPUNKNOWN pUnkOuter,
/*Pointer to whether object is or isn’t part of an aggregate */
DWORD dwClsContext,
/*Context for creating the object*/
REFIID riid,
/*Reference to the identifier of the interface (a pointer in C, a reference variable in C++*/
LPVOID * ppv
/*Indirect pointer to requested interface if successful, NULL otherwise*/);

rclsid is a pointer in C, and a reference variable in C++ -- either way, it contains the CLSID of the COMponent we want to create. pUnkOuter is used when aggregating objects. Since we're not aggregating the object, it should be set to NULL. dwClsContext is an enumeration that is used to determine where to create the object. This allows the client to control whether the COMponent is allowed to run in the same process space as itself (in-process, or in-proc), on the same machine (local server), or on a remote machine (remote server). If multiple options are acceptable, then you can use bitwise OR ( | ) to combine them. Thus, if you didn't care whether the object server was run on this machine or another machine, but you didn't trust the object server enough to let it run in your process space, you could use
CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER

The enumeration is defined in the VC++ on-line help as:

typedef enum tagCLSCTX {
CLSCTX_INPROC_SERVER = 1,
CLSCTX_INPROC_HANDLER = 2,
CLSCTX_LOCAL_SERVER = 4
CLSCTX_REMOTE_SERVER = 16} CLSCTX;
#define CLSCTX_SERVER (CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER)
#define CLSCTX_ALL (CLSCTX_INPROC_HANDLER | CLSCTX_SERVER)

4) Call interface methods

Now that we've got the object, we need to work it, using our (improvised vtable stuff). If we pass &pIMike as the address of the last argument to CoCreateInstance, then we can invoke methods off of the returned interface with a line similar to:

pIMike->lpVtbl->pfnMikeBeep( pIMike );

Note that lpVtbl appears to be a standard name for the VTable, which is why we chose to use that in our IMike.h file. Note also that since this is C, we're going to have to fake a "this" pointer to every method invocation. In C++, this argument is automatically, invisibly placed there, but in C, we have to do it ourselves. After the "this" pointer (which is always the same as the interface pointer that we're invoking the method off of), we can place any additional arguments, though in this case there are none.

5) Call the Release method

Release is invoked just like any other method. For this example, that will look like

pIMike->lpVtble->pfnRelease( pIMike ): 

6) Call OleUninitialize()

As mentioned in step three above, once we're done using OLE/COM, if we want the OS to reclaim the resources that it's keeping around for COM, we have to call either OleUninitialize() or CoUnintialize(), depending on whether we called Ole- or Co-Initialize(). The Visual C++ on-line help informs us that the functions look like this:

void OleUninitialize();

void CoUninitialize();


All Rights Reserved 1997 by Michael William Panitz (mpanitz@cascadia.ctc.edu)
Home Page
Please note that OLE, COM, ActiveX, book titles, etc are NOT in any way reserved or trademarked by Mike, but instead belong to their resepective owners. The author would also like to gratefully acknowledge the MS Visual C++ help files as being a much appreciated source of information, particularly for function/interface prototypes, data type definitions, etc.