Implementing IUnknown

Implementing IUnknown

Before we begin looking at an example of COM development, I'll introduce a few COM basics. Every COM object must implement the IUnknown interface, also dubbed IInterface in Delphi for non-COM usage of interfaces (as you saw in Chapter 2). This is the base interface from which every Delphi interface inherits, and Delphi provides a couple of different classes with ready-to-use implementations of IUnknown/IInterface, including TInterfacedObject and TComObject. The first can be used to create an internal object unrelated to COM, and the second is used to create objects that can be exported by servers. As you'll see later in this chapter, several other classes inherit from TComObject and provide support for more interfaces, which are required by Automation servers or ActiveX controls.

As mentioned in Chapter 2, the IUnknown interface has three methods: _AddRef, _Release, and QueryInterface. Here is the definition of the IUnknown interface (extracted from the System unit):

  IUnknown = interface
    function QueryInterface(const IID: TGUID;
      out Obj): Integer; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;

The _AddRef and _Release methods are used to implement reference counting. The QueryInterface method handles the type information and type compatibility of the objects.


In the previous code, you can see an example of an out parameter, a parameter passed back from the method to the calling program but without an initial value passed by the calling program to the method. The out parameters have been added to the Delphi language to support COM, but they can as well be used in a normal application, as in certain circumstances this makes parameters passing more efficient (significant cases are those of interfaces, strings, and dynamic arrays). It's also important to note that although Delphi's language definition for the interface type is designed for compatibility with COM, Delphi interfaces do not require COM. This was already highlighted in Chapter 2, where I built an interface-based example with no COM support.

You don't usually need to implement these methods, because you can inherit from one of the Delphi classes already supporting them. The most important class is TComObject, defined in the ComObj unit. When you build a COM server, you'll generally inherit from this class.

TComObject implements the IUnknown interface (mapping its methods to ObjAddRef, ObjQuery Interface, and ObjRelease) and the ISupportErrorInfo interface (through the InterfaceSupports-ErrorInfo method). Notice that the implementation of reference counting for the TComObject class is thread-safe, because it uses the InterlockedIncrement and InterlockedDecrement API functions instead of the plain Inc and Dec procedures.

As you would expect if you remember the discussion of reference counting in Chapter 2, the _Release method of TInterfacedObject destroys the object when there are no more references to it. The TComObject class does the same. Also keep also in mind that Delphi automatically adds the reference-counting calls to the compiled code when you use interface-based variables, including COM variables.

Finally, notice that the role of the QueryInterface method is twofold:

  • QueryInterface is used for type checking. The program can ask an object the following questions: Are you of the type I'm interested in? Do you implement the interface and the specific methods I want to call? If the answers are no, the program can look for another object, maybe asking another server.

  • If the answers are yes, QueryInterface usually returns a pointer to the object, using its reference output parameter (Obj).

To understand the role of the QueryInterface method, it is important to keep in mind that a COM object can implement multiple interfaces, as the TComObject class does. When you call QueryInterface, you ask for one of the possible interfaces of the object, using the TGUID parameter.

In addition to the TComObject class, Delphi includes several other predefined COM classes. Here is a list of the most important COM classes of the Delphi VCL, which you'll use in the following sections:

  • TTypedComObject, defined in the ComObj unit, inherits from TComObject and implements the IProvideClassInfo interface (in addition to the IUnknown and ISupportErrorInfo interfaces already implemented by the base class, TComObject).

  • TAutoObject, defined in the ComObj unit, inherits from TTypedComObject and implements also the IDispatch interface.

  • TActiveXControl, defined in the AxCtrls unit, inherits from TAutoObject and implements several interfaces (IPersistStreamInit, IPersistStorage, IOleObject, and IOleControl, to name just a few).

Globally Unique Identifiers

