COM, OLE, ActiveX

Top  Previous  Next

The idea behind COM, OLE and ActiveX

A standard PC running any version of the Windows O/S, standard software like the Office package and maybe some special purpose software for the daily work of its owner, is, simply speaking, a big heap of executables and dynamic load libraries that operate on data files. Depending on who wrote this software, which languages were used and even which versions of these languages were used, this software does not really cooperate with one another, it's more or less a coexistence of programs on one machine. If you're lucky (and this can even turn out as a downside), some of these programs can share their runtime systems. But this does only apply to the code. Normally the data written and read from files is in a proprietary format that suits the needs of the programmer and the program. COM is the approach to get rid of all this separation. With COM, programs can cross-call to one another, services can be made available across runtime system boundaries, data objects can be shared between languages and standards for services can be defined without being tied to a single program. The means for this cooperation are provided by Windows with its COM system. It takes care and provides all means necessary for cooperation between the COM participants, servers and clients.

 

I'm well aware that it's impossible to explain COM in-depth in the few paragraphs of this documentation. But I hope my explanations will light your curiosity for more. You'll find all the details in David Chappell's book.

COM

COM is the acronym for Component Object Model. It's specifications describe how the above ideas can be put to work on the Windows platform. The COM support functions provided by Windows help you in succeeding with these goals.

GUID

GUID is the acronym for Globally Unique Identifier. GUIDs are 16 bytes (128 bits) wide random numbers which can identify COM classes (CLSIDs), COM interfaces (IIDs), categories (CATIDs) or programs (ProgIDs). There are two ways to represent a GUID. One is the plain sequence of the sixteen bytes which can be referred to through a #define constant, for example IID_IDispatch represents the GUID of the IDispatch interface. The other way to represent a GUID is a "more human readable form" bringing some separation and bracketing into the game. Normally, when a function receives a GUID as parameter, the plain version of the GUID represented by the constant must be used.

 

Here is a little sample program that shows both representations of the IID of the IDispatch interface.

 

 

#INCLUDE "CockpitComLibs.ch"

#INCLUDE "ole.ch"

 

FUNCTION main

 

LOCAL guid := IID_IDispatch

 

* Should be called in every thread using the O/S COM/OLE functions

CoInitialize()

 

* This line will display 00 04 02 00 00 00 00 00 C0 00 00 00 00 00 00 46

? HexString(guid)

 

* This line will display {00020400-0000-0000-C000-000000000046}

? StringFromGUID2(guid)

 

RETURN NIL

 

COM Server

COM servers provide the code implementing COM objects. COM servers fall into 3 categories:

 

In-process server: A Dll that is loaded into a process' address space that requests one of it's COM objects.
Local server: A program that is started in a separate process whose objects are accessible from the COM client process.
Remote server: A program on a different computer which provides COM objects to COM clients over a network:

 

COM servers are never actually addressed when you are using COM objects in your applications. All you need to do is request an object and specify a machine on which you want to run that object. Of course you don't have to specify a machine if you want to run the object locally. Windows does the rest of the work for you, like identifying the server, loading and starting the server if necessary.

COM Client

COM clients are programs using COM servers through COM objects.

COM Class

COM classes are only used to identify COM objects. Every COM class is identified by a GUID, in this case called the CLSID. A COM class is some sort of useful vaporware, intangible but required to instantiate an object. Unlike Xbase++ classes, a COM class does not come with class methods or class vars. It's actually just the ID and the reference to the program or dll implementing the COM objects of this class. COM classes can be found as CoClasses in Type Libraries. Here you can find the information about the interfaces the COM object provides.

COM Object

COM objects are the way to provide services via COM. That may sound weird, but there is no reason why you shouldn't provide a simple collection of non-related functions, like a toolbox, as a COM object. Each of your functions could be implemented as a method of your hypothetical COM object. Every other program, that is able to act as a COM client, would be able to use these functions. As you can see, the object is not the big deal here, but its collection of methods. COM actually goes one step further, it makes these collections "strong entities" by introducing COM interfaces.

 

