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

Automation

Up to now, you have seen that you can use COM to let an executable file and a library share objects. Most of the time, however, users want applications that can talk to each other. One of the approaches you can use for this goal is Automation (previously called OLE Automation). After presenting a couple of examples that use custom interfaces based on type libraries, I'll cover the development of Word and Excel Automation controllers, showing how to transfer database information to those applications.

Note 

The current Microsoft documentation uses the term Automation instead of OLE Automation, and uses the terms active document and compound document instead of OLE document. This book tends to use the new terminology, although the older "OLE" terminology is still indicated as it was probably more clear.

In Windows, applications don't exist in separate worlds; users often want them to interact. The Clipboard and DDE offer a simple way for applications to interact, as users can copy and paste data between applications. However, more and more programs offer an Automation interface to let other programs drive them. Beyond the obvious advantage of programmed automation compared to manual user operations, these interfaces are completely language-neutral, so you can use Delphi, C++, Visual Basic, or a macro language to drive an Automation server regardless of the programming language used to write it. Automation is straightforward to implement in Delphi, thanks to the extensive work by the compiler and VCL to shield developers from its intricacies. To support Automation, Delphi provides a wizard and a powerful type-library editor, and it supports dual interfaces. When you use an in-process DLL, the client application can use the server and call its methods directly, because they are in the same address space. When you use Automation, the situation is more complex. The client (called the controller) and the server are generally two separate applications running in different address spaces. For this reason, the system must dispatch the method calls using a complex parameter passing mechanism called marshaling (something I won't cover in detail).

Technically, supporting Automation in COM implies implementing the IDispatch interface, declared in Delphi in the System unit as:

type
  IDispatch = interface(IUnknown)
    ['{00020400-0000-0000-C000-000000000046}']
    function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
    function GetTypeInfo(Index, LocaleID: Integer; 
      out TypeInfo): HResult; stdcall;
    function GetIDsOfNames(const IID: TGUID; Names: Pointer;
      NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
    function Invoke(DispID: Integer; const IID: TGUID; 
      LocaleID: Integer; Flags: Word; var Params; 
      VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
  end;

The first two methods return type information; the last two are used to invoke an actual method of the Automation server. Actually the invocation is performed only by the last method, Invoke, while GetIDsOfNames is used to determine the dispatch id (required by Invoke) from the method name. When you create an Automation server in Delphi all you have to do is define a type library and implement its interface. Delphi provides everything else through compiler magic and VCL code (actually a portion of the VCL originally called DAX framework).

The role of IDispatch becomes more obvious when you consider that there are three ways a controller can call the methods exposed by an Automation server:

  • It can ask for the execution of a method, passing its name in a string, in a way similar to the dynamic call to a DLL. This is what Delphi does when you use a variant (see the following note) to call the Automation server. This technique is easy to use, but it is rather slow and provides little compiler type-checking. It implies a call to GetIDsOfNames followed by one to Invoke.

  • It can import the definition of a Delphi dispatch interface (dispinterface) for the object on the server and call its methods in a more direct way (dispatching a number, that is calling Invoke directly as the DispId of each method is known at compile time). This technique is based on interfaces and allows the compiler to check the types of the parameters and produces faster code, but it requires a little more effort from the programmer (namely the use of a type library). Also, you end up binding your controller application to a specific version of the server.

  • It can call the interface directly, through the interface vtable, i.e. treating it as a normal COM object. This works in most cases as most Automation servers interfaces provide dual interfaces (that is support both IDispatch and a plain COM interface).

In the following examples, you'll use these techniques and compare them a little further.

Note 

You can use a variant to store a reference to an Automation object. In the Delphi language a variant is a type-variant data type, that is a variable that can assume different data types as its value. Variant data types include the basic ones (such as Integers, strings, characters, and Boolean values) but also the IDispatch interface type. Variants are type-checked at run time; this is why the compiler can compile the code even if it doesn't know about the methods of the Automation server, as we'll see later on.

Dispatching an Automation Call

The most important difference between the two approaches is that the second generally requires a type library, one of the foundations of COM. A type library is basically a collection of type information, which is often found also in a COM object (with not dispatch support). This collection generally describes all the elements (objects, interfaces, and other type information) made available by a generic COM server oran Automation server. The key difference between a type library and other descriptions of these elements (such as C or Pascal code) is that a type library is language-independent. The type elements are defined by COM as a subset of the standard elements of programming languages, and any development tool can use them. Why do you need this information?

I've mentioned earlier that if you invoke a method of an Automation object using a variant, the Delphi compiler doesn't need to know about this method at compile time. A small code fragment using Word's old Automation interface, registered as Word.Basic, illustrates how simple it is for a programmer:

var
  VarW: Variant;
begin
  VarW := CreateOleObject ('Word.Basic');
  VarW.FileNew;
  VarW.Insert ('Mastering Delphi by Marco Cantù');
Note 

As you'll see later, recent versions of Word still register the Word.Basic interface, which corresponds to the internal WordBasic macro language, but they also register the new interface Word.Application, which corresponds to the VBA macro language. Delphi provides components that simplify the connection with Microsoft Office applications, introduced later on in this chapter.

These three lines of code start Word (unless it was already running), create a new document, and add a few words to it. You can see the effect of this application in Figure 12.3.

Click To expand
Figure 12.3:  The Word document is being created and composed by the WordTest Delphi application.

Unfortunately, the Delphi compiler has no way to check whether the methods exist. Doing all the type checks at run time is risky, because if you make even a minor spelling error in a function name, you get no warning about your error until you run the program and reach that line of code. For example, if you type VarW.Isnert, the compiler will not complain about the misspelling, but at run time, you'll get an error. Because it doesn't recognize the name, Word assumes the method does not exist.

Although the IDispatch interface supports the approach you've just seen, it is also possible—and safer—for a server to export the description of its interfaces and objects using a type library. This type library can then be converted by a specific tool (such as Delphi) into definitions written in the language you want to use to write your client or controller program (such as the Delphi language). This makes it possible for a compiler to check whether the code is correct and for you to use Code Completion and Code Parameters in the Delphi editor.

Once the compiler has done its checks, it can use either of two different techniques to send the request to the server. It can use a plain VTable (that is, an entry in an interface type declaration), or it can use a dispinterface (dispatch interface). You used an interface type declaration earlier in this chapter, so it should be familiar. A dispinterface is basically a way to map each entry in an interface to a number. Calls to the server can then be dispatched by number calling IDipatch.Invoke only, without the extra step of calling IDispatch.GetIDsOfNames. You can consider this an intermediate technique, in between dispatching by function name and using a direct call in the VTable.

Note 

The term dispinterface is a keyword. A dispinterface is automatically generated by the type-library editor for every interface. Along with dispinterface, Delphi uses other related keywords: dispid indicates the number to associate with each element; readonly and writeonly are optional specifiers for properties.

The term used to describe this ability to connect to a server in two different ways, using a more dynamic or a more static approach, is dual interfaces. When writing a COM controller, you can choose to access the methods of a server two ways: you can use late binding and the mechanism provided by the dispinterface, or you can use early binding and the mechanism based on the VTables, the interface types.

It is important to keep in mind that (along with other considerations) different techniques result in faster or slower execution. Looking up a function by name (and doing the type checking at run time) is the slowest approach, using a dispinterface is much faster, and using the direct VTable call is the fastest approach. You'll do this kind of test in the TLibCli example, later in this chapter.


 
Previous Section Next Section


 


 


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