Creating the COM Server in C

Back to the Main Page

Creating an in-proc (in-process, meaning that the server will be located in a DLL, and loaded into the same process /address space as the client) server is a bit involved, though the basic outline of what we'll be doing isn't complex:

 

  1. Create a new project in Visual C++
  2. Define The IMike Interface
  3. Define an object that implements the interface
  4. Create the class factory
  5. Create DLL Entry Points
  6. Build a client to use the newly created object!
  7. Notes On Debugging the Server
  8. Possible Future Directions
Table 1: List of Files Used in this Tutorial
Module:Contains Code that Implements:Located in File Named:
Server classActual server functionality: IUnknown, plus IMike CMike.c
Contains info about the server module that we want to export, namely a definition of CMike, and it's methods CMike.h
Class Factory Creation of the CMike server objectsCMikeCF.c
Info about the class factory we want to export CMikeCF.h
DLL SupportCode for the DLL entry points required by COM to use the DLL as a COM serverCMikeDLL.c
IMike interface definitionThe interface IMikeIMike.h
Utilitiesuseful code that doesn't really go elsewhereUtil.h
 
1) Create a new project
Since we know that the client and the server will be running on the same machine, we'll going to implement the server component as a DLL (Dynamic Link Library) so that the server object will be loaded directly into the address space of the client. Thus, you should create a project that'll compile a DLL for you.

There are ways to make sure that server gets loaded into a separate address space if your client doesn't trust it's server, thus ensuring that you don't have to worry about a COM server crashing your program(see the CoCreateInstance() function). If we were creating a DCOM object, we'd have to either create the server as a stand-alone executable, or as a DLL that would then be loaded into the address space of a surrogate executable on the server machine. The server can't be loaded into the address space of a remote client since the two processes are running on separate machines.


Once you've done that, you can create a bunch of files to hold the various parts needed to implement the server. For this example, we're going to create a really simple COM server that implements an interface that we'll make from scratch, called IMike ( in honor of the creator of this tutorial ). Since we're creating a server class which implements only IMike, we may as well name the class CMike. We'll eventually end up creating a bunch of files, which are listed in Table 1.  Since we're working in C, make sure that all source files end in .C, and not .CPP, if you forget this, Microsoft Visual C++ will compile the source files using C++ rules, which are different enough to cause confusing errors.

2) Define The IMike Interface
There are two parts to defining the interface: defining the actual interface, and creating an interface ID (or IID), to refer to the interface by.
Define the Interface in C:
COM is actually a binary standard: it defines the way that the bytes are arranged within data structures and such, thus allowing compatibility with anything that's willing to arrange it's data likewise. It also happens to utilize the same binary structures as C++, and so an interface is actually a table of function pointers that, in C++, would be the virtual function table (a.k.a. vtable) for an object.We can attain this binary compatibility in C by 'faking' a class in C.
The first thing for us to decide is what we'd like our new interface to do. To keep it simple, it'll store a long integer, and retrieve the stored long integer, in addition to the IUnknown methods that all COM object must support. We'll define the interface that does this in the header file IMike.h
 
typedef struct _IMike
{
struct IMike_Vtbl *lpVtbl;
} IMike;

We then go on to define the virtual function table, like so:
 
struct IMike_Vtbl
{
/*///// The IUnknown methods that all COM objects must support /////*/
HRESULT (_stdcall *pfnQueryInterface)(IMike *pThis, REFIID pIID, void **ppv );
long (_stdcall *pfnAddRef)(IMike *pThis );
long (_stdcall *pfnRelease)(IMike *pThis );
 
/*///// The methods of the IMike interface /////*/
HRESULT (_stdcall *pfnStoreLong)(IMike *pThis, long lToBeStored );
long (_stdcall *pfnRetrieveLong)(IMike *pThis );
};
 
The above declarations work,I believe, because the declaration of the struct IMike_Vtbl seems to be both a forwards and backwards declaration -- things above and below it in the file know about it's presence. The typedef, on the other hand, seems to apply only from that point forwards in the file. Thus, by placing the typedef before the struct, we can use the typedef's IMike within the struct without compile errors.
Note that the first argument to each of the methods is a pointer to an IMike interface, named pThis. C++ implicitly has a first argument to every method call which is used to identify the object that owns the method. The argument is named "this" in C++, but in C, we'll have to handle it by hand, ourselves. Thus, the first argument to any method must be a pointer to an interface, to identify which interface we're talking about.
Create an Interface Identifiers (IID) for IMike:
This also happens to be a good place to put the IID_IMike constant definition, which you can generate using the Guidgen.exe program (if you'd like to generate IIDs/GUIDs at runtime you can also do so programatically, but we won't cover that here). Put #include <objbase.h> at the top of the file This will allow us to use the DEFINE_GUID macro:
 
