Art  Delphi  Automation  History  Home  Politics  Email me Default Colours  Printable Colours

Introduction to automation with Delphi

Contents


Sources of information

This site is intended to be a guide to automating specific programs. For general automation and COM theory, see:

Web sites

Binh Ly's tutorials

Books

Charlie Calvert: Delphi 4 Unleashed
Marco Cantu: Mastering Delphi 4
Marco Cantu et al.: Delphi Developer's Handbook (with COM chapter by John Lam)
Steve Teixeira and Xavier Pacheco's Delphi 4 Developer's Guide


Back to top

Getting started

Step 1: Import the type library

If you're using Delphi 5, and you want to automate Microsoft Office 97, you can skip this step - Borland have done it for you already. Otherwise, in Delphi, click on the Project|Import Type Library menu item. Look down the list for the program you want to automate. If it isn't there, click Add, and then look for the type library file for the program. Type library files usually have *.tlb extensions, but Microsoft Office uses *.olb instead: Msword8.olb, for example. Choose this, and the program's name will appear in the list of type libraries. Make sure the one you want is selected.

If you're using D4 or earlier, click OK to import it. Delphi 5 users should make sure that the check box saying 'Generate component wrapper' is ticked, specify the page you want the component to appear on in your palette, then click 'Install' and tell Delphi which package to install the components in (unless you've decided not to use components, of course - see D5's server components for some pros and cons). Clicking on 'Create Unit' instead gives you a ***_TLB.pas file in your Imports folder, but doesn't install the component in your component palette.

Step 2: Find the important bits

If you're using D5, you'll probably find you have an extra component (or 6) now installed in your component palette (on the Servers page by default). But if not, you can still use the methodss that worked with D4 - so read on.

Delphi's editor will now display a file with the imported information - e.g., Word_TLB, Excel_TLB. These files are initially rather daunting, but Delphi's Code Explorer and Code Insight features make using them much simpler. Even without those features, however, it's pretty easy to find the call you need to make to start up the program - just look for the classes (not interfaces) declared in the ***_TLB.pas file. The routine you want will be a class function, and it will probably contain the words Create and Application somewhere - here are Photoshop's and Word's, for example:

CoPhotoshopApplication = class 
  class function CoPhotoshopApplication.Create: IPhotoshopApplication;
CoApplication_ = class
  class function Create: _Application;

Step 3: Call the program

If you're using D5, and you've got a component for the application, drop it on your form, set Autoconnect to True, and you're away - the application will run when your program starts. If you want control over when it starts, leave Autoconnect as False, but call the component's Connect method when you want to start. E.g.:

  ExcelApplication1.Connect;

If you're using an earlier version of Delphi, or you haven't got an application component, just declare a variable of the right return type for the class function, and you can start the application very simply, like this:

var
  PS: IPhotoShopApplication;
