Delphi Programming Guide
Delphi Programmer 

Menu  Table of contents
Bookmark and Share

Part I - Foundations
  Chapter 1 Delphi 7 and Its IDE
  Chapter 2 The Delphi Programming Language
  Chapter 3 The Run-Time Library
  Chapter 4 Core Library classes
  Chapter 5 Visual Controls
  Chapter 6 Building the User Interface
  Chapter 7 Working with Forms
Part II - Delphi Object-Oriented Architectures
  Chapter 8 The Architecture of Delphi Applications
  Chapter 9 Writing Delphi Components
  Chapter 10 Libraries and Packages
  Chapter 11 Modeling and OOP Programming (with ModelMaker)
  Chapter 12 From COM to COM+
Part III - Delphi Database-Oriented Architectures
  Chapter 13 Delphi's Database Architecture
  Chapter 14 Client/Server with dbExpress
  Chapter 15 Working with ADO
  Chapter 16 Multitier DataSnap Applications
  Chapter 17 Writing Database Components
  Chapter 18 Reporting with Rave
Part IV - Delphi, the Internet, and a .NET Preview
  Chapter 19 Internet Programming: Sockets and Indy
  Chapter 20 Web Programming with WebBroker and WebSnap
  Chapter 21 Web Programming with IntraWeb
  Chapter 22 Using XML Technologies
  Chapter 23 Web Services and SOAP
  Chapter 24 The Microsoft .NET Architecture from the Delphi Perspective
  Chapter 25 Delphi for .NET Preview: The Language and the RTL
       
  Appendix A Extra Delphi Tools by the Author
  Appendix B Extra Delphi Tools from Other Sources
  Appendix C Free Companion Books on Delphi
       
  Index    
  List of Figures    
  List of tables    
  List of Listings    
  List of Sidebars  

 
Previous Section Next Section

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):

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.

Note 

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.

Warning 

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:

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.

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.


 
Previous Section Next Section


 


 


Copyright © 2004-2016 "Delphi Sources". Delphi Programming Guide
     Twitter     Facebook