COM objects are in some ways different from what you are accustomed to think of as an object. In Xbase++ an object is created from a class which means all instance methods and vars defined at class level for the instances will be available in the instances. A COM object can come with many different interfaces and when you instantiate a COM object, you must also specify an interface you intend to use. If you request an interface the object does not provide, instantiation of the COM object will fail. The COM object is not created from or by a class, it is created by a class factory object. A COM class factory can create one type (=class) of object. A class factory object is also a COM object.

After successfully instantiating a COM object, you don't get a hold of the object itself, but of an interface of the COM object. Having access to a COM object through any COM interface, you can request one or more other interfaces of this very object. You could end up with different interfaces (and hence many references) of the same object. This is a common situation with COM objects.

COM Interfaces

COM interfaces are the pivotal elements in COM technology. They are the "tangible beings" between all the other vaporware and ideas surrounding them. First of all, COM interfaces are nothing but collections of methods. After an interface has been defined, which means all methods and their parameters provided by this interface have been specified, it is assigned a GUID, in this case called the IID. After publishing this interface, it may not be changed any more. From that moment on the IID defines exactly this set of methods with their parameters. If you decide to enhance this interface by adding new methods or extending a parameter list, you must give the modified interface a new IID. Besides it's computer-readable IID every interface comes with a human readable name. This name always starts with an upper case "I" as in "IUnknown" or "IDispatch".

 

Since we just left the vaporware area with COM interfaces, we must get down to details how these interfaces are actually provided, how their methods are called and how parameters must be passed. I think that the creators of COM were big fans of C++ and so the COM specification tells us that COM interfaces are exactly like the interfaces to C++ objects. There's a danger of mixing things up here. Although you are accessing COM interfaces like C++ objects, COM objects are completely different from C++ objects. As in Xbase++, a C++ object provides only one interface. COM objects provide multiple interfaces, each of them accessed like a C++ object.

 

Since we cannot directly call a C++ object's methods from Xbase++, we cannot use COM objects with plain Xbase++. Cockpit fills this gap. The Cockpit ComInterface class provides the methods necessary to set up a wrapper to a COM interface that can be used from Xbase++. This basic wrapper class provides the means to call a method through its number and a set of parameters of the actual COM interface method. Although this basic class would suffice to access any COM object, you wouldn't be too happy with all the parameter conversions you'd have to do on your own. That's why Cockpit comes with a set of preprocessor directives to help you create your own Xbase++ objects representing those COM interfaces. Once again, we're in danger of mixing things up. These Xbase++ interface objects provided by Cockpit are referring to one interface of a COM object. Hence we can have multiple Cockpit/Xbase++ interface objects that refer to the same COM object at the same time in a running program. The interface classes provided by Cockpit are generally given the human readable name of the wrapped COM interfaces like "IUnknown" or "IDispatch".

 

As stated in the definition of COM servers, they can exist in the same process, another process or even reside on a different computer in the network. Again, it's the interfaces that actually connect a COM client with a COM server. If the COM server is instantiated within the same address space (=same process) as its client, passing of parameters is easy going, as simple as calling a function in a loaded Dll. But if the requested COM object is instantiated in a different process, the parameters must be transferred between the client and the server process. The Windows COM subsystem will install a "proxy" in the client process that "looks" just like a COM object having been loaded into this process. But upon a call to a method it does not directly send the CPU to the object's code because the code resides in a different address space and is hence invisible to the client thread. Instead it packs together the parameters and somehow sends them over to the server process. On the other side in the server process the parameters are unpacked, the actual method is called and the results are sent back to the client process. The client process (or better the client thread) has eagerly been waiting for the result to be returned and now it returns from the proxy after unpacking the return value and reference parameters. This passing of the parameters and the return value between processes is called marshaling.

 

Things aren't too different for remote COM servers that reside on different machines. But instead of transferring the parameters between processes, now the parameters are transferred over the network between computers.

 