The QueryInterface method has a parameter of the TGUID type. This type represents a unique ID used to identify COM object classes (in which case the GUID is called CLSID), interfaces (in which case you'll see the term IID), and other COM and system entities. When you want to know whether an object supports a specific interface, you ask the object whether it implements the interface that has a given IID (which for the default COM interfaces is determined by Microsoft). Another ID is used to indicate a specific class or CLSID. The Windows Registry stores this CLSID with indications of the related DLL or executable file. The developers of a COM server define the class identifier.

Both of these IDs are known as GUIDs, or globally unique identifiers. If each developer uses a number to indicate its COM server, how can we be sure these values are not duplicated? The short answer is that we cannot. The real answer is that a GUID is such a long number (16 bytes, or 128 bits—a number with 38 digits!) that it is almost impossible to come up with two random numbers having the same value. Moreover, programmers should use the specific API call CoCreateGuid (directly or through their development environment) to come up with a valid GUID that reflects some system information.

GUIDs created on machines with network cards are guaranteed to be unique, because network cards contain unique serial numbers that form a base for the GUID creation. GUIDs created on machines with CPU IDs (such as the Pentium III) should also be guaranteed unique, even without a network card. With no unique hardware identifier, GUIDs are unlikely to ever be duplicated.


Be careful not to copy the GUID from someone else's program (which can result in two different COM objects using the same GUID). You should also not make up your own ID by entering a casual sequence of numbers. To avoid any problem, press Ctrl+Shift+G in the Delphi editor, and you will get a new, properly defined, truly unique GUID.

In Delphi, the TGUID type (defined in the System unit) is a record structure, which is quite odd but required by Windows. Thanks to some Delphi compiler magic, typically set up to help make more straightforward some tedious or time consuming task, you can assign a value to a GUID using the standard hexadecimal notation stored in a string, as in this code fragment:

  Class_ActiveForm1: TGUID = '{1AFA6D61-7B89-11D0-98D0-444553540000}';

You can also pass an interface identified by an IID where a GUID is required, and again Delphi will magically extract the referenced IID. If you need to generate a GUID manually and not in the Delphi environment, you can call the CoCreateGuid Windows API function, as demonstrated by the NewGuid example (see Figure 12.1). This example is so simple that I've decided not to list its code.

Click To expand Figure 12.1: An example of the GUIDs generated by the NewGuid example. Values depend on my computer and the time I run this program.

To handle GUIDs, Delphi provides the GUIDToString function and the opposite StringToGUID function. You can also use the corresponding Windows API functions, such as StringFromGuid2, but in this case, you must use the WideString type instead of the string type. Any time COM is involved, you have to use the WideString type, unless you use Delphi functions that automatically do the required conversion for you. When you need to bypass Delphi functions that can call COM API functions directly, you can use the PWideChar type (pointer to null-terminated arrays of wide characters) or casting a WideString to PWideChar (exactly as you cast a string to the PChar type when calling a low-level Windows API.) does the trick.

The Role of Class Factories

When have registered the GUID of a COM object in the Registry, you can use a specific API function to create the object, such as the CreateComObject API:

function CreateComObject (const ClassID: TGUID): IUnknown;

This API function will look into the Registry, find the server registering the object with the given GUID, load it, and, if the server is a DLL, call the DLLGetClassObject method of the DLL. This is a function every in-process server must provide and export:

function DllGetClassObject (const CLSID, IID: TGUID;
  var Obj): HResult; stdcall;

This API function receives as parameters the requested class and interface, and it returns an object in its reference parameter. The object returned by this function is a class factory.

As the name suggests, a class factory is an object capable of creating other objects. Each server can have multiple objects. The server exposes a class factory for each of the COM objects it can create. One of the many advantages of the Delphi simplified approach to COM development is that the system can provide a class factory for you. For this reason, I didn't add a custom class factory to my example.

The call to the CreateComObject API doesn't stop at the creation of the class factory, however. After retrieving the class factory, CreateComObject calls the CreateInstance method of the IClassFactory interface. This method creates the requested object and returns it. If no error occurs, this object becomes the return value of the CreateComObject API.

By setting up this mechanism (including the class factory and the DLLGetClassObject call), Delphi makes it simple to create COM objects. At the same time, Window's CreateComObject is just a simple function call with complex behavior behind the scenes. What's great in Delphi is that many complex COM mechanisms are handled for you by the RTL. Let's begin looking in detail at how Delphi makes COM easy to master.

For each of the core VCL COM classes, Delphi also defines a class factory. The class factory classes form a hierarchy and include TComObjectFactory, TTypedComObjectFactory, TAutoObjectFactory, and TActiveXControlFactory. Class factories are important, and every COM server requires them. Usually Delphi programs use class factories by creating an object in the initialization section of the unit defining the corresponding server object class.

Part I: Foundations