COM: All You Need to Know to Get Started
by Francis Shanahan

First off, do yourself a favour and get the code for this lesson:

Ok with .NET and C# coming out now I figure it's time to write an article on COM. I've been meaning to write this for a long time now and hence I've a number of different ideas in my head. Let's start by getting those out to give you all an idea of what I will and won't cover in this article.

a) What's COM?
b) What can I do with COM? I mean really do?
c) How do I do that stuff? Give me an example.

What's COM?

COM is an acronym for Component Object Model. What's that? Well it's a specification which describes how to to write code. Ok so that's great but I already know how to write code. Why should I follow a spec? Well there's a number of reasons. If you've made the transition from C to C++ then you'll understand some of the benefits you got when you made that transition. Similarly there are a number of benefits from taking your code from C++ and implementing it in a fashion that complies with COM. Said a different way, there's a number of benefits you get when you write COM objects instead of straight C++ objects.

Some of those benefits are as follows:

  1. Ever written a DLL? Ever tried to use a DLL that's ALREADY written? Well if you have then you'll understand that it's not so easy. You'll also understand that you need to know exactly the functions etc that you're calling inside the DLL. Not only that you'll need to know the parameters for those functions. With COM you don't need to know all that, or at least it's easier to find that information out. You'll see what I mean a little later on.
  2. If you ever written some code in one language and wanted to make use of that functionality from another language then you'll understand how difficult that it. E.g. Calling a C++ function from VB? How in the world do you do that? Well you need to write the code into a DLL which is not very object oriented. It also will require access to the original code and will most likely need more changes if you wanted to change languages again, e.g. VC++ to Java or VB to Java.
  3. Probably the neatest thing about COM is that it can easily enable your code to operate in a distributed environment (D/COM). That means you can more easily harness the power of multiple machines. Probably the most obvious application of this is in Artificial Intelligence. Think how much more computation you could do if there were more computers working on the same problem?

Microsoft tells us: "COM is the fundamental "object model" on which ActiveX Controls and OLE are built. COM allows an object to expose its functionality to other components and to host applications. It defines both how the object exposes itself and how this exposure works across processes and across networks. COM also defines the object's life cycle."

A COM object must sport a number of features, the good news is that 99% of this is done FOR you by Visual C++/VBasic/VJ++ . The language which gives the most control is definitely Visual C++ and so that's the one I'll use for this article.

From the MSDN:
Interfaces - the mechanism through which an object exposes its functionality.
IUnknown - the basic interface on which all others are based. It implements the reference counting and interface querying mechanisms running through COM.
Reference counting - the technique by which an object (or, strictly, an interface) decides when it is no longer being used and is therefore free to remove itself.
QueryInterface - the method used to query an object for a given interface.
Marshaling - the mechanism that enables objects to be used across thread, process, and network boundaries, allowing for location independence.
Aggregation - a way in which one object can make use of another.

I like to keep my brain devoid of all useless information. In keeping with that philosophy instead of describing each of these topics, we're going to jump right into an example. If you do feel the burning desire to find out more of the theory I recommend you take a click over to msdn.microsoft.com and search on any of those topics.

On to the Example

Think of an interface as the way one object tells another about itself. It's like saying "Hey, look what I can do!". Every object must know the basic way to ask another object about itself, kind of like "Hi there, how are you doing?". This is made possible by the standard interface which all COM objects implement: IUnkown. IUnknown defines a few methods, my favourite being "QueryInterface()". QueryInterface is literally the function that says "What can YOU do?". It allows an object to ask another object for a pointer to one of its interfaces. Dizzy yet? Keep reading it'll all start to make sense in a second or two.

Interfaces are implemented with pointers, tables and vtables and IDL (interface definition language). The GREAT news is VC++ does the hard work of generating ALL the code you need. You just need to tell VC++ what to generate and it does the work for you, ain't technology grand? We'll create and implement an interface on our COM object in a minute.

Reference Counting, what's that?

Microsoft tells us that "COM itself does not automatically try to remove an object from memory when it thinks the object is no longer being used. Instead, the object programmer must remove the unused object. The programmer determines whether an object can be removed based on a reference count. ".

So how do you know when to let go of an object? Reference counting is one of the ways in which you can determine if anything's using the object. When an interface is used, a counter is incremented (through a function on IUnknown called AddRef). When it's done being used, a counter is decremented (through a function called Release). When the counter = 0, no one's using the object and it'll be freed up.

In reality, the programmer (you) doesn't need to worry about this. They've made it VERY easy for programmers to use COM and as a result so much of it is taken care of for us.

-===============================-

Alright, let's setup the project and get cracking with some code.
Open Visual C++. Click New on the File menu and click the Projects tab.
Choose ATL COM AppWizard as your application type and follow the steps. ATL stands for Active Template Library. it's kind of like a macro in that it'll do a lot of stuff for you automatically and makes the coding process much simpler. Choose from one of three project types: Dynamic Link Library (DLL), Executable (EXE) or Service (EXE). We'll keep things simple and go with DLL. I'm going to call my project "Cflake_Hello_World".

When you're done your workspace should look something like this:

-===============================-

The following is from MSDN and tells you what you get when you generate your project:

Files Generated by the ATL COM AppWizard
Choose the FileView tab in the Project Workspace and expand by clicking + to see the files generated for your project:

Test.cpp
Contains the implementation of your DLL's exports for an in-process server and the implementation of WinMain for a local server. For a service, this additionally implements all the service management functions.

Test.def
Typically, contains a list of your DLL's exports. Generated only for an in-process server.

