![]() |
|
Implementing IUnknownBefore 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): type IUnknown = interface ['{00000000-0000-0000-C000-000000000046}'] function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; end; The _AddRef and _Release methods are used to implement reference counting. The QueryInterface method handles the type information and type compatibility of the objects.
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:
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:
Globally Unique IdentifiersThe 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.
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: const 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. ![]() 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 FactoriesWhen 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. |
|
Copyright © 2004-2025 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide |
|