HOME
Login
Change Info
Logout


TUTORIALS

C, C++
Win32
Java
Visual Basic
MFC
DCOM
Networking
C#
Perl
HTML
XML
ASP
PHP
Javascript
Other

DOWNLOADS
ITCLib
SourceVizor meets the notification, reporting, and admin needs of teams using Microsoft Visual SourceSafe.
Free 30-day trial!


An Introduction to DCOM - Part I

Microsoft's Distributed Component Object Model Revealed
Adapted from Understanding DCOM Copyright © 1998

by William Rubin and Marshall Brain


Understanding the Simplest COM Client

The most straightforward way to begin understanding COM is to look at it from the perspective of a client application. Ultimately, the goal of COM programming is to make useful objects available to client applications. Once you understand the client, then understanding servers becomes significantly easier. Keeping clients and servers straight can be confusing; and COM tends to make the picture more complex when you are first learning the details. Therefore, let's start with the simplest definition: A COM client is a program that uses COM to call methods on a COM server. A straightforward example of this client/server relationship would be a User Interface application (the client) that calls methods on another application (the server). If the User Interface application calls those methods using COM, then the user Interface application is, by definition, a COM client.

We are belaboring this point for good reason - the distinction between COM servers and clients can get (and often is) much more complex. Many times, the application client will be a COM server, and the application's server will be a COM client. It's quite common for an application to be both a COM client and server. In this chapter, we will keep the distinction as simple as possible and deal with a pure COM client.

Four Steps to Client Connectivity

A client programmer takes four basic steps when using COM to communicate with a server. Of course, real-life clients do many more things, but when you peel back the complexity, you'll always find these four steps at the core. In this section we will present COM at it's lowest level - using simple C++ calls.

Here is a summary of the steps we are going to perform:

  1. Initialize the COM subsystem and close it when finished.
  2. Query COM for a specific interfaces on a server.
  3. Execute methods on the interface.
  4. Release the interface.

For the sake of this example, we will assume an extremely simple COM server. We'll assume the server has already been written and save its description for the next tutorial.

The server has one interface called IBeep. That interface has just one method, called Beep. Beep takes one parameter: a duration. The goal in this section is to write the simplest COM client possible to attach to the server and call the Beep method.

Following is the C++ code that implements these four steps. This is a real, working COM client application.


#include "..\BeepServer\BeepServer.h"

// GUIDS defined in the server
const IID IID_IBeepObj =
    {0x89547ECD,0x36F1,0x11D2,
    {0x85,0xDA,0xD7,0x43,0xB2,0x32,0x69,0x28}};
const CLSID CLSID_BeepObj =
    {0x89547ECE,0x36F1,0x11D2,
    {0x85,0xDA,0xD7,0x43,0xB2,0x32,0x69,0x28}};

int main(int argc, char* argv[])
{
    HRESULT hr;                   // COM error code
    IBeepObj *IBeep;              // pointer to interface

    hr = CoInitialize(0);         // initialize COM
    if (SUCCEEDED(hr))            // macro to check for success
    {
        hr = CoCreateInstance(
			CLSID_BeepObj,        // COM class id
			NULL,                 // outer unknown
			CLSCTX_INPROC_SERVER, // server INFO
			IID_IBeepObj,         // interface id
			(void**)&IBeep );     // pointer to interface

        if (SUCCEEDED(hr))
        {
            // call method
            hr = IBeep->Beep(800);

            // release interface
            hr = IBeep->Release();
        }
    }
    // close COM
    CoUninitialize();
    return 0;
}

The header "BeepServer.h" is created when we compile the server. BeepServer is the in-process COM server we are going to write in the next section. Several header files are generated automatically by developer studio when compiling the server. This particular header file defines the interface IBeepObj. Compilation of the server code also generates the GUIDs seen at the top of this program. We've just pasted them in here from the server project.

Let's look at each of the 4 steps in detail.

Initializing the COM subsystem:

This is the easy step. The COM method we need is CoInitialize().


CoInitialize(0);

This function takes one parameter and that parameter is always a zero - a legacy from its origins in OLE. The CoInitialize function initializes the COM library. You need to call this function before you do anything else. When we get into more sophisticated applications, we will be using the extended version, CoInitializeEx.

Call CoUninitialize() when you're completely finished with COM. This function de-allocates the COM library. I often include these calls in the InitInstance() and ExitInstance() functions of my MFC applications.

Most COM functions return an error code called an HRESULT. This error value contains several fields which define the severity, facility, and type of error. We use the SUCCEEDED macro because there are several different success codes that COM can return. It's not safe to just check for the normal success code (S_OK). We will discuss HRESULT's later in some detail.

Query COM for a specific interface

What a COM client is looking for are useful functions that it can call to accomplish its goals. In COM you access a set of useful functions through an interface. An interface, in its simplest form, is nothing but a collection of one or more related functions. When we "get" an interface from a COM server, we're really getting a pointer to a set of functions.

