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

The Basics of COM

Understanding how COM works can be intimidating at first. One reason for this intimidation is the fact that COM uses its own vocabulary. A second reason is that COM contains a number of new concepts. One of the easiest ways to master the vocabulary and concepts is to compare COM objects to normal C++ objects to identify the similarities and differences. You can also map unfamiliar concepts from COM into the standard C++ model that you already understand. This will give you a comfortable starting point, from which we'll look at COM's fundamental concepts. Once we have done this, the example presented in the following sections will be extremely easy to understand.

Classes and Objects

Imagine that you have created a simple class in C++ called xxx. It has several member functions, named MethodA, MethodB, and MethodC. Each member function accepts parameters and returns a result. The class declaration is shown here:


class xxx {
public:
   int MethodA(int a);
   int MethodB(float b);
   float MethodC(float c);
};

The class declaration itself describes the class. When you need to use the class, you must create an instance of the object. Instantiations are the actual objects; classes are just the definitions. Each object is created either as a variable (local or global) or it is created dynamically using the new statement. The new statement dynamically creates the variable on the heap and returns a pointer to it. When you call member functions, you do so by dereferencing the pointer. For example:


xxx *px;            // pointer to xxx class
px = new xxx;       // create object on heap
px->MethodA(1);     // call method
delete px;          // free object

It is important for you to understand and recognize that COM follows this same objected oriented model. COM has classes, member functions and instantiations just like C++ objects do. Although you never call new on a COM object, you must still create it in memory. You access COM objects with pointers, and you must de-allocate them when you are finished.

When we write COM code, we won't be using new and delete. Although we're going to use C++ as our language, we'll have a whole new syntax. COM is implemented by calls to the COM API, which provides functions that create and destroy COM objects. Here's an example COM program written in pseudo-COM code.


ixx *pi                     // pointer to xxx COM interface
CoCreateInstance(,,,,&pi)   // create interface
pi->MethodA();              // call method
pi->Release();              // free interface

In this example, we'll call class ixx an "interface". The variable pi is a pointer to the interface. The method CoCreateInstance creates an instance of type ixx. This interface pointer is used to make method calls. Release deletes the interface.

I've purposely omitted the parameters to CoCreateInstance. I did this so as not to obscure the basic simplicity of the program. CoCreateInstance takes a number of arguments, all of which need some more detailed coverage. For now, let's take a step back and look at the bigger issues with COM.

How COM Is Different

COM is not C++, and for good reason. COM objects are somewhat more complicated then their C++ brethren. Most of this complication is necessary because of network considerations. There are four basic factors dictating the design of COM:

  • C++ objects always run in the same process space. COM objects can run across processes or across computers.
  • COM methods can be called across a network.
  • C++ method names must be unique in a given process space. COM object names must be unique throughout the world.
  • COM servers may be written in a variety of different languages and on entirely different operating systems, while C++ objects are always written in C++.

Let's look at what these differences between COM and C++ mean to you as a programmer.

COM can run across processes