DEFINE_GUID(IID_IMIke,
0xAAAAAAAA, 0xBBBB, 0x11d1, 0x81, 0xd5, 0x0, 0x0, 0xc0, 0x53, 0xa3, 0xe2);
 
This will declare IID_IMike to be {whatever}, but won't actually allocate space for it unless you've also #include'd <initguid.h>. The trick is that we'll want to allocated space for our IIDs/CLSIDs once per project. If we don't allocated this space, we won't be able to refer to our GUIDs, and if we define this multiple times, the compiler will complain about multiple definitions of the GUID.
At this point, we're done with defining the interface, and so now we'll go and make an object that implements the IMike functionality.
3) Define an object that implements the interface
At this point, we know what interface we want to implement, and what that interface looks like -- the IMike interface. So at this point, we need to create a class that will actually implement the interface, which (as we decided back in step 1) will be named CMike. In order to be able to refer to this class in COM, we'll need a GUID for it, too. A GUID that identifies a class is also refered to as a CLSID (CLaSs ID). Again, we can use GuidGen to generate a GUID for us to use in the DEFINE_GUID format. Copy that, and paste it into the top of the CMike.h file. For the name, we'll use CLSID_CMike (so that we can name the class CMike in C and not have the name collide w/ the GUID)
Having created CLSID for CMike, we need to define a struct that will represent this class. The struct will need the interface defined in the last step, plus any other methods we might want the class to have, plus any auxilliary data members (for example, the current count of clients for each interface). Since we're storing a long int, we'll need a long integer, pluse an additional long to maintain the reference count for this object (which is pretty much absolute bare minimum needed). Thus, the CMike struct will look like:

typedef struct _tagCMike2
{
IMike2 IM2;
long cRefCount;
long m_l;
} CMike2;

