Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Building a LOCAL COM Server and Client: A Step by Step Example

0.00/5 (No votes)
28 Oct 2004 2  
A step-by-step introduction to how to build your own Local COM server and client.

Introduction

Many CPians have written about COM, why again?

Most people learned COM by building a InprocServer, i.e., a DLL file first, and then continued to write a LOCAL COM server together with a client. At this point, a simple example showing a LOCAL COM server and client will be very helpful. However, believe it or not, I spent hours searching the net, trying to find a small example of a LOCAL COM server and client so I can grow my own system based on it, but I never found one! Most of the examples I found are Inproc servers. In some cases, I found LOCAL servers, but the document only lists the basic steps/ideas without giving the code. As a matter of fact, from knowing the steps to building a real one is still quite a long way to go: you have to write the idl file, the reg file, you have to include different files at the right places (even in the right order! you will see), ..., and a single mistake will bring you very confusing error messages or results. I then found Andrew Troelsen's book and tried to follow his example. However, I moved his server code from the attached CD to my local PC only to find that his LOCAL server did not even compile - I finally figured out the reason (you will see if you continue to read) and I believe that he should have mentioned this problem in his book - this would have saved his readers lots of frustration and searching around. Meanwhile, he did not present the client code, so that is still not a whole story.

So I decided to write down all the necessary steps you should follow, all the real code you need to write, all the files you have to create, and all the things you want to remember if you decide to build a LOCAL COM server and client - this small example can then serve as a starting point for your much better and bigger (also ambitious) project. Still, let me clearly state that half of the credit should go to Andrew Troelsen: for the server side, I am using his example and his code (with the problem corrected), if you want to know more about this part, you can check his book out.

Therefore, if you are interested in building a LOCAL COM server and client, this example might be helpful to you. Also, I assume you have the basic knowledge of COM: the architecture, the interfaces, COM run-time service functions, and the variety of tools that you can use to get the GUID and to inspect your components, so on and so forth. In the next section, a LOCAL COM server is constructed, and after that, a client is built. Enjoy!

Step By Step: Building a LOCAL COM Server

In this section, the steps that are needed to build a LOCAL server are described. This simple server contains a single coclass called MyCar and the following three interfaces: ICreateMyCar, IEngine and IStats. The whole server is very much self-explained, so let the show begin!

Step 1: Start VC6.0 and create a new WIN32 Application workspace, select "a simple application", and name it CarLocalServer (or whatever you want to).

Step 2: create your idl file, it should look like the following, and name it CarLocalServerTypeInfo.idl:

import "oaidl.idl";

// define IStats interface

[object, uuid(FE78387F-D150-4089-832C-BBF02402C872),
 oleautomation, helpstring("Get the status information about this car")]
interface IStats : IUnknown
{
   HRESULT DisplayStats();
   HRESULT GetPetName([out,retval] BSTR* petName);
};

// define the IEngine interface

[object, uuid(E27972D8-717F-4516-A82D-B688DC70170C),
 oleautomation, helpstring("Rev your car and slow it down")]
interface IEngine : IUnknown
{
   HRESULT SpeedUp();
   HRESULT GetMaxSpeed([out,retval] int* maxSpeed);
   HRESULT GetCurSpeed([out,retval] int* curSpeed);
};

// define the ICreateMyCar interface

[object, uuid(5DD52389-B1A4-4fe7-B131-0F8EF73DD175),
 oleautomation, helpstring("This lets you create a car object")]
interface ICreateMyCar : IUnknown
{
   HRESULT SetPetName([in]BSTR petName);
   HRESULT SetMaxSpeed([in] int maxSp);
};

// library statement

[uuid(957BF83F-EE5A-42eb-8CE5-6267011F0EF9), version(1.0),
 helpstring("Car server with typeLib")]
library CarLocalServerLib
{
   importlib("stdole32.tlb");
   [uuid(1D66CBA8-CCE2-4439-8596-82B47AA44E43)]
   coclass MyCar
   {
      [default] interface ICreateMyCar;
      interface IStats;
      interface IEngine;
   };
};