This may sound like pure horror to an Xbase++ programmer. Actually there's not much to worry about because the COM subsystem in Windows does all the dirty work for you. All you have to do is create an object, specify the interface you intend to use and, if necessary, a machine to run the COM object on. After that, you won't even notice a difference between an in-process, local or remote server.

 

From a programmer's point of view, there are standard interfaces and custom interfaces. As you know by now, interfaces are identified by their IID which uniquely and exactly identifies the methods and their parameters of an interface. Standard interfaces are interfaces that support some special COM functionality and custom interfaces are ones that provide some custom functionality, e.g. an interface providing a collection of all trigonometric functions. There are many standard interfaces you will encounter when you start digging into COM yourself. Among them are prominent ones like IUnknown (provided by every COM object),  IDispatch (providing the methods for OLE Automation) or ITypeInfo (providing information about COM objects, their interfaces, methods. structures, enumerations). The Cockpit COM Library already provides wrappers for many of them.

 

All the wrappers are based on the ComInterface class. An instance of the ComInterface class stores two important values of an interface and these values can be accessed as instance vars. :baseAddress is the base address of the COM interface of one specific object, :vtblAddress is the address of the virtual function table of the COM object.

COM Interface Member Functions

The methods provided by a COM interface are called member functions (or just members) of the interface. Member functions always return a result code as function value. Other values are returned through reference parameters. The COM specification does not define member variables, hence all access to COM objects happens through methods in interfaces.

Inheritance

As you know from the definitions of COM classes and objects, they are nothing but concepts with a name. COM does not define or support any kind of inheritance between classes or objects. If you create your own COM objects you can implement some kind of inheritance by providing interfaces with your objects that your objects import from other COM objects. This can be done by writing wrappers for the imported interfaces and exporting them as your own interfaces. This technique is called "containment" or "delegation". Or you can simulate inheritance by actually passing on the interface pointers to your object's clients that your object holds to the COM objects it uses. This technique is called "aggregation". But unless you are implementing your own COM objects, this won't bother you at all.

 

Interfaces, however, do support inheritance. By definition every COM interface must inherit from the IUnknown interface. How does this work? Suppose you have an interface IMyInterface providing the methods MyMethod1, MyMethod2 and MyMethod3. According to the COM specification your interface must inherit from the IUnknown interface. This adds the IUnknown methods QueryInterface, AddRef and Release to your actual interface. Hence the interface you implement must support the methods QueryInterface, AddRef, Release, MyMethod1, MyMethod2 and MyMethod3. Inheritance of interfaces is not as bad as you might think because an interface can only inherit from a maximum of one other interface. This interface, of course, could have inherited from another interface itself. But interfaces that indirectly inherit from more than one other interface (IUnknown) are actually quite rare. The exception are dual interfaces which inherit from IDispatch which in turn inherits from IUnknown.

The IUnknown interface

The IUnknown interface is the basic COM interface from which every other interface must inherit. IUnknown provides only three methods (or "members"). One can be used to acquire another interface and two are used for reference counting. Here are the three methods (or "member functions"):

 

IUnknown:QueryInterface(<riid>,<ppf>): Query the object for another interface. The interface id is specified in <riid> and the requested interface will be returned in <ppf>. If an interface is returned, the object's reference counter has been incremented.

 

IUnknown:AddRef(): Increment the reference counter of the interface on the object by one.

 

IUnknown:Release(): Decrement the reference counter of the interface on the object by one.

 

Since every other COM interface inherits from this interface, reference counting is available for every interface and you can query an object for another interface through every interface it provides.

 

Since your program accesses COM objects through interfaces, you never hold a direct reference to a COM object. So if you have references to different interfaces in your program, you cannot easily say if some of these interfaces actually access the same object. The COM conventions say that by comparing the addresses of the IUnknown interface returned by any interface of an object you can check if the same object is referenced. So if you request the IUnknown interface through the interfaces you want to check, you can actually check if the interfaces access the same object by comparing their base addresses (it's available as :baseAddress instance var in any object inheriting from Cockpit's ComInterface).