We'll need to write out all the member functions that are found going to be used, and place all of these into the CMike.h file so that we can get this info both in CMike.c and CMikeDll.c
Copy and paste the function prototypes from IMike.h to make sure signatures match, and then rename them so we'll have more readable functions. Since all of these functions are for the CMike class, it makes sense to use the standard name (i.e., QueryInterface) prefixed by CMike. We'll go through these functions one by one:

  1. QueryInterface
    The QueryInterface method is a way of asking the COMponenet "Do you support this interface?" If the COMponent doesn't, then it should zero out the ppv argument and return E_NOINTERFACE. If it does, it should set ppv to be a pointer to the requested interface. CMike really only implements one interface: IMike. Since IMike "contains" an IUnknown, we can cheat a little by only implmenting IMike, and handing back a pointer to it when asked for either IUnknown or IMike. Technically, this is bad but not illegal, but since this is only a small demo app, it's ok. As mentioned above, our naming scheme for the CMike class's definition of QueryCMikeQueryInterface. Note that QueryInterface has only one r -- something which messed me up for a little while : )
    For simplicity, we'll declare
     
    CMike *pThis = (CMike *)pIm;
     
    so that we can treat the given IMike interface as a CMike object. The above liine uses some pointer magic to get to the 'front' of the object. Since our object only implements one interface, we know that if the QueryInterface method is invoked, the interface pointer neccesarily points to the "beginning" of a CMike structure. One of the differences between implementing single and multiple interfaces is that an object that supports multiple interfaces must first figure out where in the structure the interface pointer refers to. One can write macros to automatically do this at compile time, and some have (CITE OLE GUY)
    The QueryInterface method is really pretty easy in this case -- we use the IsEquallIID function to compare whether the requested IID is the same as either IUnknown or IMike, in which case we fill in ppv to be a pointer to the interface, and then increment the reference count on the new interface by one (count per type of interface, not per object). Since the requested interface was found, return S_OK
    If the requested interface ISN'T IUnknown or IMike, then the user asked for something we don't support, so NULL out the pointer ppv, and then return E_NOINTERFACE.
    We'll also need to add _stdcall to the function declarations, so that the way the methods are called will be binary compatible with other COM objects. If you happen to omit this, your objects will work with each other, but aren't guaranteed to work with other COM objects.
  2. AddRef
    Again, we'll change the name to CMikeAddRef after copying the function prototype out of IMike.h, and don't forget to add _stdcall to the function declaration!
    The method is pretty much trivial: it increments the internal reference count on the object by one.
    Note that the pointer magic to get pThis is the same, and it allows us to get to the member variable cCMike (count of CMikes) to increment it's reference count.
    The method is expected to return the total number of references after this increment, but this information should only be used for debugging purposes, in debugging mode. In particular, you shouldn't rely on it to stay the same if you don't change it
  3. Release
    The Release decrements the object's reference count by one, freeing the object from memory if this call to Release decrements to count to zero (meaning that nobody is keeping track of this object anymore). Like AddRef, Release returns the reference count after this Release operation, but this information is to be used only for debugging purposes, not for anything else.
    We name this as CMikeRelease, and do similar pointer magic as the above two methods to get the pThis pointer. Don't forget to add _stdcall to the function declaration!
    If this is the last person holding onto the object, free ourselves and note that one of the CMike COMponents has been destroyed by decrementing cCMike. We do this because we need to know when the server (the DLL) can unload, so we need to have acess to a global count (named cCMike) of the number of objects whose code is contained in this DLL. Thus, when cCMike is zero, there are no more CMike objects being serviced by this DLL, and DllCanUnloadNow returns true to signal that it can be unloaded if Windows wants to. This is the same reference counting idea as in AddRef/Release, only this time for COM servers (and DLLs). Note that cCMike is declared extern in this file, and actually defined in DllStuff.c.
    It's also good to note that the use of
    delete this
    in C++ is generally a bad idea, but since we're careful not to touch data or use method pointers in the
    struct, it's ok. In particular, we return 0 rather than return pThis->cRefCount
  4. The IMike Interface
    Having gone through the above, the IMike interface is pretty trivial. In fact, it's so trivial that we'll cover all three methods in one list item :) MikeBeep() simply calls Beep, and MikeGet/Set will get/set an unsigned long integer value. The mechanics of MikeGet/Set are essentially the same as AddRef/Release, even down to the pointer magic.. Don't forget to add _stdcall to the function declarations!
  5. CMike.h
    Having created the actual object and code, we now need to put the finishing touches on the header file. Namely, we can define an instance of the CMike VTable structure so as to make creation of CMikes easier. It looks like: struct IMikeVtbl IMVT = {
    CMikeQueryInterface,
    CMikeAddRef,
    CMikeRelease,
    MikeSet,
    MikeGet,
    MikeBeep
    };

    Since the ClassFactory (which will be discussed shortly) needs to know about this VTable, we'll have to make an extern declaration in CMikeCF.h
  6. Project Tweaking:
    If you haven't already, you should add CMike.c to the project (either Insert->Files Into Project, or via right-click), and compile the file. Take a minute to figure out what all those compile-time errors are, and kill them. If you find anything that you think this tutorial should warn people about, send me email at mpanitz@cascadia.ctc.edu and I'll add it into this tutorial
 