Insert this file into your project. Also, notice that all the GUIDs should be generated by using guidgen.exe, so your IDs will not look like the ones in the above file. I listed above every line of this file so you can have a clear picture about this simple server. For the rest of the files in the server, you can download them and read them through.

Step 3: Insert the following files into your workspace: CarLocalServer.cpp, MyCar.cpp, MyCarClassFactory.cpp, Locks.cpp, MyCar.h, MyCarClassFactory.h and Locks.h. Now, here come the things that you really need to pay attention to (these two things are not mentioned in Troelsen's book, but they are the reasons that I could not compile):

  1. pay attention to the included .h files and remember, whenever you need to include the stdafx.h file, you want to include it first, before any other included files. If you don't do this, your compiler will give you some very confusing error messages (at least mine did).
  2. open stdafx.h, check if this file contains the following line:
    #define WIN32_LEAN_AND_MEAN
    // Exclude rarely-used stuff from Windows headers

    If it does, make sure to get rid of it (comment it out). This is the other reason why I could not get the server successfully compiled (and it took me a while to figure this out).

I really think Troelsen should have mentioned these two things. Well, if you happen to read his book, I hope you notice this article too, it might save you some time.

Step 4: Create the .reg file. You can use whatever name you want for this file, but its content has to be exactly the same as the one you downloaded from this article, except:

  1. you should use your own ID that you just generated and,
  2. you should use your own path to replace the path I used in this file,
  3. you can use different names (keep the coclass name MyCar unchanged).

After you create this file, double click it, your Windows system should tell you that your components are successfully registered.

Step 5: Build the whole project. You should have no compiling errors, and remember that you should at least run it once.

Now you have it: a LOCAL COM server. Let me again say that this part of the code is from Troelsen's book and I just made little corrections as seen in step 3 (to get rid of the compiling errors). Also, I added some error protection in the server too; in case something goes wrong, you can get some error messages that make sense.

3. Step By Step: Building a LOCAL COM Client

Now, let us follow the following steps to build a LOCAL COM client. I will list the source code here and explain the important parts since Troelsen did not present the client in his book.

Step 1: Start your VC6.0 again and create a new WIN32 Console Application workspace. Select "an empty project", and name it CarLocalClient (or whatever you want to).

Step 2: Create a new .cpp to be your client code, use whatever name you want for this file, and add it to the project. Your client code should look like the following:

#include "../CarLocalServer/CarLocalServerTypeInfo.h"    // use your own path here

#include "../CarLocalServer/CarLocalServerTypeInfo_i.c"  // use your own path here

#include "iostream.h"


// for showing possible mistakes

void ShowErrorMessage(LPCTSTR,HRESULT);

int main()
{
   // initialize the COM runtime

   cout << "Initialize the COM runtime...";
   CoInitialize(NULL);
   cout << "success." << endl;

   // declare variables

   HRESULT hr;
   IClassFactory* pICF = NULL;
   ICreateMyCar*  pICreateMyCar = NULL;
   IEngine*       pIEngine = NULL;
   IStats*        pIStats = NULL;

   cout << endl << "Get the class factory interface for the Car class...";
   hr = CoGetClassObject(CLSID_MyCar,CLSCTX_LOCAL_SERVER, 
                         NULL,IID_IClassFactory,(void**)&pICF);
   if ( FAILED(hr) )
   {
      ShowErrorMessage("CoGetClassObject()",hr);
      exit(1);
   }
   else cout << "success." << endl;

   cout << "Create the Car object and get back the ICreateMyCar interface...";
   hr = pICF->CreateInstance(NULL,IID_ICreateMyCar,(void**)&pICreateMyCar);
   if ( FAILED(hr) )
   {
      ShowErrorMessage("CoCreateInstance()",hr);
      exit(1);
   }
   else cout << "success." << endl;

   // set parameters on the car

   cout << endl << "Set different parameters on the car...";
   pICreateMyCar->SetMaxSpeed(30);
   BSTR carName = SysAllocString(OLESTR("COMCar?!"));
   pICreateMyCar->SetPetName(carName);
   SysFreeString(carName);
   cout << "success." << endl;

   cout << endl << "Query the IStats interface...";
   pICreateMyCar->QueryInterface(IID_IStats,(void**)&pIStats);
   cout << "success." << endl;

   cout << endl << "Use the IStats interface to 
                    display the status of the car:" << endl;
   pIStats->DisplayStats();

   cout << endl << "Query the IEngine interface...";
   pICreateMyCar->QueryInterface(IID_IEngine,(void**)&pIEngine);
   cout << "success." << endl;

   cout << endl << "Start to use the engine..." << endl;
   int curSp = 0;
   int maxSp = 0;
   pIEngine->GetMaxSpeed(&maxSp);
   do
   {
      pIEngine->SpeedUp();
      pIEngine->GetCurSpeed(&curSp);
      cout << "current speed is: " << curSp << endl;
   } while (curSp <= maxSp);

   if ( pICF )         pICF->Release();
   if ( pICreateMyCar) pICreateMyCar->Release();
   if ( pIStats )      pIStats->Release();
   if ( pIEngine )     pIEngine->Release();

   cout << endl << "Close the COM runtime...";
   CoUninitialize();
   cout << "success." << endl;
   return 0;
}

void ShowErrorMessage(LPCTSTR header, HRESULT hr)
{
   void* pMsg;

   FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                 FORMAT_MESSAGE_FROM_SYSTEM,NULL,hr,
                 MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), 
                 (LPTSTR)&pMsg,0,NULL);

   cout << header << ": Error(" << hex << hr << "): " 
        << (LPTSTR)pMsg << endl;

   LocalFree(pMsg);
}