In COM, you as the programmer are allowed to create objects in other processes, and on any machine on the network. That does not mean that you will always do it (in many cases you won't). However, the possibility means that you can't create a COM object using the normal C++ new statement , and calling its methods with local procedure calls won't suffice.

To create a COM object, some executing entity (an EXE or a Service) will have to perform remote memory allocation and object creation. This is a very complex task. By remote, we mean in another process or on another process. This problem is solved by creating a concept called a COM server. This other entity will have to maintain tight communication with the client.

COM methods can be called across a network

If you have access to a machine on the network, and if a COM server for the object you want to use has been installed on that machine, then you can create the COM object on that computer. Of course, you must the proper privileges, and everything has to be set-up correctly on the other computer.

Since your COM object will not necessarily be on the local machine, you need a good way to "point to" it, even though its memory is somewhere else. Technically, there is no way to do this. In practice, it can be simulated by introducing a whole new level of objects. One of the ways COM does this is with a concept called a proxy/stub. We'll discuss proxy/stubs in some detail later.

Another important issue is passing data between the COM client and it's COM server. When data is passed between processes, threads, or over a network, it is called "Marshalling". Again, the proxy/stub takes care of the marshalling for you. COM can also marshal data for certain types of interface using Type Libraries and the Automation marshaller. The Automation marshaller does not need to be specifically built for each COM server.

COM objects must be unique throughout the world

The whole world? Come on! This may seem like an exaggeration at first, but consider the Internet to be a worldwide network. Even if you're working on a single computer, COM must handle the possibility. Uniqueness is the issue. In C++ all classes are handled unequivocally by the compiler. The compiler can see the class definition for every class used in a program and match up all references to it to make sure they conform to the class exactly. The compiler can also guarantee that there is only one class of a given name. In COM there must be a good way to get a similarly unequivocal match. COM must guarantee that there will only be one object of a given name even though the number of objects available on a worldwide network is huge. This problem is solved by creating a concept called a GUID.

COM is language indpendent

COM servers may be written with a different language and an entirely different operating system. COM objects have the capability of being remotely accessible. That means they may be in a different thread, process, or even on a different computer. The other computer may even be running under a different operating system. There needs to be a good way to transmit parameters over the network to objects on other machines. This problem is solved by creating a new way to carefully specify the interface between the client and server. There is also a new compiler called MIDL (Microsoft® Interface Definition Language). This compiler makes it possible to generically specify the interface between the server and client. MIDL defines COM objects, interfaces, methods and parameters.

COM Vocabulary

One of the problems we're going to have is keeping track of two sets of terminology. You're probably already familiar with C++ and some Object Oriented terminology. This table provides a rough equivalency between COM and conventional terminology.

Concept Conventional (C++/OOP) COM
Client A program that request services from a server. A program that calls COM methods.
Server A program that "serves" other programs. A program that makes COM objects available to a COM client.
Interface None. A pointer to a group of functions that are called through COM.
Class A data type. Defines a group of methods and data that are used together. The definition of an object that implements one or more COM interfaces. Also, "coclass".
Object An instance of a class. The instance of a coclass.
Marshalling None. Moving data between client and server.


You'll notice the concepts of Interface and Marshalling don't translate well into the C++ model. The closest thing to an interface in C++ is the export definitions of a DLL. DLL's do many of the same things as COM when dealing with a tightly coupled (in-process) COM server. Marshalling in C++ is almost entirely manual. If you're trying to copy data between processes and computers, you'll have to write the code using some sort of inter-process communication. You have several choices, including sockets, the clipboard, and mailslots.

The Interface

Thus far, we've been using the word "interface" pretty loosely. My dictionary (1947 American College Dictionary) defines an interface as follows:

"Interface, n. a surface regarded as the common boundary of two bodies or surfaces"

That's actually a useful general description. In COM "interface" has a very specific meaning. COM interfaces are a completely new concept, not available in C++. The concept of an interface is initially hard to understand for many people. An interface is a ghostlike entity that never has a concrete existence. It's sort of like an abstract class - but not exactly.

At its simplest, an interface is nothing but a named collection of functions. In C++, a class (using this terminology) is allowed only one interface. The member functions of that interface are all the public member functions of the class. In other words, the interface is the publicly visible part of the class. In C++ there is almost no distinction between an interface and a class. Here's an example C++ class:


class yyy {
public:
	int DoThis();
private:
	void Helper1();
	int count;
	int x,y,z;
};

When someone tries to use this class, they only have access to the public members. (For the moment we're ignoring protected members and inheritance.) They can't call Helper1, or use any of the private variables. To the consumer of this class, the definition looks like this:


class yyy {
	int DoThis();
};

This public subset of the class is the 'interface' to the outside world. Essentially the interface hides the guts of the class from the consumer.

This C++ analogy only goes so far. A COM interface is not a C++ class. COM interfaces and classes have their own special set of rules and conventions.

COM allows a coclass (COM class) to have multiple interfaces, each interface having its own name and its own collection of functions. The reason for this feature is to allow for more complex and functional objects. This is another concept that is alien to C++. (Perhaps multiple interfaces could be envisioned as a union of two class definitions - something that isn't allowed in C++.)

Interfaces isolate the client from the server

One of the cardinal rules of COM is that you can only access a COM object through an interface. The client program is completely isolated from the server's implementation through interfaces. This is an extremely important point.

Figure 2-1
Figure 2-1
The client program knows nothing about the COM object or C++ class that implements the COM object. All it can see is the interface. The interface is like window into the COM object. The interface designer allows the client to see only those parts of the object that he or she wishes to expose. Figure 2-1 illustrates how all client access to the COM object is funneled through the interface.

The notation used here, a small circle connected by a stick, is the conventional way to draw a COM interface. There are many important rules associated with interfaces. While critical for understanding the details how COM works, we can leave them until later. For now, we'll concentrate on the broad concepts of interfaces.

Imagining a COM Interface

Here's another way to visualize an interface. In this section we'll present a COM interface without any of the C++ baggage. We'll try to look at an interface in its abstract form. Imagine a "car" object. All "car" objects that you are familiar with in the real world have a "driving" interface that allows you to direct the car left and right and also to speed the car up and slow it down. The member functions for the driving interface might be "left", "right", "faster", "slower", "forward" and "reverse". Many cars also happen to have a "radio" interface as well, if they have a radio installed. The functions for the radio interface might be "on", "off", "louder", "softer", "next station" and "previous station".

Driving Radio
Left() On()
Right() Off()
Slower() Louder()
Faster() Softer()
Forward() NextStation()
Reverse() PrevStation()


There are many kinds of cars, but not all of them have radios. Therefore, they do not implement the radio interface, although they do support the driving interface. In all cars that do have radios the capabilities of the radio are the same. A person driving a car without a radio can still drive, but cannot hear music. In a car that does have a radio, the radio interface is available.

COM supports this same sort of model for COM classes. A COM object can support a collection of interfaces, each of which has a name. For COM objects that you create yourself, you will often define and use just a single COM interface. But many existing COM objects support multiple COM interfaces depending on the features they support.

Another important distinction is that the driving interface is not the car. The driving interface doesn't tell you anything about the brakes, or the wheels, or the engine of the car. You don't drive the engine for example, you use the faster and slower methods (accelerator and brakes) of the driving interface. You don't really care how the slower (brake) method is implemented, as long as the car slows down. Whether the car has hydraulic or air brakes isn't important.

Imagine a component

When you're building a COM object, you are very concerned about how the interface works. The user of the interface however, shouldn't be concerned about its implementation. Like the brakes on a car, the user cares only that the interface works, not about the details behind the interface.

This isolation of interface and implementation is crucial for COM. By isolating the interface from it's implementation, we can build components. Components can be replaced and re-used. This both simplifies and multiplies the usefulness of the object.

What's in a name?

One important fact to recognize is that a named COM interface is unique. That is, a programmer is allowed to make an assumption in COM that if he accesses an interface of a specific name, the member functions and parameters of that interface will be exactly the same in all COM objects that implement the interface. So, following our example, the interfaces named "driving" and "radio" will have exactly the same member function signature in any COM object that implements them. If you want to change the member functions of an interface in any way, you have to create a new interface with a new name.

The source of all interfaces - IUnknown

Traditional explanations of COM start out with a thorough description of the IUnknown interface. IUnknown is the fundamental basis for all COM interfaces. Despite its importance, you don't need to know about IUnknown to understand the interface concept. The implementation of IUnknown is hidden by the higher level abstractions we'll be using to build our COM objects. Actually, paying too much attention to IUnknown can be confusing. Let's deal with it at a high level here so you understand the concepts.

IUnknown is like an abstract base class in C++. All COM interfaces must inherit from IUnknown. IUnknown handles the creation and management of the interface. The methods of IUnknown are used to create, reference count, and release a COM object. All COM interfaces implement these 3 methods and they are used internally by COM to manage interfaces. You will likely never call these 3 methods yourself.

A typical COM object

Now let's put all of these new concepts together and describe a typical COM object and a program that wants to access it. In the next section and the following chapters we will make this real by implementing the actual code for the object.

Imagine that you want to create the simplest possible COM object. This object will support a single interface, and that interface will contain a single function. The purpose of the function is also extremely simple - it beeps. When a programmer creates this COM object and calls the member function in the single interface the object supports, the machine on which the COM object exists will beep. Let's further imagine that you want to run this COM object on one machine, but call it from another over the network.

Here are the things you need to do to create this simple COM object:

  • You need to create the COM object and give it a name. This object will be implemented inside a COM server that is aware of this object.
  • You need to define the interface and give it a name.
  • You need to define the function in the interface and give it a name.
  • You'll need to install the COM server.

For this example, let's call the COM object Beeper, the interface IBeep and the function Beep. One problem you immediately run into in naming these objects is the fact that all machines in the COM universe are allowed to support multiple COM servers, each containing one or more COM objects, with each COM object implementing one or more interfaces. These servers are created by a variety of programmers, and there is nothing to stop the programmers from choosing identical names. In the same way, COM objects are exposing one or more named interfaces, again created by multiple programmers who could randomly choose identical names. Something must be done to prevent name collision, or things could get very confusing. The concept of a GUID, or a Globally Unique IDentifier, solves the "how do we keep all of these names unique" problem.

How to be unique - the GUID

There are really only two definitive ways to ensure that a name is unique:

  1. you register the names with some quasi-governmental organization.
  2. you use a special algorithm that generates unique numbers that are guaranteed to be unique world-wide (no small task).

The first approach is how domain names are managed on the network. This approach has the problem that you must pay $50 to register a new name and it takes several weeks for registration to take effect.

The second approach is far cleaner for developers. If you can invent an algorithm that is guaranteed to create a unique name each time anyone on the planet calls it, the problem is solved. Actually, this problem was originally addressed by the Open Software Foundation (OSF). OSF came up with an algorithm that combines a network address, the time (in 100 nanosecond increments), and a counter. The result is a 128-bit number that is unique.

The number 2128 power is an extremely large number. You could identify each nanosecond since the beginning of the universe - and still have 39 bits left over. OSF called this the UUID, for Universally Unique Identifier. Microsoft uses this same algorithm for the COM naming standard. In COM Microsoft decided to re-christen it as a Globally Unique Identifier.

The convention for writing GUID's is in hexadecimal. Case isn't important. A typical GUID looks like this:


"50709330-F93A-11D0-BCE4-204C4F4F5020"

Since there is no standard 128-bit data type in C++, we use a structure. Although the GUID structure consists of four different fields, you'll probably never need to manipulate its members. The structure is always used in its entirety.


typedef struct _GUID
{
    unsigned long Data1;
    unsigned short Data2;
    unsigned short Data3;
    unsigned char Data4[8];
} GUID;

The common pronunciation of GUID is "gwid", so it sounds like 'squid'. Some people prefer the more awkward pronunciation of "goo-wid" (sounds like druid).

GUIDs are generated by a program called GUIDGEN. In GUIDGEN you push a button to generate a new GUID. You are guaranteed that each GUID you generate will be unique, no matter how many GUIDs you generate, and how many people on the planet generate them. This can work because of the following assumption: all machines on the Internet have, by definition, a unique address. Therefore, your machine must be on the network in order for GUIDGEN to work to it's full potential. Actually, if you don't have a network address GUIDGEN will fake one, but you reduce the probability of uniqueness.

Both COM objects and COM interfaces have GUIDs to identify them. So the name "Beeper" that we choose for our object would actually be irrelevant. The object is named by its GUID. We would call the object's GUID the class ID for the object. We could then use a #define or a const to relate the name Beeper to the GUID so that we don't have 128-bit values floating throughout the code. In the same way the interface would have a GUID. Note that many different COM objects created by many different programmers might support the same IBeep interface, and they would all use the same GUID to name it. If it is not the same GUID, then as far as COM is concerned it is a different interface. The GUID is the name.

A COM server

The COM server is the program that implements COM interfaces and classes. COM Servers come in three basic configurations.

  • In-process, or DLL servers
  • Stand-alone EXE servers
  • Windows NT based services.

COM objects are the same regardless of the type of server. The COM interfaces and coclasses(***) don't care what type of server is being used. To the client program, the type of server is almost entirely transparent. Writing the actual server however, can be significantly different for each configuration:

  • In-Process servers are implemented as Dynamic Link Libraries (DLL's). This means that the server is dynamically loaded into your process at run-time. The COM server becomes part of your application, and COM operations are performed within application threads. Traditionally this is how many COM objects have been implemented because performance is fantastic - there is minimal overhead for a COM function call but you get all of the design and reuse advantages of COM. COM automatically handles the loading and unloading of the DLL.
  • An out-of-process server has a more clear-cut distinction between the client and server. This type of server runs as a separate executable (EXE) program, and therefore in a private process space. The starting and stopping of the EXE server is handled by the Windows Service Control Manager (SCM). Calls to COM interfaces are handled through inter-process communication mechanisms. The server can be running on the local computer, or on a remote computer. If the server is on a remote computer, we refer to this as "Distributed COM", or DCOM.
  • Windows NT offers the concept of a service. A service is a program that is automatically managed by Windows NT, and is not associated with the desktop user. This means services can start automatically at boot time and can run even if nobody is logged on to Windows NT. Services offer an excellent way to run COM server applications.
  • There is a fourth type of server, called a "surrogate". This is essentially a program that allows an in-process server to run remotely. Surrogates are useful when making a DLL-based COM server available over the network.

Interactions between client and server

In COM, the client program drives everything. Servers are entirely passive, only responding to requests. This means COM servers behave in a synchronous manner toward individual method calls from the client.

  • The client program starts the server.
  • The client requests COM objects and interfaces.
  • The client originates all method calls to the server.
  • The client releases server interfaces, allowing the server to shut down.

This distinction is important. There are ways to simulate calls going from server to client, but they are difficult to implement and fairly complex (They are called callbacks). In general, the server does nothing without a client request.

Here is a typical interaction between a COM client and server:

Client Request Server Response
Requests access to a specific COM interface, specifying the COM class and interface (by GUID)
  • Starts the server (if required). If it is an In-Process server, the DLL will be loaded. Executable servers will be run by the SCM.
  • Creates the requested COM object.
  • Creates an interface to the COM object.
  • Increments the reference count of active interfaces.
  • Returns the interface to the client.
Calls a method of the interface. Executes the method on a COM object.
Release the interface
  • Decrements the interfaces reference count.
  • If the reference count is zero, it may delete the COM object.
  • If there are no more active connections, shut down the server. Some servers do not shut themselves down.

If you're going to understand COM, you must take a client-centric approach.

Summary

We've tried to look at COM from several points of view. C++ is the native language of COM, but it's important to see beyond the similarities. COM has many analogues in C++, but it has important differences. COM offers a whole new way of communicating between clients and servers.

The interface is one of the most important COM concepts. All COM interactions go through interfaces, and they shape that interaction. Because interfaces don't have a direct C++ counterpart, they are sometimes difficult for people to grasp. We've also introduced the concept of the GUID. GUID's are ubiquitous in COM, and offer a great way to identify entities on a large network.

COM servers are merely the vehicles for delivering COM components. Everything is focused on the delivery of COM components to a client application. In the following chapters, we'll create a simple client and server application to demonstrate these concepts.

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



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