4) Create the class factory
  1. Why Bother with a class factory?
    A class factory is another COM-style object that controls the creation of COMponents. It's handy to have this separate because it allows us to create objects using whatever allocation methods we might want to use ( can certainly, allocating space for an object in C is much different from allocating space for an object in LISP. Indeed, even C and C++ differ in that we'll need to fake a constructor in C). It also allows us (the creator of the COMponent) to decide how to create instances of a COMponent. For example, if we wanted to create an object for each request, we could. On the other hand, if we wanted all the clients to share the use of the same object, we could do that by having the class factory create only one object, and then giving out a reference to that object to all of the clients.
  2. The class factory interface
    See the main page for an explanation of this topic
  3. Project Tweaking:
    Create a new file, IMikeCF.c to hold the class factory source, and add it to the project
  4.  

    Since the IClassFactory interface has already been defined, we only have to write the actual methods -- this will be CMikeCF, which looks like:

    typedef struct _CMikeCF
    {
    IClassFactory icf;
    int cRef;
    } CMikeCF;

    CMikeCF will use nearly identical implementations of QueryInterfce, AddRef, and Release as CMike, except that we'll be passed a pointer to an IClassFactory, and we'll be casting stuff to CMikeCF. It's also good to note that if we want to invoke the methods of an IClassFactory interface, we can use lpVtbl, which appears to be a standard name for the VTable among COM definitions. The interesting methods are:

    • CreateInstance
      The purpose of CreateInstance is explained on the main page, though we'll go through the details of it here. Since our simple object doesn't support aggregation, we return E_NOAGGREGATION to if pUnkOuter is non-NULL.We then try and create the object, and ask the object if it supports the requested interface. If either of those fail, the user is informed thereof, otherwise a the appropriate interface of a newly created object is returned.
      We're going to want to know when we can unload the DLL, so that we can respond intelligently when someone calls DllCanUnloadNow. We could potentially trash the class factory whenever since it doesn't maintain any state, but the objects themselves can't do anything if the DLL gets unloaded. What we need is a count of outstanding objects, incremented in CreateInstance, decremented in the objects' Release methods, which we use to keep track of whether or not the DLL can be unloaded or not. Thus, in CreateInstance, we must included the line cCMike++ to increment the number of objects outstanding.
    • LockServer
      The purpose of LockServer is explained on the main page, and the details are pretty trivial. Essentially, the DLL won't be unloaded unless DllCanUnloadNow returns true, which only happens if all the instances of CMike have been released. I.e., when cCMike is zero. Thus, we can cheat a bit and increment cCMike when LockServer(TRUE) is called, and decrement it when LockServer(FALSE) is called. Another way to accomplish this is to create another global variable (cLockServer), and have DllCanUnloadNow check to see if both cCMike and cLockServer are zero before returning TRUE.
  5. The only thing left to do is define a VTable for our use later on, which we do at the bottom of CMikeCF.c After this, the only things left to do are writing the section of code that deals with the DLL entry points, and (finally) actually registering and using the COMponent.
 
5) Create DLL Entry Points