Notice the include statement: you don't really need to include anything like windows.h, you only need to include the CarLocalServerTypeInfo.h and CarLocalServerTypeInfo_i.c files, and these two files include all the necessary header files for you already. Notice that these two files are generated by your system after VC6.0 compiles CarLocalServerTypeInfo.idl file, and they are located in the workspace that you have created in Section 2. So you need to either copy them to your client workspace directory, or you can use a relative path like I did here.

To access your COM components (your coclass and interfaces), you first make the call to the COM service function CoGetClassObject(), and what you get from this call is the pointer to your IClassFactory interface. Once you have this interface, the rest seems to be obvious: you then make a call to CreateInstance() on this interface to start your journey. You may have noticed the CLSTX_LOCAL_SERVER parameter in CoGetClassObject() call, this is where we show that we are building a LOCAL server!

Another choice is to call CoCreateInstance() instead of CoGetClassObject(). This function in fact makes a call to CoGetClassObject() and then another call to CreateInstance() just like what we did in our client code. Perhaps you should do what we did here in this client code: besides the performance considerations, it seems to be safer to do so: by making the two calls yourself, you can put error protections after each call, so if anything goes wrong, you have a fairly clear idea of where the problem is. On the other hand, if you use CoCreateInstance(), if this call fails, you lose control of where the problem is. Notice in this client code, we just show the error, we did not put any exception handling code - I just assume that you will be the one who will add the appropriate exception handlings.

Step 3: build the project and try it out. You should be able to "talk" to your COM server without making it a in-process DLL file.

These are the basic steps of how to build a LOCAL COM server and client. If you really try it out, you will see that it does involve a fairly large amount of work, and again, a single mistake will give you very confusing error messages. Perhaps, after trying this example out, you will start to appreciate ATL more.

That is it!

This article presents a simple example of how to build your own LOCAL COM server and client. The real benefit of it, at least I hope, is that you can use this as a starting point to build much more practical and bigger projects. You might want to use ATL in your real work, but understanding what is going on under the hood is always good. Thank you for reading, and I certainly look forward to your comments and suggestions.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here