The IDispatch Interface

The IDispatch Interface is a Visual Basic invention. Long ago when the IDispatch interface was specified, it was impossible to access COM objects from Visual Basic, because Visual Basic had no way to handle the C++ like method tables required to use COM interfaces and the C++ data types required for passing parameters. So some smart guys back then must have said: Let's define one interface that is able to wrap any other interface. We'll put everything necessary to access this special interface into the Visual Basic runtime system and from then on we'll be able to access every COM object that supports this interface from Visual Basic programs. So the IDispatch interface was defined and, believe it or not, it only has 4 members (besides, of course, the three members of the IUnknown interface from which it inherits):

 

IDispatch:GetTypeInfoCount(): Check, if type information is available for the object.

IDispatch:GetTypeInfo(): Get a type information interface for the object.

IDispatch:GetIDsOfNames(): Translate method or parameter names to their dispatch assigned dispatch IDs.

IDispatch:Invoke(): Call a method, set or get a property though its dispatch ID.

 

But how can you call different methods with different parameters through this interface? To specify the method to be invoked you must pass a DISPID (dispatch ID, one is assigned to every method accessible through your IDispatch interface) to the Invoke method. Inside the Invoke method there is a big DO CASE statement that checks the DISPID and actually dispatches the CPU to the appropriate code. Parameters must be wrapped into an array containing type and value information by the caller. Inside the called method they must be unwrapped again. This does not actually make IDispatch a fast way to invoke methods. Still it has become very popular because you have to put only one wrapper for the IDispatch interface into your runtime system to use COM objects from your development platform.

 

Besides methods that can be called, the IDispatch interface supports properties that can be set and read. Each property gets its own DISPID and by specifying an invoke-kind of SET or GET when calling the method Invoke() with the property's DISPID you can specify if you want to set or get this very property. However, you are still calling a method in a COM interface in this case that does a virtual set or get. You are not directly accessing a property. That is not possible with COM objects.

 

Apart from its speed problems due to parameter conversions IDispatch comes with another nasty problem which results from the idea behind it: Give simple access to an object through methods. The IDispatch paradigm implies an object has only one set of methods and properties. That means that an object accessed through IDispatch can have only one interface: IDispatch. - A horrible blow for the idea of separating interfaces and objects into two strong entities.

Dispinterfaces

Dispinterfaces are always based in the IDispatch interface, but the actual methods invoked and the properties set and read through it are different from dispinterface to dispinterface. An object can even have more than one dispinterface. Dispinterfaces can (and must in the former case) have IIDs that differ from the IID of IDispatch. If this is the case, one of the dispinterfaces must be tagged as default. That's the one that is returned by QueryInterface if you request an interface with the IID of IDispatch.

Dual Interfaces

As stated before, the IDispatch interface is rather slow because the methods to be actually called must be determined by checking the DISPID and the parameters to be passed must be wrapped and unwrapped again before the actual code can be executed. Still it is a good idea to provide the IDispatch interface with COM objects because they can be used from almost any development platform. But instead of providing the same interface twice in two flavors (IDispatch and standard COM) we can make use of interface inheritance to put both interfaces into one. Let's say you have an interface IMyInterface with the Methods A, B, and C. If this interface inherits from IDispatch, it supports these methods:

 

QueryInterface(): Inherited from IUnknown through IDispatch

AddRef(): Inherited from IUnknown through IDispatch

Release(): Inherited from IUnknown through IDispatch

GetTypeInfoCount(): Inherited from IDispatch

GetTypeInfo(): Inherited from IDispatch

GetIDsOfNames(): Inherited from IDispatch

Invoke(): Inherited from IDispatch

A(): Defined in IMyInterface

B(): Defined in IMyInterface

C(): Defined in IMyInterface

 

In a dual interface like this one the methods A, B and C are made available through the IDispatch interface with the necessary DISPID-method-dispatch and parameter wrapping. But they are also available for straight calls in the C++ manner.

 