You can obtain an interface pointer by using the CoCreateInstance() function. This is an extremely powerful function that interacts with the COM subsystem to do the following:

  • Locate the server.
  • Start, load, or connect to the server.
  • Create a COM object on the server.
  • Return a pointer to an interface to the COM object.

There are two data types important to finding and accessing interfaces: CLSID and IID. Both of these types are Globally Unique ID's (GUID's). GUID's are used to uniquely identify all COM classes and interfaces.

In order to get a specific class and interface you need its GUID. There are many ways to get a GUID. Commonly we'll get the CLSID and IID from the header files in the server. In our example, we've defined the GUIDs with #define statements at the beginning of the source code. There are also facilities to look up GUIDs using the common name of the interface.

The function that gives us an interface pointer is CoCreateInstance.


hr = CoCreateInstance(
	CLSID_BeepObj,        // COM class id
	NULL,                 // outer unknown
	CLSCTX_INPROC_SERVER, // server INFO
	IID_IBeepObj,         // interface id
	(void**)&IBeep );     // pointer to interface

The first parameter is a GUID that uniquely specifies a COM class that the client wants to use. This GUID is the COM class identifier, or CLSID. Every COM class on the planet has its own unique CLSID. COM will use this ID to automatically locate a server that can create the requested COM object. Once the server is connected, it will create the object.

The second parameter is a pointer to what's called the 'outer unknown'. We're not using this parameter, so we pass in a NULL. The outer unknown will be important when we explore the concept known as "aggregation". Aggregation allows one interface to directly call another COM interface without the client knowing it's happening. Aggregation and containment are two methods used by interfaces to call other interfaces.

The third parameter defines the COM Class Context, or CLSCTX. This parameter controls the scope of the server. Depending on the value here, we control whether the server will be an In-Process Server, an EXE, or on a remote computer. The CLSCTX is a bit-mask, so you can combine several values. We're using CLSCTX_INPROC_SERVER - the server will run on our local computer and connect to the client as a DLL. We've chosen an In-Process server in this example because it is the easiest to implement.

Normally the client wouldn't care about how the server was implemented. In this case it would use the value CLSCTX_SERVER, which will use either a local or in-process server, whichever is available.

Next is the interface identifier, or IID. This is another GUID - this time identifying the interface we're requesting. The IID we request must be one supported by the COM class specified with the CLSID. Again, the value of the IID is usually provided either by a header file, or by looking it up using the interface name.

The last parameter is a pointer to an interface. CoCreateInstance() will create the requested class object and interface, and return a pointer to the interface. This parameter is the whole reason for the CoCreateInstance call. We can then use the interface pointer to call methods on the server.

Execute a method on the interface.

CoCreateInstance() uses COM to create a pointer to the IBeep interface. We can pretend the interface is a pointer to a normal C++ class, but in reality it isn't. Actually, the interface pointer points to a structure called a VTABLE, which is a table of function addresses. We can use the -> operator to access the interface pointer.

Because our example uses an In-Process server, it will load into our process as a DLL. Regardless of the details of the interface object, the whole purpose of getting this interface was to call a method on the server.


hr = IBeep->Beep(800);

Beep() executes on the server - it will cause the computer to beep. There are a lot simpler ways to get a computer to beep. If we had a remote server, one which is running on another computer, that computer would beep.

Methods of an interface usually have parameters. These parameters must be of one of the types allowed by COM. There are many rules that control the parameters allowed for an interface. We will discuss these in detail in the section on MIDL, which is COM's interface definition tool.

Release the interface

It's an axiom of C++ programming that everything that gets allocated should be de-allocated. Because we didn't create the interface with new, we can't remove it with delete. All COM interfaces have a method called Release() which disconnects the object and deletes it. Releasing an interface is important because it allows the server to clean up. If you create an interface with CoCreateInstance, you'll need to call Release().

Summary

In this chapter we've looked at a simple COM client. COM is a client driven system. Everything is oriented to making component objects easily available to the client. You should be impressed at the simplicity of the client program. The four steps defined here allow you to use a huge number of components, in a wide range of applications.

Some of the steps, such as CoInitialize() and CoUninitialize() are elementary. Some of the other steps don't make a lot of sense at first glance. It is important for you to understand, at a high level, all of the things that are going on in this code. The details will clarify themselves as we go through further examples.

Visual C++ Version 5 and 6 simplify the client program further by using 'smart pointers" and the #import directive. We've presented this example in a low level C++ format to better illustrate the concepts. We'll discuss smart pointers and imports in a later section.

In the next section, we'll build a simple in-process server to manage the IBeep interface. We'll get into the interesting details of interfaces and activation in later chapters.

;
Previous Page
Return to beginning of article

Next Page


Featured Article

An Introduction to C#
By Joey Mingrone

Register Today!


100% FREE

Members enjoy these benefits:
Access to ITI Downloads
Access to more articles and tutorials
Optional weekly newsletter
And more...

Click here to register
Or
Click here to log in



© 2010 Interface Technologies, Inc. All Rights Reserved
Questions or Comments? devcentral AT iticentral DOT com
PRIVACY POLICY