Test.idl
Includes the definitions for all your interfaces. As an Interface Definition Language (IDL) file, it will be processed by the MIDL compiler to generate the Test.tlb type library and marshaling code.

Test.rc
Contains the resource information for your project.

Resource.h
The header file for the resource file.

StdAfx.cpp
Includes the files StdAfx.h and Atlimpl.cpp.

StdAfx.h
Includes the ATL header files.

-===============================-

Alright, so we've got an ATL project. Let's create a class in that project. I'm going to call my class Chelloworld. Right click on the project and select add new class. In the window that pops up make sure you pick ATL class from the drop down as opposed to generic class. When you're done you should have a workspace with some files that look like this:

Notice the funny looking thing called Ihelloworld. You're probably wondering where that guy came from. That's the object's interface and was generated for you when you created the class on the project. There's two instances of it in the workspace, the first is the iimplementation of it in C++ and the second is the actual interface definition in IDL (interface definition language). We'll add a method to the interface in a second so keep an eye on him as that's the key to this whole thing.

Alright now let's implement our function "helloworld" on that class. Right click and select "Add Member Function". I'm going to call my function "helloworld" and give it a char* as a parameter, it'll take another char * which we'll fill in inside the function. Here's the definition.

int helloworld(char *mystring, char *retmsg);

You can probably guess where I'm going but if you don't this application is going to take a string A as input, it's going to alter it and place the result in B which will get returned to the calling function. Just about as basic as you can get.

Here's the implementation of the function.

int Chelloworld::helloworld(char *mystring, char *retmsg)
{
  char tmp[200];

  sprintf(tmp, "hello there %s, how are you today",mystring);

  strcpy(retmsg, tmp);

  return 1;
}

Now let's add the interface method : Right click on the Ihelloworld and select "Add Method", you'll get a box like this one.

Fill it in as I've done. You'll notice we're using BSTR instead of char* this is because char * is not a COM safe datatype. BSTR is. There's only certain types which can be used to exchange data between objects and each one maps to one of the more common data types. Check MSDN for more info on these COM safe data types.

At this point the workspace will look like this:
You'll also notice we specified [out,retval] for the second paramater. This is to tell whoever calls this function that this parameter is the return value. COM returns S_OK from an interface call if the function was called ok, since we want to return stuff other than S_OK as we'd normally do it in C++ we use [out,retval] to achieve this. The S_OK is more for COM's use than ours. You'll see what I mean later...

Now let's implement the interface method. Here's how I did that:

STDMETHODIMP Chelloworld::sayhello(BSTR input, BSTR *output)
{
  // TODO: Add your implementation code here
  return S_OK;
}

was generated for me. I wrote the following:

STDMETHODIMP Chelloworld::sayhello(BSTR input, BSTR *output)
{
  char strtmp[200];
  char char_output[200];

  // Here we convert the BSTR to a char *
  WideCharToMultiByte(CP_ACP,
                      0,
                      input,              // actual Message
                      lstrlenW(input),    // sizeof bstrMsg
                      strtmp,             // recieving variable
                      lstrlenW(input),    // sizeof(strMsg),
                      NULL,
                      NULL);

  // Tack on an EOL
  strtmp[lstrlenW(input)] = '';

  // here we call the internal COM functions.
  if (helloworld(strtmp, char_output) ) {
    // Create a temporary CComBSTR and place the strRetMsg in it.
    CComBSTR bstrTemp(char_output);

    // Return the output string
    *output = bstrTemp.Detach();
  }

  return S_OK;
}

Here I just convert my input BSTR to a char * to make it easier for me to deal with, then I pass that to my other functions and return the result. Notice the lack of error checking to make this code a little easier for you to understand. Again notice the S_Ok which get's returned to COM internally.

Go ahead and compile this, we're done with the C++ part of the tutorial. Next up we're going to use this COM object from VB. Open up Visual Basic and create a new form. You'll want to add a button and two textboxes to it. We're going to call the COM object when we hit the button, giving the text1 text as input and print the result in the second textbox.

First we need to tell VB that we're using this COM object. Do this by adding our COM DLL to the project as a reference. Hit Project in the menu, then references and find your COM project name in the References window (you might have to scroll down the list to find it). Click the checkbox as I've done when you find it. How did VB know about this Library you ask? Well when you compiled in VC++ the last thing the compiler did was register the COM object in the registery (go and check if you like). VB simply pulls back the list of all registered objects. Note if you change the compiled DLL's located you'll have to re-register to get things to work.

Here's the code for the form: NOTE, and this is HUGE: we Dim the object as an Interface (Ihelloworld) NOT as the object itself. Does it ALL make sense now? We then create the object and it's "plugged into" via the interface. Take a look at the code for the entire form:

Dim myObj As Ihelloworld

Private Sub Command1_Click()
  Text2.Text = myObj.sayhello(Text1.Text)
End Sub

Private Sub Form_Load()
  Set myObj = CreateObject("cflake_hello_world.helloworld.1")
End Sub

Ok so we run the darn thing and here's what we get when I hit the button:

I hope you've learned something from this article. It's been a long time coming and took a while to get it to the level of detail I wanted. COM is a useful thing to know and can make your engine code EXTREMELY reusable WITHOUT performance degredation. It's also a lot of fun in a geeky type of way. Anyway what you've learned here will hopefully set you up for some serious .NET learning. Maybe you'll start to enjoy coding as much as I do. Regards,
fs

Discuss this article in the forums


Date this article was posted to GameDev.net: 2/28/2001
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Windows

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!