What makes this solution perfect is that every dual interface can be treated exactly like an IDispatch interface by platforms not supporting the C++ way of calling methods. Other platforms (like C++ or plain C) can make use of the straight calls which is a lot faster because all the dispatching and wrapping overhead is not necessary. The only drawback in this case is that the parameter types available are limited to the ones supported by OLE automation.

 

With Xbase++ (1.9 and later) you have access to the IDispatch part of any dual interface. With Cockpit you may use the IDispatch part or you can use the "fast lane" to call the methods directly.

 

Many interfaces available through COM are defined as dual interfaces. Particularly objects commercially available from third party vendors for use in your applications support dual interfaces to maximize their target audience. Still there are many interfaces provided by the O/S that do not inherit from IDispatch. All the interesting interfaces provided by the Windows' COM subsystem for management are non-dual interfaces that cannot be accessed through the Xbase++ AutomationObject.

Reference Counting

The easiest way to create an (uninitialized) object is to call CoCreateInstance passing the CLSID of the object you're after and the IID of an interface the object supports. If everything works out fine you'll hold a reference to the newly created object through the interface you requested. But how can you get rid of this object when you're done with it? There might be something like a Destroy() method but that would mean you're 100% responsible for the lifetime of an object. Suppose you start a new thread and pass this object in as an initial parameter. Then you'd have to check if the object is still in use before destroying it. Actually the new and the old thread might now have to destroy the object depending on which one terminates last. Or suppose you call a function which puts the object in a STATIC variable which will be accessed later during the course of your program. The same problem. How do you know when there's no more valid reference to the object? That could be accomplished by using a counter which is incremented whenever a new reference to the object is required and decremented when a reference to the object will not be used any more. The creators of COM must have had the same idea when they defined the IUnknown interface. The member functions AddRef() and Release() do exactly that. AddRef() increments this counter and Release() decrements it. When the counter hits 0, which means no reference is active any longer, the object can be destroyed.

 

That may sound simple but it puts you in charge. For example if you pass an interface to a new thread you are responsible to increment the reference counter by calling AddRef(). And whenever you don't need an interface to an object any more you must call Release(). Otherwise the object will live happily ever after until your process terminates. (Some objects will even survive that if their server is an executable running in a separate process.)

 

A call to QueryInterface() will also increment the reference counter of the interface if it succeeds. You are responsible to call Release() when the object is not needed any longer. The same applies to CoCreateInstance(): If you don't need the object any more, you must release it by calling the Release() method. After releasing an object you may not call its methods any more.

 

The current reference count of an object should be returned by the AddRef() and Release() methods. There is no way to query it otherwise. And the returned number of references can be incorrect.

The Differences between Cockpit's COM Support and the Xbase++ ActiveX Wrappers

The new ActiveX support announced for 1.9 will provide you with access to one interface only: IDispatch. Internally the AutomationObject will also use some other interfaces to do some housekeeping, to retrieve event names and parameters and to finally receive events from a wrapped ActiveX object. Hence the AutomationObject will only work with COM objects that support the IDispatch interface or dual interfaces that inherit from IDispatch. This will not prove to be a big hurdle, because many COM objects support the plain IDispatch or dual interfaces. Still there are quite some COM objects, for example in the Windows Shell area, that do not support IDispatch at all. For example shell links cannot be created or modified through an IDispatch interface. Or the running object table (ROT) cannot be accessed through a dispinterface.

 

Cockpit takes a different approach. The Cockpit Core provides a basic ComInterface object that can be used to wrap any COM interface. The Cockpit COM Library comes with a big set of standard interfaces you might need in your applications. But you can also create your own COM interface adapter classes, for example an adapter for the ShellLink object or the Running Object Table. (Actually these adapter classes come with Cockpit.)

 

Cockpit does also provide an AutomationObject (with source) that is very similar to the Automation object provided by Xbase++ 1.9. In the Cockpit version you can see what is going on behind the scenes and you can interfere if problems arise.