begin  
  try
    PS := CoPhotoshopApplication.Create;
  except
    MessageDlg('Cannot start Photoshop, mtError, [mbOK], 0);
  end; 

However, there are a few other things to notice. The first is that you may need to tell the app to show itself:

  Word.Visible := True;

The second is that if the app is already running, this function will create another instance of it. If you just want the existing instance to be used, you need to use GetActiveObject, like this:

uses ComObj, ActiveX, Word_TLB;
var
  AppWasRunning: boolean;
  Unknown: IUnknown;
begin 
  AppWasRunning := False; 
  Result := GetActiveObject(CLASS_Application_, nil, Unknown); 
  if (Result = MK_E_UNAVAILABLE) then 
    Word := CoApplication_.Create 
  else begin 
    { make sure no other error occurred during GetActiveObject } 
    OleCheck(Result); 
    OleCheck(Unknown.QueryInterface(_Application, Word));
    AppWasRunning := True; 
  end;
  Word.Visible := True; 
  ...
end; 

The CLASS_Application_ parameter is a TGUID declared in Word_TLB. For Photoshop, this parameter would be CLASS_PhotoshopApplication. So you can see that these TGUIDs are not hard to spot! But the Code Explorer makes it incredibly easy.

The AppWasRunning variable can be useful when you want to know whether you should close down the program when you've finished with it.

Back to top

OK, it's started. What do I do next?

Getting the program started is the easy bit: working out how to make it do anything useful is harder. The principal resources you need are the ***_TLB.pas file that you created by importing the type library, and the help files of the applications you're automating (plus any SDKs). The latter will tell you about the objects you can use, and their methods; the former will help you translate them from VBA into Delphi.

One thing to bear in mind, when you read the application's helpfiles, is that they aren't just written in Basic rather than Pascal - they're also written for late binding using variants rather than early binding using interfaces. So the examples won't just use parentheses () where Pascal demands square brackets [] - they also won't tell you that a property returns only an IDispatch-type interface. For example, the ActiveSheet property in Excel returns an IDispatch, instead of the _Worksheet interface its name might suggest, so in early binding you have to cast it to get the interface you want:
  var
    Wks: _Worksheet;
  ...
    Wks := Excel.ActiveSheet as _Worksheet;

The Excel help files won't tell you that, because with variant-based automation every object is accessed through the IDispatch interface, so it makes no difference. But a quick look at the Excel_TLB.pas file shows you the return type of the ActiveSheet property.

Delphi 4+'s Code Insight is another helpful tool. Type a full stop after a variable, and wait a moment - you'll see all the properties and methods it has that you can use. Type a ( after a method name and wait a moment - you'll see a list of all the parameters it takes, and their types. This feature really comes into its own in automation, and I wouldn't want to program without it.

Finally, when you're really stuck, you can turn to the newsgroups. The borland.public.delphi.oleautomation newsgroup is a great Delphi-specific resource, but don't forget the newsgroups for the application you're automating, too.

Back to top

Optimizing your code

Automation is slow. To speed things up, you need to reduce the amount of toing-and-froing between the automation client and server.

First and foremost, use early binding rather than late binding. When you use variants, every function call results in calls to IDispatch GetIDsOfNames and IDispatch.Invoke - a big waste of time when you can call the function directly with early binding.

Secondly, try to work with the automation server's macro language to reduce client-to-server traffic. Visual Basic for Applications may not be lightning fast, but it's likely to be faster than sending information from the server to the client and back again.

Thirdly, use local variables and with clauses wherever possible, to reduce the number of calls to the server. In other words, don't do this:

  (Excel.ActiveSheet as _Worksheet).Cells.Item[1,1].Name := 'FirstCell';
  (Excel.ActiveSheet as _Worksheet).Cells.Item[1,1].Value := 'This is the first cell'; 

Instead, do this:

  with (Excel.ActiveSheet as _Worksheet).Cells.Item[1,1] do
  begin
    Name := 'FirstCell';
    Value := 'This is the first cell'; 
  end


or this:

  var
    R: Range;
  ...
    R := (Excel.ActiveSheet as _Worksheet).Cells.Item[1,1];
    R.Name := 'FirstCell';
    R.Value := 'This is the first cell';   

Fourthly, make sure you use any of the application's own methods for speeding things up. For example, Microsoft Office applications have a ScreenUpdating property which you can use to stop screen updates temporarily while your code executes, which can make a big difference to the speed.

Back to top

Parameters

The easiest way to find the parameters a method needs is to use Delphi's Code Insight, but looking in the ***_TLB.pas file will also show you them. However, this only reveals the names and types of the parameters - to know what they mean, you may need to read the help files for the application.


EmptyParam

Often the names of function parameters are enough to tell you what they are, or at least to show you that you aren't interested in them: you'll probably find that you don't want to specify anything for many of the parameters. This is where Delphi 4's EmptyParam comes in:

  Word.Documents.Open(FileName,
       EmptyParam,EmptyParam,EmptyParam,EmptyParam,
       EmptyParam,EmptyParam,EmptyParam,EmptyParam,
       EmptyParam);

If you use Delphi 3, you'll need to create your own EmptyParam, like this:

implementation

{$IFDEF VER100} 
var
  EmptyParam: OleVariant;
{$ENDIF} 

...

initialization
{$IFDEF VER100} 
  TVarData(EmptyParam).VType := varError;
  TVarData(EmptyParam).VError := DISP_E_PARAMNOTFOUND;
{$ENDIF} 

LCID

Sometimes you'll find that Delphi demands a parameter that the application's help files don't mention at all. This is likely to be an LCID, or locale identifier. (You don't need to include this parameter in late binding, but then, late binding prevents proper localization of your programs.)

It's easy to find the right value for this parameter: simply use the LOCALE_USER_DEFAULT constant.

Back to top