Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics Dr.Bob's Delphi Courseware Manuals
 Dr.Bob Examines... #30
See Also: other Dr.Bob Examines columns or Delphi articles

An earlier version of this article originally appeared in Delphi Developer (April 2002). Copyright Pinnacle Publishing, Inc. All rights reserved.

Delphi & COM (1) - COM Objects
It's time for a new series on Delphi development by Bob Swart. This time, I want to start with "regular" COM Objects, move via OLE Automation to COM+ and MTS and end with re-using Delphi COM stuff in the .NET Framework environment (but by that time we'll be talking half a year from now). This first article, I want to keep it simple and create, use and deploy COM Objects.


COM Objects Before we start, let's briefly explain what a COM Object is all about. COM is the Common Object Model that's common among Windows, and can be used by just about any serious (development) environment that runs on Windows. The biggest benefit is the fact that we can create a COM Object in one environment, and use it in a totally different environment. And they both don't need to know anything about each other. Obviously, I'm using Delphi only at this time, but you could also use C++Builder or some other environment (especially when it comes to using COM Objects).

New COM Object
To create COM Objects, we must start Delphi 6, close the default project, do File | New - Other, and go to the ActiveX tab of the Object Repository:

We'll start with a regular COM Object, but will also explore the ActiveX Library (the container for COM Objects), and later articles will cover the Transactional Object and finally the Type Library in some more detail.
For now, just double-click on the COM Object icon, which produces the following message:

The difference between a COM Object placed in an application (Local Server - also called Out-of-Process Server) and a COM Object placed in a DLL (In-Process Server) will be made clear later in this article series. For now, create a new ActiveX Library as container for our (in-Process) COM Object, using File | New - Other, go to the ActiveX tab, and from there select the ActiveX Library icon. Save the resulting project in Euro42.dpr, which only consists of a few lines of code:
  library Euro42;
  uses
    ComServ;

  exports
    DllGetClassObject,
    DllCanUnloadNow,
    DllRegisterServer,
    DllUnregisterServer;

  {$R *.RES}

  begin
  end.
Now that we have an ActiveX Library as container for our COM Objects, it's time to do File | New - Other again, and double-click on the COM Object icon again. This will (finally) produce the New COM Object wizard:

The Class Name will be used both as internal and external name of your COM Object. Normally, you can enter anything here, but for the example in this section use Euro as Class Name. This will result in the classname TEuro, derived from TTypedComObject and implementing the IEuro interface.
The Instancing is set to Multiple Instance, and the Threading Model is set to Apartment by default, which are perfect values (generally, you should not have to change these setting).
Note that we can specify an interface to implement, as well as a description. The description is free (it usually helps to put in something descriptive), but the interface will also automatically be defined once you type the classname. For a classname of Euro, you'll note that the Implemented Interface is set to IEuro. By default, the wizard gives your object a default interface that descends from IUnknown. After exiting the wizard, you can then use the Type Library editor (coming up) to add properties and methods to this interface.
Finally, the checkbox to Include a Type Library is helpful in the remainder of this section, as is the "mark interface Oleautomation" which lets you avoid writing your own proxy-stub DLL for custom marshaling (something I won't cover in this article series, just leave them set).

Instancing/Threading
The five choices you have for threading model are Single, Apartment, Free, Both and Neutral. Single means that all client requests are handled in a single thread (which is not a good idea, because others have to wait until the first client is finished). Apartment means that every client request runs in its own thread, separated from each other (no thread can access the state of another). Class instance data is safe, but we must guard against threading issues when using global variables and the like. This is the preferred threading model that I always use myself. The third option, Free, means that a class instance can be accessed by multiple threads at the same time. This means that class instance data is no longer thread-safe, so you must take care to avoid threading issues here. The fourth option, Both, is a combination of Apartment and Free, in that it is following the Free threading model with the exception that callbacks are executed in the same thread (so parameters in callback functions are safe from multi-threading problems). The last option Neutral is COM+ specific, and defaults to Apartment for COM. It means that client requests can access object instances on different threads, but COM ensures that the calls will not conflict. Still, you'll have to watch threading issues with global variables as well as instance data in between method calls.
For instancing we have three choices. Note that it doesn't matter what we select if we're registering the Active Server Object as an in-process server (as we can see in a moment), but it's good to know what the choices are anyway. The first choice Internal denotes that this COM object is only instantiated within its own DLL. Single Instance means that the application can have one client instance, while multiple instance means that a single application (ActiveX Library) can instantiate more than one instance of the COM object.

Anyway, after you click on the OK button a new unit will be generated (save in Euro.pas) with the following contents:
  unit Euro;
  {$WARN SYMBOL_PLATFORM OFF}
  interface
  uses
    Windows, ActiveX, Classes, ComObj, Euro42_TLB, StdVcl;

  type
    TEuro = class(TTypedComObject, IEuro)
    protected
    end;

  implementation
  uses
    ComServ;

  initialization
    TTypedComObjectFactory.Create(ComServer, TEuro, Class_Euro,
      ciMultiInstance, tmApartment);
  end.

Type Library Editor
Apart from the new unit, we're also presented with the Type Library Editor. This is the place to edit the Type Library for the COM Object (in fact, the Type Library for the entire ActiveX Library).
The Type Library can be seen as the definition of your COM Objects, the interface with methods and properties (and much more in fact) made accesible using a Type Library Editor, which can always be viewed using the View | Type Library menu choice from the Delphi IDE. The Type Library itself has the name of the project with the .tlb extension (Euro42.tlb), and the import unit is placed in the file Euro42_TLB.pas

We can now add new methods and properties to our IEuro interface. And then click on the Refresh Implementation button to make sure the Euro.pas file also shows the properties and method "skeletons" (so we can implement them).
As an example, click on the New Method button and add the About method. Now, click on the Refresh Implementation button and write the following code inside the generated (but still empty) About method in the Euro unit:
  function TEuro.About: HResult;
  begin
    ShowMessage('Euro Converter (c) 2002 by Bob Swart')
  end;
Make sure to add the Dialogs unit to your uses clause, or this won't compile. Compile your ActiveX Library with the COM Object. This still gives a warning that indicates that the return value of function TEuro.About might be undefined. Quite right. Why do we need a function result in the first place? Well, you can turn it into a procedure (using the Type Library editor), but in the COM world, it's become custom to return a so-called HResult value as error indicator. If HResult is S_OK, then everything went fine, otherwise the result value indicates a certain error. Since COM Objects are used by many different environments, I personally think it's best to listen to such conventions and make sure we behave ourselves. So, just add one more line to the TEuro.About function as follows:
  function TEuro.About: HResult;
  begin
    ShowMessage('Euro Converter (c) 2002 by Bob Swart');
    Result := S_OK
  end;
Note that while it compiles without problems, you are (still) not able to run it, since it's a DLL (and not an executable). COM Objects are used by COM clients, and we'll build one now to show how to use the Euro COM Object.

Register COM Objects
But before we can do that, we must first register the ActiveX Library (which will register all available COM Objects defined and implemented in it), using the Run | Register ActiveX Server menu option. This will produce a message if the registration succeeded.

We'll get back to other means of registration later, with other kinds of COM and ActiveX Objects.

Using COM Objects
Now that we have a registered ActiveX Library with a COM Object inside, it's time to create a new application to use the COM Object (and more specifically, to retrieve a handle to the interface, and use the implementation of the COM Object).
Start a new application, save the main form in MainForm.pas and the project itself in EuroClient.dpr. Next, add both ComObj and the Euro42_TLB Type Library import unit to the MainForm unit of this new project (so we know the definition of the IEuro interface), drop a button on the main form, and write the following code for an OnClick event:

  procedure TForm1.Button1Click(Sender: TObject);
  var
    Euro: IEuro;
  begin
    Euro := CreateCOMObject(Class_Euro) as IEuro;
    Euro.About
  end;
Now, compile and run the client application. Once you click on the button, a message dialog will pop up as follows:

Note that the caption says EuroClient (the name of our client application - using the COM Object in the ActiveX Library) and the message says "Euro Converter (c) 2002 by Bob Swart" as defined in the About method inside our Euro COM Object.

What about S_OK?
Remember how the Euro.About method actually isn't a method but a function? Why should we call it as if it's just a method? Well, in this case, it will always return S_OK no matter what. But other COM methods may actually return error values, in which case we should be able to respond to them. How, you ask? Good question, but easily solved, since Delphi contains the OleCheck "wrapper" function that can be used to "embed" any COM Method that returns an HResult.
By using OleCheck, we can change the last listing as follows in order to completely follow the COM guidelines:

  procedure TForm1.Button1Click(Sender: TObject);
  var
    Euro: IEuro;
  begin
    Euro := CreateCOMObject(Class_Euro) as IEuro;
    OleCheck(Euro.About)
  end;
Of course, we're still not really doing anything yet - we've only covered the framework. Feel free to experiment with the Type Library Editor and add some methods to convert euros to guilders and vice versa. You'll encounter some issues that will be covered next time (you can also just wait one more month for the follow-up story, of course).

Next Time
This article covered the first basics of COM Objects and Interfaces. Next times, we'll continue with COM Objects, adding more methods, and continue with some other (special) COM Objects such as OLE Automation Objects and (in later articles) Transaction Objects using MTS and COM+.
Everything from now on will use, extend and boil down to the information presented in this first article (and the next), so make sure you understand what we've covered so far before you continue. Don't hesitate to contact me for questions or comments, and see you next time!


This webpage © 2002-2010 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.