At this point, we've done all the work for the actual COM object, and so we only need to create a way for our COM object to be interact with the rest of the world. Since we're building this object as an in-proc server, we'll need to create DLL entry points for our object.

  1. Project Tweaking
    First we tell VC++ that the DLL's the entry points
    In Build->Settings ( VC4 ), or Project->Settings ( VC5 ) go to the Link tab, and add to the Object/Library Modules text area the following::

    /export:DllRegisterServer /export:DllUnregisterServer
    /export:DllGetClassObject /export:DllCanUnloadNow


    Note that there is no whitespace between the /export: and the function name. Once you've got the project built, you can verify that these entry points exist (which is nice for debugging purposes) using the dumpbin utility which is supplied with VC++. The syntax for using dumpbin is:

    dumpbin /export <DllName.dll>
  2. DllGetClassObject
    This is a reasonably simple function -- if we're asked to provide a class factory for the CMike COM object, we do so. If we're asked to provide another type of class factory, then return CLASS_E_CLASSNOTAVAILABLE to indicate that we don't support that type of object. The only bit of trickiness that happens is that since we don't need multiple class factories, DllGetClassObject creates only one, and returns interfaces on it to anyone who asks. The alternative would be to create a new class factory each time someone asks for the Class Object of CMike.
  3. DllCanUnloadNow
    This function will return S_OK if the DLL can be unloaded, and S_FALSE if it can't. The DLL can be unloaded if there are no outstanding CMike objects, and the class factory hasn't been locked into place using the LockServer method. We kill two birds with one stone by using cCMike, which is a count of the outstanding CMike objects. The class factory increments this by one when creating a CMike, and CMike objects decrement this by one when their Release method indicates that they should destroy themselves. Likewise, the LockServer method also uses this variable, which is a bit of a cheat, but not a big deal here. Note the use of DebugBreak(), which is used to debug the server once we've built it.
  4. DllRegisterServer
    This and the next function get a little more complicated, but only because we deal with the registry. It's probably best to add this and the next entry point to the DLL so that by using the regsvr32.exe application, we can register all of the DLL's COMponents. The Reg* functions themselves are pretty clearly documented in VC++ help, so I won't cover them in any great detail here, but instead cover what the DllRegisterServer function does.
    Before we go into the details, you should be aware of a program called regedit (and regedit32), which will allow you to examine the contents of the registry. Once you've got Dll(Un)RegisterServer written, try running it, and examining the registry using regedit(32) to see if it worked. The only thing you have to be careful of is that regedit doesn't seem to reload the registry settings if another program modifies them. So I usually run regsvr32, then regedit to make sure it worked, then quit regedit, run regsvr32 /u, and finally, regedit again to make sure the DLL was properly unregistered.
    It's also a bit cludgey because I haven't figured out how to convert a GUID to a ASCII C (1 byte/char, NULL terminated) string yet. To make it easier, I define a global variable
    char szClsidCMike = "{21143C01-DDDF-11d0-AB8F-0000C0148FDB}";
    to hold the CLSID for the object, so that if we decided to change this (or reuse the code), we'll only have to change it in one place. Next, we remember that we have to create the following twokey, and set their values like so:

    KeyValue
    HKEY_CLASSES_ROOT\CLSID\<CLSID_CMike>"The CMike Class, by Mike!"
    HKEY_CLASSES_ROOT\CLSID\<CLSID_CMike>\InprocServer32"C:\winnt\system32\ProjectName.dll

    We use the function RegCreateKey, which looks like (according to VC++ on-line help):
    LONG RegCreateKey( HKEY hKey, /* handle of an open key */
    LPCTSTR lpSubKey, /* address of name of subkey to open */
    PHKEY phkResult /* address of buffer for opened handle */);
    to create the key -- note that in the line before, we line before, we set up the lpSubKey string with the line
    sprintf( szTemp, "CLSID\\%s", szClsidCMike );
    which will leave us with CLSID\{21143C01-DDDF-11d0-AB8F-0000C0148FDB}. Note that there is no "\" prefixing the CLSID\*, nor should there be. To do so will cause RegCreateKey to fail, which certainly isn't what you want.phkResult is a handle to the resulting key, which we'll use in the subsequent calls to RegSetValueEx, and then RegCloseKey. We call RegSetValueEx in order to set the value of the key to "The CMike Class, by Mike!". This doesn't really do anything except leave a human-readable name for people browsing the registry using regedit. At this point we could use our open key to create the InprocServer32 subkey, but instead will close it, and reinitialize the szTemp string with the full path to the subkey. It's probably more elegant to use the already open key, but this way allows you to cut and paste code with amazing ease. We set up the string using:
    sprintf( szTemp, "CLSID\\%s\\InprocServer32", szClsidCMike );
    create the subkey using RegCreateKey, and then set the value again using RegSetValueEx. This time we set the value to be: C:\winnt\system32\ProjectName.DLL, where ProjectName.dll is whatever you happened to name your project.

  5. DllUnregisterServer
    Just as DllRegisterServer allows you to use regsvr32.exe to programatically register your COM object server, DllUnregisterServer allows you to programatically unregister your components. We use RegOpenKey to open the key, RegDeleteKey to delete it once it's been opened, and RegCloseKey to flush the changes to the registry. Since RegDeleteKey won't delete a key that has subkeys, we have to first delete the InprocServer32 subkey, then delete the actual key itself.
  6. Registering/Placing the DLL
    Once everything else has been done, the only thing left is to run regsvr32.exe on our DLL, and then move it to the appropriate directory. I believe that the documentation in VC++ states that you can place the DLL in any directory, so long as you make the appropriate registry entry. I've never gotten this to work for any directory other than \Windows\system\
    Explain Registry structure -- HKEY_CLASSES_ROOT\CLSID\{<CLISD>}\InprocServer32
    Note others, we won't have to worry about them for now
6 ) On to the client!
 
Once we're created a client, we can actually watch our COM objects in action. Go and make yourself a client, then come back here when you're done.
 
7) How do we debug this thing?
Ok, now that we've created the object server, registered it, and got a client up and running, the question arises of "How do we debug this thing?" There are a couple of ways. Since we've built the server as a DLL, all of the executing code will be loaded into our address space, and we could (at least in theory) start the client under the debugger, and then step to method invocations,etc. While this works for somethings, we'd also like to be able to debug our class factory, whose methods get called by COM. In order to do that, we can use this nifty function called DebugBreak().
 
And that's about it for creating an in-proc COM object server in C.
 
 
Random Stuff to Cover In The Future:
If I had more time, I might explain some of the following stuff, but at this point won't
  1. Multiple interfaces
  2. Object Aggregation (the COM equivalent of C++ inheritance)
  3. Component categories and other registry magic
  4. examples of implementing interfaces from OLE-land
  5. C++ examples, maybe Java too

Notes for Mike:

Concurrency issues:
Do we need semaphores, etc? Probably....
Another example: dsh for NT
 
Declaration: Tell the compiler that the variable exists.
Definition: Declare a variable and allocate space for it.
Structure: Main page of concepts, then a flowchart of How to create COMponents, separate pages for individual languages (with links back to flowchart), glossary of terms w/ language dependent details noted
 
Random question: Since COM is a binary standard, won't COM have to always use 32bit pointers in
VTables? 

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.