Anda di halaman 1dari 39

September 2002, Volume 8 Number 9

Extending COM Components

Cover Art By: Arthur A. Dugoni Jr.

ON THE COVER
4

Dynamic Delphi

Changing the Immutable Malcolm Matthews


Your COM components were perfect when you created them, performing
their jobs as designed. Now things have changed and the components
need updating, but their interfaces must of course remain backwards
compatible. Malcolm Matthews demonstrates three techniques for
breaking this seeming impasse.

27

OP Tech

Optimizing StringReplace: Part II Peter Morris


Peter Morris creates a far more capable routine to replace the
native StringReplace function introduced in Delphi 4. Named
BMStringReplace, its based on the Boyer-Moore string-searching
algorithm (covered last month), is orders of magnitude more
efficient, and can be localized.

FEATURES

REVIEWS

11

29

PocketStudio 1.1

33

DBISAM 3.08

36

ExpressQuantumTreeList Suite

Greater Delphi

XML Transformations: Part II Cary Jensen, Ph.D.


Last month, Cary Jensen provided a guided tour of the XML Mapper utility
that ships with Delphi 6 Enterprise Edition. This month, he completes the
process by using the XML transformation files to transform XML to and from
the special XML data packet format used by ClientDataSet components.

16

On Language

Versatile Variants Bill Todd


Bill Todd demonstrates how to use variants as powerful tools for storing
all types of data and passing them as parameters. You can store
single values or arrays of values, and the array elements can be of different types. Theyre also extremely useful for passing all types of data
across a COM connection.

22

Delphi at Work

XML Building Blocks: Part I Keith Wood


Keith Wood presents generic building-block components that can be
used for many XML-related purposes, such as generating a DOM from
an existing XML document as text, writing out an altered DOM as text,
and applying an XSL transformation to a DOM.
1 September 2002 Delphi Informant Magazine

Product Review by Robert Leahey


Product Review by Robert Leahey
Product Review by Robert Leahey

DEPARTMENTS
2

Delphi Tools

39

File | New by Alan C. Moore, Ph.D.

Delphi
T O O L S

New Products
and Solutions

Book Picks
The Book of SAX
W. Scott Means
and Michael A. Bodie
No Starch Press

AdventurerLand Entertainment Introduces Lab Technology Construction Kit


AdventurerLand Entertainment
created a specification that defines
an abstract game editor architecture known as Lab Technology.
The Lab Technology Construction
Kit is coded from the ground up
in highly-optimized Delphi code
to implement the Lab Technology
specification. It delivers the main
tools that any game editor needs,
all in easy-to-use, professional,
royalty-free VCL components
for Delphi and C++Builder. The
construction kit includes its own
project file format and a COMbased transactional server that
allows multiple clients to connect
to local or remote lab projects.
With the Lab Project Designer,
you can visually create and design
Lab Technology projects; define
your game classes, properties, and
events; and quickly internationalize or localize your projects for
new languages and cultures.
The Lab Server provides local
and remote transactional access
to lab projects, and any number
of clients can connect to the same

project file. The VCL components


let you easily build your game
editor from the ground. Built-in
state-of-the-art IDE tools cover
everything from an object inspector to advanced data handling
explorers to manage classes, object
instances, users, to-do items,
bugs, resource packages, files,
and disk volumes. The Builder
component automatically creates
and distributes all the output your

game interpreter needs. With the


File Manager tool, you can easily
instruct the Builder component
where to drop each file.
Online documentation and help
are available, along with source
code examples.
AdventurerLand Entertainment
Price: Contact AdventurerLand Entertainment
Contact: (54) 11+4831-1832
Web site: www.adventurerland.com

Aha-soft Announces IconXP 1.10


ISBN: 1-886411-77-8
Cover Price: US$29.95
(293 pages)
www.nostarch.com

XML Family of Specifications


Kenneth B. Sall
Addison-Wesley

ISBN: 0-201-70359-9
Cover Price: US$49.99
(1,122 pages, CD-ROM)
www.aw.com/cseng

Aha-softs latest icon editor,


IconXP 1.10, is specially
designed for the easy creation of
slick and trendy Windows XP
icons. In compliance with the
new icon format introduced by
Windows XP (32-bit color with
an 8-bit alpha channel), IconXP
enables you to create icons that
contain a smooth edge over any
background.
Other visual effects that are
popular and prominent with
Windows XP icons today
include drop shadow, gradient,
light source from a certain angle,

soft and slightly rounded edges,


and a 3-D outlook. Version 1.10
offers several new functions to
make better use of opacity and
transparency, including the
ability to capture transparency
when moving or copying images.
You can use RGB lock mode,
which allows you to modify a
pixels transparency while keeping its color. Similarly, IconXPs
transparency lock mode lets you
alter a pixels color while freezing
its transparency.
IconXP is fundamentally an
icon editor. All of its functions

are designed specially to create


icons, unlike other graphics
applications that arent suited
for icon creation. In IconXP,
youll find a full-featured editor
for creating attractive icon files.
You can start from scratch, or
simply import multiple standard image files (BMP, JPEG,
etc.) and convert them into an
icon format.
Aha-soft
Price: US$19.95
Contact: contact@aha-soft.com
Web Site: www.aha-soft.com

Dreamscape Announces PDFReport


PDFReport from Dreamscape
is a developer component for
Delphi, C++Builder, Visual Basic,
and Visual C++. It enables your
applications to directly generate
a PDF file from scratch, using a
small and simple subset of functions, such as WriteText or SavePDF. These reports can be viewed
on every platform, including
Linux, and inside a Web browser.
The footprint is minimal. Its
80 KB for the DCU version

2 September 2002 Delphi Informant Magazine

for Delphi (no DLL), and


300 KB for the OCX version
(only one DLL). The required
memory is about the size of the
file being generated (i.e. 30 KB
PDF file equals 30 KB RAM).
After use, all of the memory is
deallocated.
The generated PDF files pass all
tests and validations from Adobe
software and are almost the same
as if they were generated by
Acrobat Distiller. You can also

create a PDF file on the fly inside


a CGI and pass it directly to the
browser. A Web user can open
the PDF file inside the navigator, e.g. a tax form filled with
the data from a Web form, or an
invoice ready to be printed after
an order entry.
Consist Srl.
Price: Around US$200
Contact: sales@consist.it
Web Site: www.consist.it/prodo.htm

Delphi
T O O L S

New Products
and Solutions

Book Picks
XML Programming:
Web Applications and
Web Services with JSP and ASP
Alexander Nakhimovsky
and Tom Myers
Apress

ISBN: 1-59059-003-1
Cover Price: US$44.95
(555 pages)
www.apress.com

Inside XSLT
Steven Holzner
New Riders

ISBN: 0-7357-1136-4
Cover Price: US$49.99
(616 pages)
www.newriders.com

AtoZed Software Ships IntraWeb 5


AtoZed Software released version 5 of IntraWeb. Apart from
the many new features, IntraWeb
5 takes advantage of advanced
browser technology, providing
the end user with more flexibility
and power while developing Web
applications.
IntraWeb is now supported by
not only Delphi, but Kylix 2 and
C++Builder 5, with support for 6
coming shortly. IntraWeb allows
the developer to choose between
a wide range of development

platforms, without restricting them to certain editions of


platforms. In fact, IntraWeb is
supported by both Professional
and Enterprise versions of Delphi
and C++Builder.
AtoZed Software now provides
different licensing schemes for
IntraWeb. Apart from the preexisting Developer and Enterprise
editions, theres also a Personal edition, as well as a Lite edition thats
completely free for non-profit
development.

Version 5 includes a WYSIWYG


editor, browser preview, Alignment
and Anchor properties, drag-anddrop file upload, an improved
rendering engine, client-side data
components, Mozilla support,
multiple browser support in the
debug window, and a debug form
component.
AtoZed Software
Price: Starting at US$99
Contact: info@atozedsoftware.com
Web Site: www.atozedsoftware.com

Cytron Research Releases Visual VCL Pack 1


Visual VCL Pack 1 by Cytron
Research provides over 30 VCL
components and classes, 17 of
which are design-time controls,
for Delphi and C++Builder.
Support for the easy display of
XML data, as well as part-lists
data, e.g. financial transactions,
is provided by a multi-column
tree view component. Rich Edit
2.0 control functionality, which
supports OLE linking and embedding, multi-level undo, and many
other features, is provided by an
enhanced Rich Edit control.

The pack also includes two


development kits, one to design
custom combo boxes, and the
other for the creation of custom
dictionaries and multilingual spell
checkers. The combo box kit
includes examples such as a tree
view combo box and calculator
combo box.
Visual VCL Pack 1 comes with
utility controls, including a binary
streaming control, a stringlist
streaming control, and a file storage control. The utility controls
have a 128-bit encryption control.

A simple-to-use button array


is built in. This component
uses custom button shapes,
supplied at design time or allocated dynamically at run time,
which are rendered in 3-D by
the control. This allows developers to provide an enriched
experience to the users of their
applications.
Cytron Research Ltd.
Price: US$33
Contact: info@cytronresearch.co.uk
Web Site: www.cytronresearch.co.uk

Actional Accelerates Web Services with SOAPswitch


Actional, a provider of cross-platform Web Services infrastructure
solutions, announced their flagship Web Services product, SOAPswitch. Functioning as a software
infrastructure layer, SOAPswitch is
a Web Services gateway that allows
a company to successfully publish,
secure, and manage Web Services.
The solution instantly transforms
existing software assets into Web
Services.
SOAPswitch incorporates native
understanding of a broad set of
applications, providing built-in
connectivity to existing software
assets. With this technology,
SOAPswitch can jumpstart a
companys entrance into Web Services by quickly exposing its existing back-end applications as Web
Services. SOAPswitch works by
residing within an organizations
IT infrastructure, and allows internal staff, customers, or suppliers
access to existing packaged, legacy,
and custom-built applications
via Web Services standards, while
providing a centralized security

3 September 2002 Delphi Informant Magazine

and management model.


This product includes support
for packaged applications such
as SAP, PeopleSoft, and Siebel, as
well as custom applications based
on Java, CORBA, COM, or MQ
series. SOAPswitch is a standardsbased Web Services framework

that complies with the latest Web


Services standards, including
SOAP, WSDL, and UDDI.
Actional Corp.
Price: US$10,000 per CPU
Contact: sales@actional.com
Web Site: www.actional.com

Dynamic Delphi
COM Components / Interfaces / Delphi 3-6

By Malcolm Matthews

Changing the Immutable


Reusing COM Components

f you develop COM components, sooner or later you will need to update them.
Either the business requirements will change, or youll overlook something in the
original design. However, interfaces are supposed to be immutable, so how do you
change them?
This article looks at several techniques for
extending the functionality of Delphi COM
components, examining some of the benefits and
drawbacks of each. Although this article shows
each step of the process, it assumes youre familiar
with developing COM components with Delphi,
so many of the basic details will be skipped.

In the same way that all COM development


is split into two parts (the interface and the
implementation) the techniques for enhancing
COM components also fall into these two areas.
The techniques well look at for dealing with the
interface are:
changing the existing interface,
creating a separate new interface, and
using inheritance to create a new extended
interface.
For the implementation we will look at:
CoClass inheritance,
multiple interfaces on one CoClass,
containment, and
aggregation.
These last two techniques can be employed to
reuse any existing COM component by using it
as the basis of a new component.

Example Code

Figure 1: Delphis Type Library editor.


4 September 2002 Delphi Informant Magazine

Since were dealing with enhancing existing components, we need a starting point. For the purposes
of this article we will consider a simple calculator
component, COMReuseDemo.Calculator, with an
ICalculator interface implemented by TCalculator,
as follows:

Dynamic Delphi

Figure 2: Implementation options.

However, the simplest way


to add the new method is to
modify the existing interface
in the Type Library editor (see
Figure 1), refresh the implementation, add the new functionality
to the modified implementation unit, and re-compile the
component.

Providing were the only user


of the component, this rule
can be violated. See Cheat\
COMReuseDemo.dpr for an

example of this. Having changed

the interface, all of the clients

should be recompiled. If the only

change to the interface is to add

methods or properties, they may


work without recompilation, but

its best to be safe. This requirement to recompile the clients



is one reason for not changing

the interface. Obviously, as the
number of client applications
increases, so does the overhead of
changing the interface. But if we
only have one client, as is often the case with application components,
this is the easiest way of upgrading the component.

ICalculator = interface(IDispatch)
function Add(Value: Double): Double; safecall;
function Subtract(Value: Double): Double; safecall;
function Multiply(Value: Double): Double; safecall;
function Divide(Value: Double): Double; safecall;
property Accumulator: Double
read Get_Accumulator write Set_Accumulator;
end;
TCalculator = class(TAutoObject, ICalculator)
...
end;

If the component is in use by other people, then the Immutability Rule


should not be broken. They may have used the interface as the parent
for an inherited interface, in which case any change to the interface,
even adding methods, will cause a problem. There is also the problem of
notifying all of the users that the component has changed; remember, in
the world of COM not all users will even know they are using it!

Modifying the Interface Design Correctly

The code for this is in Original\COMReuseDemo.dpr in the accompanying zip file (available for download; see the end of this article for
details). All of the example COM components have a test bed application in the same directory. Warning! These components are for demonstration purposes and any resemblance to real code is purely accidental.

The correct way to implement this change is to create a new


interface. This then poses a question: What methods should be
included in the interface? Should it just include the new methods,
or should it include the old and new methods? The answer to this
depends on a number of factors.

The use of this calculator is quite simple. Set the value of the accumulator, then perform a function on it. For example:

Is the new functionality logically separate from the existing functionality? Interface design is beyond the scope of this article, but the
general rule is that small, highly focused interfaces are more re-useable and therefore better. So if the new methods support separate
functionality from the original interface, they should be part of a new
interface. For our example, we might create IScienceCalc:

var
fltAnswer : Double;
begin
Calculator.Accumulator := 5.0;
fltAnswer := Calculator.Add(1.5);
end;

The Quick and Dirty Method


Lets assume that this calculator works well in our application, but
now we want to add some trigonometric functions. For example:
function Sin: Double; safecall;

The first thing to do is to go back to the definition of a COM


interface: Interfaces are immutable contracts. So, since ICalculator is
already in use, it cannot be changed.
5 September 2002 Delphi Informant Magazine

IScienceCalc = interface(IDispatch)
function Sin: Double; safecall;
end;

As is often the case in the real world, the arguments for this division
arent always clear cut. The alternative is to create a new interface that
extends the original by inheriting from it. By convention, this will
have the same name with an additional, identifying number. So we
now have ICalculator2:
ICalculator2 = interface(ICalculator)
function Sin: Double; safecall;
end;

Dynamic Delphi
The main difference is that with ICalculator2
we can access all of the original methods, and
the new ones, from a single interface reference:
var
MyCalc: ICalculator2;
begin
// Default interface for this is now
// ICalculator2.
MyCalc := CoCalcualtor.Create;
MyCalc.Accumulator := 5.0;
fltAnswer := MyCalc.Add(1.5);
fltAnswer := MyCalc.Sin;
end;

Whereas, with IScienceCalc, we need to


create a second reference on the new interface to access the new functionality. Note:
We must create the new COM component,
not the old one.
For example:

Figure 3: Adding an interface using the Type Library editor.

var
MyCalc : ICalculator;
MySciCalc : IScienceCalc;
begin
MyCalc := CoScienceCalc.Create;
MyCalc.Accumulator := 5.0;
fltAnswer := MyCalc.Add(1.5);
MySciCalc := MyCalc as IScienceCalc;
fltAnswer := MySciCalc.Sin;
end;

While this requires more effort in the client application, the potential
benefits of well-designed, generic, small interfaces are often worth it.
One thing to bear in mind at this stage is that scripting clients can
only use the default interface of a component. So if scripts need to
be able to access the new and old functionality, youll need to create
a new interface that inherits from the old one.
These two influences are mutually exclusive, so the decision must
be made early on as to which way to go. One approach is to have
two sets of interfaces: one enormous Swiss Army knife of an interface for scripting clients, and a set of small re-useable interfaces
for other early-binding clients.

Implementation
With the new interface design decided, we now need to consider how
to implement it. Both the new interface and the inherited interface
can be implemented within the existing CoClass for ICalculator
or the CoClass can be subclassed and the new functionality implemented there. These options are shown in Figure 2.
From a simple class design perspective, separating the old and new
functionality by subclassing the CoClass seems attractive. For the
new interface, IScienceCalc, this is the case.
However, for the inherited interface, ICalculator2, creating a
subclass for the new functionality may cause problems during later
development. This is best explained with an example. Assume you
implement ICalculator2 with a subclass: TCalculator2. Then, another
change is required, this time to the parameters being passed for the
Add method. For example:
6 September 2002 Delphi Informant Magazine

function Add(Value1, Value2: Double): Double; safecall;

Now you need to implement two versions of the same method. A


common technique to handle this without duplicating code is to
move the existing code to the new version of the method, update
it to handle the new parameter, then call the new method from
the original one passing in a default value for the new parameter.
Implementing the new interfaces in subclasses precludes this as an
option, because of the structure of the class hierarchy. My preference is to implement an inherited interface in the original class,
and to implement a new interface in a subclass.

Implementing Inherited Interfaces


The first step to implement an inherited interface like ICalculator2
in the same CoClass is to open the type library, create a new interface
(see Figure 3) and name it (ICalculator2). Secondly, set the parent
interface to ICalculator. Now add the new methods to the new
interface.
Next, implement ICalculator2 in the CoClass by selecting the
CoClass in the Type Library editor, clicking the Implements tab,
then right-clicking and selecting ICalculator2 from the Insert Interface
dialog box (see Figure 4). This will add ICalculator2 to the list of
interfaces implemented by Calculator.
Select ICalculator2
from this list, rightclick and mark it as
the default interface,
do the same with
ICalculator but mark
it as not the default.
All that remains is to
refresh the implementation (see Figure 5).
Marking ICalculator2
as the default
interface will allow
scripting clients
Figure 4: The Insert Interface dialog box.
to access the new

Dynamic Delphi
interface instead of the old one. The final
step is to add the code (and of course
update the test application so you can
prove it works).
Remember, all of the functionality of the
COM component is available through the
new interface, so theres no need to change
interfaces within the client. Its possible to set
the component up so that the ICalculator2
methods all call different methods in the
underlying component from their ICalculator
equivalents by using method resolution clauses,
but that topic is beyond this article (see Calc2\
COMReuseDemo.dpr for a copy of this code).

Implementing New Interfaces


The easiest way to implement a new interface
like IScienceCalc in a sub-classed object is to
create a new Automation object (assuming that
is what the original component was based on) Figure 5: Refreshing the implementation.
and set the CoClass name appropriately, in
this example ScienceCalc. The new methods can
then be added to the new interface that the Type Library editor creates
automatically. The important step is to modify the declaration of the
class that implements this new interface so it inherits from the class
that implements the original interface. In this example you need to
change the declaration of TScienceCalc as follows:

TScienceCalc = class(TCalculator, IScienceCalc)


protected
function Sin: Double; safecall;
end;

TScienceCalc will now implement the IScienceCalc methods,


and through inheritance, ICalculator as well (see SciCalc\
COMReuseDemo.dpr for a copy of this code). Notice that
the client program now needs to create an instance of the new
ScienceCalc component instead of the old Calculator one.

Containment and Aggregation


So far weve considered extending the component using Delphibased techniques. Theres another pattern for implementing a
new interface such as IScienceCalc. In this case the new interface
is implemented in a completely new COM component, and
delegation is used to surface the old functionality from the original
component. This technique doesnt require any changes to the
original component, so were far less likely to break it in the process.
In fact, we can create the new functionality in a separate DLL thats
distributed separately. No existing DLLs are modified, guaranteeing
backward compatibility. Another benefit is the avoidance of
functional dependencies in the object hierarchy; which can lead to
significant maintenance problems in deep hierarchies.
Since no changes will be made to the original component, we dont
need the source code for it. So we can enhance any COM component,
not just ones we have written (license agreements allowing).
Again, we have two choices as to how to go about this process.
They differ in the amount of coding effort and flexibility. Simple
containment with manual delegation is the easiest to set up and
the most flexible, but requires more code. Aggregation requires
little code, but is less flexible.
7 September 2002 Delphi Informant Magazine

Figure 6: The containment solution.

Containment
With containment, the new component implements both the old
and the new interfaces, but any calls made on the old interface
just result in a call delegating the method to the original component. This is shown in Figure 6.
The new component needs to create an instance of the original
component when its created. It then just passes the calls on. This
does require us to write code for each of the original methods,
but they only need to be one-line methods; they simply call the
original method.
The power of this approach is that we have complete control, so
we can modify the behavior of some or all of the methods. This is
just like modifying the behavior of a virtual method in a Delphi
object. So we can do pre- and post-processing, or replace the original
behavior completely.

Implementing Containment
To implement this, create a new component as before, and create
the new interface. Then you use the Type Library editor to mark the
component as implementing the old interface in the same way that
ICalculator2 was added to Calculator earlier. Upon refreshing the
implementation youll find all the methods for both interfaces. A

Dynamic Delphi
TScienceCalc = class(TAutoObject, ICalculator, IScienceCalc)
private
InternalCalculator : ICalculator;
protected
{ ICalculator }
function Add(Value: Double): Double; safecall;
function Divide(Value: Double): Double; safecall;
function Get_Accumulator: Double; safecall;
function Multiply(Value: Double): Double; safecall;
function Subtract(Value: Double): Double; safecall;
procedure Set_Accumulator(Value: Double); safecall;
{ IScienceCalc }
function Sin: Double; safecall;
public
procedure Initialize; override;
end;

Figure 8: The aggregation solution.

Figure 7: Class definition for the ScienceCalc component.

tip to help with future maintenance is to edit the class definition to


group the methods by interface and to comment them as such.
The next step is to make sure that the Calculator component is
created when the ScienceCalc component is created. As always with
COM components, do this by overriding the Initialize method rather
than the constructor. The created component needs to be stored
somewhere; in this case, use a private variable. The class definition is
shown in Figure 7.
Now edit the Initialize method to create the Calculator component:
procedure TScienceCalc.Initialize;
begin
inherited;
InternalCalculator := CoCalculator.Create;
end;

All of the ICalculator methods can now simply delegate the call to the
contained component thus:
function TScienceCalc.Add(Value: Double): Double;
begin
Result := InternalCalculator.Add(Value);
end;

You can add any additional code you require, either before or
after this call, or even replace it completely, depending on your
requirements. Once all the other methods have been set up, you
can compile, register, and test the component again. Remember
that as with the new interface implemented by the subclassed
object, the clients will need to change the interface thats being
used to switch from ICalculator to IScienceCalc functionality, and
its the new ScienceCalc component that must be created. (It is
also possible to use containment to implement an inherited interface if a single point of access is required.)

Aggregation
As you can see, containment is relatively straightforward to set up, but
does require code for each method in the original interface. If no additional functionality has been added, this is a bit of a waste of our time.
Aggregation is a specialized case of containment. In it, the original
component is created inside the new one as with containment. But
this time, the interface that is exposed to the outside world is the
interface of the original component (as shown in Figure 8). Not too
surprisingly, this does require a bit of work to set up correctly.
8 September 2002 Delphi Informant Magazine

Aggregation has rules that must be obeyed by both outer and inner
components for it to work correctly. The goal of all of those rules is
that the client application must not be able to tell that an aggregated
component is handling one of the interfaces. Externally the component should look like one that does all of the work itself.
For this to be the case, the IUnknown methods (QueryInterface,
AddRef, and Release) need special handling. Note that these are the
IUnknown methods, not the IUnknown interface itself. Every interface inherits ultimately from IUnknown, so every interface includes
the three IUnknown methods. (Each interface is a separate vTable,
so its possible for methods in one interface to call different code to
similar methods in another interface.)
There are three main rules that govern implementing the IUnknown
QueryInterface method on a COM object:
1) Components must have identity.
2) The set of interfaces on a component instance must be static.
3) It must be possible to query successfully for any interface on a
component from any other interface.
If the inner components interface is used in the normal way, all three
of these rules will be broken. In our example, asking for the IUnknown
interface from ICalculator will return the inner IUnknown, while from
the IScienceCalc interface we will get the ScienceCalc IUnknown interface. These two are supposed to be the same (identity).
Likewise, if we try to get from IScienceCalc to ICalculator it will work
(since TScienceCalc knows it supports both interfaces), but if we try to go
the other way it will not work (TCalculator doesnt know anything about
IScienceCalc). There are also problems with reference counting.
The solution to this is for the IUnknown methods for the aggregated interface belonging to the inner component (ICalculator) to
delegate to the IUnknown interface of the outer component. For
this to be possible, the inner component must know when it is
being aggregated and how to delegate.
Note: The inner components IUnknown interface itself must still
work in the usual way for the outer component (ScienceCalc) to work
with it. So TCalculator must deal with the IUnknown methods in one
of two different ways, depending on which interface theyre called on.

Implementing Aggregation
Aggregation is a fairly advanced topic to get to grips with. Fortunately, TComObject has all the necessary functionality built in.

Dynamic Delphi

Figure 9: The Environment Options dialog box.

All you need to do is create the inner component so that it knows


its aggregated, and tell Delphi that an aggregated component is
handling the interface. All the other technicalities become useful
facts with which to amaze co-workers.
To implement this for the ScienceCalc component, create a new
ScienceCalc Automation object again, and set up the IScienceCalc
interface. Before you add the ICalculator interface you need to
modify the Type Library editor configuration. Otherwise, when
the ICalculator interface is added to the list of implemented interfaces, Delphi will generate code that you dont want.
To do this, go to Tools | Environment Options, and select the Type
Library tab. Make sure Display updates before refreshing is checked
(see Figure 9). This will allow you to control what code Delphi
creates. Also make sure that the Type Library editor isnt open
when you change these settings; it doesnt like it.
Now go back to the Type Library editor. If you are aggregating a
component in a separate DLL, youll need to add a reference to
the type library on the Uses tab in the Type Library editor. (Select
Show All Type Libraries from the Uses tab context menu and check
the required library.) For this example, where everything is in the
same ActiveX library, this is not required. You just need to add the
ICalculator interface to the list of implemented interfaces for the
ScienceCalc CoClass.
When you click refresh, because of the Display updates before
refreshing option, Delphi will prompt you with a list of changes
that are about to be made to the underlying pas unit. You will
need to uncheck all of the ICalculator methods to prevent Delphi
generating any code for them (see Figure 10).
The first step, as with the containment example, is to create the
inner component in the Initialize method. You probably usually
use the helper classes that Delphi provides in the _TLB.pas unit
when creating COM components. For example:
MyCalc := CoCalculator.Create;

9 September 2002 Delphi Informant Magazine

Figure 10: Implementation file update wizard.


procedure TScienceCalc.Initialize;
var
MyUnknown : IUnknown;
begin
inherited;
MyUnknown := Self;
OleCheck(CoCreateInstance(CLASS_Calculator, MyUnknown,
CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER,
IUnknown, FInnerUnk));
end;

Figure 11: Calling CoCreateInstance directly.

This wont work in this case, since the inner component needs to be
informed that its being aggregated. To do this you will need to use the
COM subsystem API call CoCreateInstance directly (CoCalculator.Create
calls this ultimately anyway), as shown in Figure 11.
CLASS_Calculator is the CLSID of the COM component to be
aggregated (a Type Library editor-generated constant in this case).
MyUnknown is the IUnknown interface of the controlling or outer
component (it must be IUnknown). CLSCTX_INPROC_SERVER or
CLSCTX_LOCAL_SERVER just requests either a local (out of process), or in-process component. IUnknown is the interface you want to
get back (this also must be IUnknown in this case; its the only interface
the outer component can use to control the inner one). FInnerUnk is a
private member variable to hold the returned ICalculator interface.
CoCreateInstance is wrapped in an OLECheck call since it returns
an HResult. This saves having to check the return code. OLECheck
will raise an exception if an error is returned.
Now for the code to link everything up, Delphi does this using
the implements keyword and a property. You need to create a
property of type ICalculator and mark this as implementing the
ICalculator interface. Any calls on this interface will then be

Dynamic Delphi
routed directly to the interface returned by the property accessor.
So you need to create the property and provide a Get method to
return the ICalculator interface of the internal Calculator component. Its easiest to see all of this in the completed unit, shown in
Listing One.
And thats all there is to it! Dont be tempted to optimize the
repeated conversion of FInnerUnk to ICalculator by storing a reference to the Calculator components ICalculator interface within
the TScienceCalc class. This is because the reference counting for
that interface actually operates on the ScienceCalc component,
so it will be self-referencing and will never be destroyed. The
Calculators IUnknown interface operates normally, so the reference counting operates on the Calculator component.

Conclusion
As with many design issues, theres no single correct answer to the
question: How do I enhance my COM component when interfaces are supposed to be immutable? However, we have looked at
the main options and seen that none of them (with the possible
exception of aggregation) is difficult to implement. You can even
cheat and change the existing interface. Just remember that the
whole point of immutability is for backwards compatibility. The
important thing is to assess your own circumstances, and try to
decide which option will be best.
The six example projects (with test beds) referenced in this article are
available on the Delphi Informant Magazine Complete Works CD
located in INFORM\2002\SEP\DI200209MM.

Malcolm is principal consultant with Dunstan Thomas Limited and has many years
experience designing and implementing client/server and distributed database
applications using both Microsoft and Borland technologies. As well as being a
Borland Certified Delphi Consultant, he is a Microsoft Certified Software Developer
(MCSD). Malcolm has consulted for clients throughout Europe and is currently
focused on developing Web-based systems using Delphi, MTS, and XML. He spends
his time away from computers being jumped on by the kids. You can reach him at
malcolmm@fish.co.uk.

Begin Listing One The ScienceCalcCOM Unit


unit ScienceCalcCOM;
interface
uses
ComObj, ActiveX, COMReuseDemo_TLB, StdVcl;
type
TScienceCalc = class(TAutoObject, IScienceCalc,
ICalculator)
private
FInnerUnk : IUnknown; // Holds aggregated COM object.
protected
// Property accessor.
function GetCalculator: ICalculator;
{ ICalculator }
property Calculator: ICalculator read GetCalculator
implements ICalculator;
{ IScienceCalc }
function Sin: Double; safecall;
public
procedure Initialize; override;
destructor Destroy; override;
end;
implementation
uses ComServ;
destructor TScienceCalc.Destroy;
begin
{ Explicitly free the Calculator for clarity. }
FInnerUnk := nil;
inherited;
end;
function TScienceCalc.GetCalculator: ICalculator;
begin
{ Return ICalculator interface to handle
ICalculator call. }
Result := FInnerUnk as ICalculator;
end;
procedure TScienceCalc.Initialize;
var
MyUnknown : IUnknown;
begin
inherited;
{ Create the aggregated Calculator component. }
MyUnknown := Self; OleCheck(CoCreateInstance(CLASS_
Calculator, MyUnknown,
CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IUnknown,
FInnerUnk));
end;
function TScienceCalc.Sin: Double;
var
Accumulator : Double;
begin
// Use the accumulator value from the aggregated object.
Accumulator := (FInnerUnk as ICalculator).Accumulator;
Result := System.Sin(Accumulator);
end;
initialization
TAutoObjectFactory.Create(ComServer, TScienceCalc,
Class_ScienceCalc, ciMultiInstance, tmApartment);
end.

End Listing One

10 September 2002 Delphi Informant Magazine

Greater Delphi
XML Transformations / DataSnap / Delphi 6 Enterprise

By Cary Jensen, Ph.D.

XML Transformations
Part II: To DataSnap and Back

elphi 6 Enterprise includes a set of components and a supporting utility that permit you
to easily convert between almost any type of XML file and the special XML file format
required to populate a ClientDataSet. The components are referred to as the XML transformation components, and the utility is the XML Mapper.
Together, these tools permit you to use the standard TDataSet interface to work with XML data,
regardless of its source. Working with XML in this
manner is much easier than using a programmatic
interface, such as the Document Object Model
(DOM), or Simple API for XML (SAX).
Last month, I demonstrated how to use XML
Mapper to create the transformation files required
by the XML transform components. In this second
and final article in this series, well complete the
process by using the XML transformation files
created last month to transform XML to and
from the special XML data packet format used by
ClientDataSet components.

Understanding the XML


Transform Components
Delphi 6 Enterprise includes three XML
transformation components that make
use of the transformation files created
by the XML Mapper. These are the
XMLTransform, XMLTransformProvider, and
XMLTransformClient components, and they
are located on the Data Access page of the
Component palette.
The most important of these three components
is the XMLTransform. Its responsible for
converting XML strings from one form to
another. The other two components encapsulate
one or more XMLTransform instances to
perform their more specialized tasks. For the
XMLTransformProvider, this task is to treat an
XML file as the data source for a ClientDataSet,
while the XMLTransformClient is used to create
an XML file from the data packet obtained from
a DataSnap server.
The use of the XMLTransform component
is demonstrated in the XMLDemo project,
whose main form is shown in Figure 1. This
project, along with its supporting XML and
transformation files, can be found in the XML
Transformation Demo folder in the code
download (see end of article for details).

Figure 1: The XMLDemo projects main form at design time.


11 September 2002 Delphi Informant Magazine

When this project is run, the OnCreate event


handler of the main form loads the two XML
files into the two Memo components on the
left side of the form. The top XML file is a
simple, well-formed XML file, and the bottom
one is an XML data packet that can be loaded

Greater Delphi
ClientDataSet. This event handler also assigns the fully-qualified
file name for the two transformation files created in last months
article to the XMLTransformFile property of the two XMLTransform
components that appear on the main form. Figure 2 shows this
complete OnCreate event handler.
As you can see in Figure 2, after extracting the name of the
application directory, Memo1 and Memo2 are loaded with the
contents of the xmldata.xml and demo.xml files, respectively.
Next, the Text properties of Memo1 and Memo2 are formatted
using the FormatXMLData method, which is declared in the
XMLDoc unit of Delphi 6 Enterprise (the same unit in which the
TXMLDocument class is declared). FormatXMLData adds carriage
returns and indentation to an XML string,
making it easier to read (the XML data packet
created by a ClientDataSet is not formatted for
readability).

procedure TForm1.FormCreate(Sender: TObject);


var
AppDir: string;
begin
AppDir := ExtractFilePath(Application.ExeName);
Memo1.Lines.LoadFromFile(AppDir + 'xmldata.xml');
Memo2.Lines.LoadFromFile(AppDir + 'demo.xml');
Memo1.Text := FormatXMLData(Memo1.Text);
Memo2.Text := FormatXMLData(Memo2.Text);
XMLTransform1.TransformationFile := AppDir + 'ToDp.xtr';
XMLTransform2.TransformationFile := AppDir + 'ToXml.xtr';
end;

Figure 2: The main forms OnCreate event handler.

The last two lines of this OnCreate event handler


assign the names of the two transformation
files created last month. ToDp.xtr contains the
transformation to convert the xmldata.xml
file into an XML data packet required by
ClientDataSet, and ToXml.xtr contains the
transformation to convert a ClientDataSets data
packet into an XML file with the format used by
xmldata.xml. Figure 3 shows how the main form
looks when this project is first run.
The actual transformation is performed by
calling the TransformXML method of an
XMLTransform component. The following is the
syntax of this method:
function TransformXML(const SourceXml: string;
const ATransformationFile: string = ''): string;

Figure 3: The main form when its first run.

TransformXML can take one or two parameters.


The first is a string containing the XML to
transform. The second refers to an .xtr file that
describes the transformation. If the second
parameter is omitted, the XMLTransform
component uses the transformation referred to
by its TransformationFile property, which, as you
can see in Figure 2, was assigned from the main
forms OnCreate event handler.
In the XML Transformation Demo project the
transformation is performed by clicking the
buttons that appear in the panels on the righthand side of the main form. For example, the
following is the OnClick event handler associated
with Button1:
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo3.Text := FormatXMLData(
XMLTransform1.TransformXML(Memo1.Text));
end;

Figure 4: The main form after both transformations have been executed.

As you can see, FormatXMLData is again


used to make the transformed XML more readable. Figure 4
shows how the main form looks once both transformations have
been executed. Instead of defining the XML to transform as a
12 September 2002 Delphi Informant Magazine

string, as was done with TransformXML1 in this example, the


SourceXML, SourceDocument, or SourceFile properties of the
XMLTransform component could have been set to an XML string,

Greater Delphi
1) Begin by creating a new project in Delphi 6
Enterprise.
2) Place a DBNavigator on the main form,
aligned to alTop, and a DBGrid, aligned to
alClient. These components are found on
the Data Controls page of the Component
palette.
3) Next, place a DataSource, a ClientDataSet,
and an XMLTransformProvider from
the Data Access page of the Component
palette. Set the DataSource properties of the
DBNavigator and DBGrid to DataSouce1.
4) Now set the DataSet property of
DataSouce1 to ClientDataSet1, and the
ProviderName property of ClientDataSet1 to
XMLTransformProvider1.
5) Using the Object Inspector, expand the
Figure 5: Setting the ClientDataSets Active property to True at design time.
TransformRead subcomponent property
of the XMLTransformProvider and
set its TransformationFile property to
the file ToDp.xtr located in the XML
Transformation Demo project directory.
Likewise, expand the TransformWrite
property and set its TransformationFile
property to ToXml.xtr. Finally, set the
XMLFile property to xmldata.xml.
6) Now set the ClientDataSets Active property
to True to see it load the file xmldata.xml
through the ToDp.xtr transformation. Your
main form should now look like that shown
in Figure 5.
7) If you were to run this project now, you
would be able to use the ClientDataSet to
edit the data it loaded from the XML file.
However, to save changes you make to data
Figure 6: Posting a new record.
using a ClientDataSet that loads its data
using a provider, you must explicitly call ApplyUpdates.
an IDOMDocument implementing object, or an XML file name,
Therefore, add a MainMenu to this project. Double-click
respectively. (An IDOMDocument implementing object can be
the MainMenu component to open the Menu Designer,
obtained by reading the DOMDocument property of an active
and add a single menu item. Set the Caption property of
TXMLDocument instance.)
this menu item to Apply Updates. Now add the following
OnClick event handler for this menu item:
When these properties are used, you read the Data property of
procedure TForm1.ApplyUpdates1Click(Sender: TObject);
the XMLTransform instead of calling the TransformXML method.
begin
Also, instead of setting the TransformationFile property to the
ClientDataSet1.ApplyUpdates(-1);
.xtr file containing the transformation, you can alternatively set
end;
the TransformDocument property to point to an IDOMDocument
implementing object that references the transformation.
8) Run the project. Add and post a new record to the
ClientDataSet, like that shown in Figure 6. Before closing
Using XMLTransformProvider
the project, select Apply Updates from the displayed menu
The XMLTransform component is simple and easy to use.
to save your changes. (If you fail to post the new record,
However, most developers dont use the XMLTransform component
calling ApplyUpdates wont do anything.) After closing the
directly. Instead, they typically use the XMLTransformProvider
project, display the xmldata.xml file using your favorite
component, which encapsulates two XMLTransform instances. The
XML viewer (Internet Explorer version 5 and later includes
XMLTransformProvider is used with a ClientDataSet to load data from,
a nice XML viewer). As you can see in Figure 7, the new
and save changes back to, an external XML file. Importantly, because of
record appears in the XML file.
the transformations, the external XML doesnt need to match the special
XML format required by the ClientDataSet.
Transforming XML from a DataSnap Server
DataSnap, formerly known as MIDAS, is Borlands multi-tier
Using the XMLTransformProvider is easy to demonstrate using
framework for building distributed database applications. In a
the xmldata.xml file and the two transformations, ToDp.xtr and
typical DataSnap application, end users work with a DataSnap
ToXml.xtr, employed in the preceding example. The following
client application, which communicates to a DataSnap server
steps demonstrate how you do this:
using one of a variety of network protocols, including DCOM,
13 September 2002 Delphi Informant Magazine

Greater Delphi
TCP/IP, and HTTP. The client application
obtains data from a DataSetProvider on a
remote data module of the DataSnap server,
loading that data into a ClientDataSet.
Using XML transformations, you can
convert the data obtained from a DataSnap
server into an XML format. There are two
ways of doing this. The first mechanism is
to load the data obtained from a DataSnap
server into a ClientDataSet as is normally
done in a DataSnap client, and then use an
XMLTransform component, converting the
XML obtained through the ClientDataSets
XMLData property using an appropriate
transformation file.
The second mechanism makes use of the
Figure 7: The new record in the XML file.
XMLTransformClient component. This
component takes the place of a ClientDataSet.
Specifically, you use an XMLTransformationClient on the
DataSnap client to connect to a DataSetProvider on a DataSnap
server, converting the retrieved data into XML using a
transformation file.
I prefer the first mechanism. Specifically, in most DataSnap
client applications youll probably want to permit your end
users to review or edit the data before saving it to an XML
file. In those cases, the ClientDataSet provides you with these
capabilities without any additional work. By comparison, an
XMLTransformClient is limited to generating XML through a
transformation file based on the data obtained from a DataSnap
server. If you want to view or edit this data, you have to resort to
using an XMLDocument component, a third-party XML parser,
or transform the data for use in a ClientDataSet. On the other
hand, if all your application needs to do is to obtain data from a
DataSnap server and store it in XML, the XMLTransformClient
provides you with all you need.

Figure 8: The XMLTransformClient demo application.


procedure TForm1.FormCreate(Sender: TObject);
var
DataDir: string;
begin
DataDir := ExtractFilePath(Application.ExeName);
XMLTransformClient1.TransformGetData.
TransformationFile := DataDir + 'ToXml.xtr';
Memo1.Text := FormatXMLData(
XMLTransformClient1.GetDataAsXml(''));
end;

In addition to the XMLTransformClients RemoteServer property,


which behaves the same as the RemoteServer property of the
ClientDataSet, the XMLTransformClient component encapsulates
three XMLTransform components. These are accessed through the
TransformApplyUpdates, TransformGetData, and TransformSetParams
subcomponent properties. Use TransformApplyUpdates to convert
XML data that includes changes to a valid ClientDataSet data
packet before sending the changes back to the DataSnap server.
Use TransformGetData to convert the data packet obtained from
the DataSnap server to an arbitrary XML format. Finally, use
TransformSetParams to convert an XML file containing query
parameters to an XML data packet. This data packet is stored in the
XMLTransformClient through a call to its SetParams method. Once
stored, the parameters are sent to the DataSetProvider the next time
the XMLTransformClients GetDataAsXml method is invoked.

Figure 9: The main forms OnCreate event handler.

The use of the XMLTransformClient component is demonstrated


using the files located in the XMLTransformClient directory of the
code download. Because this is a DataSnap example, there are two
projects you must compile. The first is a simple DataSnap server,
located in the server directory under XMLTransformClient. This
DataSnap server includes a single DataSetProvider on its remote
data module. This provider returns the entire customer table in the
employee.gdb InterBase database that ships with Delphi. (Obviously,

All code associated with the retrieval and transformation of


the data obtained from the DataSnap server can be found
in the OnCreate event handler of the main form, shown in
Figure 9. As you can see from this code sample, after setting
the TransformationFile property of the TransformGetData
subcomponent property to an appropriate transformation file,
the GetDataAsXml method is invoked, and the formatted,
transformed XML is loaded into the main forms Memo

14 September 2002 Delphi Informant Magazine

InterBase must be installed and running to use this server.) Load and
run this server once before attempting to run the client application.
The XMLTransformClient component is found in the project located
in the client directory under XMLTransformClient. The main form
for this project is shown in Figure 8.

Greater Delphi

Figure 10: The demo application at run time.

component. Figure 10 shows how this form will look once the
project is running.

Summary
The XML Mapper utility creates transformation files that
can be used with Delphis XML transform components to
convert between XML files and the special XML format used
by the ClientDataSet. The XMLTransform component is
the primary transformation component, permitting you to
easily convert between XML formats. The other two XML
transform components, the XMLTransformProvider and the
XMLTransformClient, encapsulate one or more XMLTransform
instances. Of these two, the XMLTransformProvider is the most
useful, permitting you to effortlessly view and edit XML files
using the TDataSet interface.
The files referenced in this article are available on the Delphi
Informant Magazine Complete Works CD located in INFORM\
2002\SEP\DI200209CJ.

Cary Jensen is president of Jensen Data Systems, Inc., a Texas-based training


and consulting company that won the 2002 Delphi Informant Magazine
Readers Choice award for Best Training. He is the author and presenter for
Delphi Developer Days (http://www.DelphiDeveloperDays.com), an information-packed Delphi seminar series that tours North America and Europe.
Cary is also an award-winning, best-selling co-author of 18 books, including
Building Kylix Applications (Osborne/McGraw-Hill, 2001), Oracle JDeveloper
(Oracle Press, 1999), JBuilder Essentials (Osborne/McGraw-Hill, 1998), and
Delphi In Depth (Osborne/McGraw-Hill, 1996). For information about onsite
training and consulting, contact Cary at cjensen@jensendatasystems.com or
visit his Web site at http://www.JensenDataSystems.com.

15 September 2002 Delphi Informant Magazine

On Language
Variants / Object Pascal / Delphi 2-6

By Bill Todd

Versatile Variants
Dont Avoid This Useful Type

ave you ever needed to store a value in a variable, but you werent going to know the
type of the value until run time? Or have you ever wanted to pass the contents of a
binary file to a function in a COM server? Variants can do both of these things and more.
Variants are useful because a variant variable can
hold almost any type of data, and the data type
can change from moment to moment as your
application runs. A variant can hold any type of
data except structured data types and pointers.
Put another way, a variant cannot hold records,
sets, static arrays, files, classes, class references, or
pointers. Any other data type will work, including dynamic arrays, COM objects, and CORBA
objects. Like any other variable, a variant can hold
a single value. But variants also can hold arrays,
and the arrays can be dynamically created and
resized. They also can be asymmetrical. Variants are
particularly useful in database applications, because
they can store the null state.

This flexibility and power comes at a price, however.


Variants consume more memory than statically typed
variables. Plus, variants that hold strings have another
limitation. An ANSI string variable can be accessed
character-by-character, as though it were an array of
characters, but a variant that holds a string cannot.

Variants Are Slow, but Who Cares?


Variants are also much slower than statically typed
variables. Figure 1 shows the main form of the
Speed sample application after it ran on my 1GHz
Pentium III notebook. (See the end of the article
for details about downloading associated files.) The
times are in milliseconds.
Figure 2 shows the code for the Variant button.
The code for the other buttons is identical except
for the data type of the W, X, Y, and Z variables.
In this test, variants are 300 times slower than
integers. But note that the loop iterates 10 million
times, so each iteration using variants only takes
.0006 milliseconds. Variants may be slower, but the
difference is nothing to worry about unless you are
performing a very large number of calculations.

Automatic Type Conversion

Figure 1: Variant speed comparison test application.


16 September 2002 Delphi Informant Magazine

A more significant reason for not using variants is


that they make it easier to introduce bugs into your
program. Because Pascal is a strongly typed language,
assigning a value of the wrong type to a variable will
cause an error at compile time, so you can correct the
problem easily. Assigning a value of the wrong type
to a variant wont raise an error until you try to do
something with the value at run time.

On Language
procedure TfrmVarSpeed.btnVariantClick(Sender: TObject);
var
W, X, Y, Z: Variant;
I: Integer;
Start, Stop: DWORD;
begin
W := 2;
X := 2;
Y := 0;
Start := GetTickCount;
for I := 1 to 10000000 do
Y := Y + (W * X);
Z := Y;
Stop := GetTickCount;
lblVariant.Caption := IntToStr(Stop - Start);
end;

Figure 2: The speed-test code.

Figure 3:
The Convert sample
application.

procedure TForm1.btnConvertClick(Sender: TObject);


var
V: Variant;
I: Integer;
S: string;
begin
S := '123';
V := S;
// Convert string variant to integer.
I := V * 2;
// Convert integer to variant integer.
V := I;
// Convert variant integer to string.
editStrInt.Text := V;
// Convert variant integer to variant float.
V := V * 1.33;
// Convert variant float to string;
editFloatStr.Text := V;
end;

Figure 4: The OnClick event handler for the Convert button.

It can be difficult to find an instance in which you assigned a value


of the incorrect type to a variant. This is because variants perform
automatic type conversion, as shown in the OnClick event handler
of the button in the Convert sample application. Figure 3 shows the
main form, and Figure 4 shows the event handler.
Whenever possible, variants perform automatic type conversion to
allow an operation or expression to proceed. For example, in the
expression I := V * 2 in Figure 4, V is a variant that contains the
string 123. The expression works because the attempt to multiply
the string variant V by 2 makes the variant try to change its type
from string to integer. Because the string contains only numbers,
the conversion succeeds, and the result is that I is set to 246. The
same thing happens when V := I is executed to assign the integer
246 to the variant V, and then V is assigned to the Text property of a
TEdit control. Because the Text property requires a string, the variant
converts its value from integer to string.
17 September 2002 Delphi Informant Magazine

TVarData = packed record


VType: TVarType;
case Integer of
0: (Reserved1: Word;
case Integer of
0: (Reserved2, Reserved3: Word;
case Integer of
varSmallInt: (VSmallInt: SmallInt);
varInteger: (VInteger: Integer);
varSingle:
(VSingle: Single);
varDouble:
(VDouble: Double);
varCurrency: (VCurrency: Currency);
varDate:
(VDate: TDateTime);
varOleStr:
(VOleStr: PWideChar);
varDispatch: (VDispatch: Pointer);
varError:
(VError: LongWord);
varBoolean: (VBoolean: WordBool);
varUnknown: (VUnknown: Pointer);
varShortInt: (VShortInt: ShortInt);
varByte:
(VByte: Byte);
varWord:
(VWord: Word);
varLongWord: (VLongWord: LongWord);
varInt64:
(VInt64: Int64);
varString:
(VString: Pointer);
varAny:
(VAny: Pointer);
varArray:
(VArray: PVarArray);
varByRef:
(VPointer: Pointer);
);
1: (VLongs: array[0..2] of LongInt);
);
2: (VWords: array[0..6] of Word);
3: (VBytes: array[0..13] of Byte);
end;

Figure 5: The TVarData record from the System unit.

In addition to the automatic type conversion that variants


perform, you can change the type of a variant using the
VarAsType function. For example, if you have a variant named
V that contains the string 123, you can convert it to a variant
integer as follows:
V := VarAsType(V, varInteger);

If the type conversion cannot be performed, an exception is


raised.

How Variants Work


Figure 5 shows the TVarData record defined in the System unit.
If you need a review of Pascal records with variant parts (not to be
confused with the variant data type), see pages 5-22 through 5-24
in the Delphi 6 Object Pascal Language Guide.
TVarData begins with the VType field, which is actually of
type Word and contains the type of the variant. Figure 6 shows
constants representing types the variant can contain.
The variant type is stored in the low-order 12 bits (three bytes)
of the VType field. The varTypeMask constant has the low-order
12 bits set so you can perform a Boolean and operation with
VType to extract the type code, and ignore the varArray and
varByRef bits. Because determining the type of a variant
variable is a common operation, Delphi provides the VarType
function, which returns the VType field of the TVarData record.
If you need to know if the variant contains an integer, use
the following expression:
if VarType(V) and varTypeMask = varInteger then...

On Language
const
varEmpty = $0000;
varNull = $0001;
varSmallint = $0002;
varInteger = $0003;
varSingle = $0004;
varDouble = $0005;
varCurrency = $0006;
varDate = $0007;
varOleStr = $0008;
varDispatch = $0009;
varError = $000A;
varBoolean = $000B;
varVariant = $000C;
varUnknown = $000D;
varShortInt = $0010;
varByte = $0011;
varWord = $0012;
varLongWord = $0013;
varInt64 = $0014;
varStrArg = $0048;
varString = $0100;
varAny = $0101;
varTypeMask = $0FFF;
varArray = $2000;
varByRef = $4000;

Figure 6: The variant type constants.

TVarArray = packed record


DimCount: Word;
Flags: Word;
ElementSize: Integer;
LockCount: Integer;
Data: Pointer;
Bounds: TVarArrayBoundArray;
end;

Figure 7: The TVarArray record from the System unit.

procedure TForm1.btnRunClick(Sender: TObject);


var
V: Variant;
S: string;
E: Extended;
begin
with TblCust do begin
// Return the field value as a variant. This will
// fail if TaxRate is null.
E := FieldByName('TaxRate').Value;
E := FieldByName('TaxRate').AsVariant;
E := tblCust['TaxRate'];
// Convert a null to zero.
E := FieldByName('TaxRate').AsFloat;
// Return field value as a variant. This will not
// fail if TaxRate is null because V is a variant.
V := FieldByName('TaxRate').Value;
V := FieldByName('TaxRate').AsVariant;
V := tblCust['TaxRate'];
// Return the field as a variant. This will fail
// if Addr2 is null.
S := FieldByName('Addr2').Value;
S := FieldByName('Addr2').AsVariant;
S := tblCust['Addr2'];
// The following will convert a null
// to an empty string.
S := FieldByName('Addr2').AsString;
end;
end;

Figure 8: Using database data and variants.


18 September 2002 Delphi Informant Magazine

This will be true if the variant contains a single integer or an array of


integers. To see if the variant is an array, use the VarIsArray function:
if VarIsArray(V) then...

If the varArray and varByRef bits of VType arent set, the rest of the
TVarData record contains the variables value. If varArray is set,
the variant contains a pointer to a TVarArray record that contains
information about the array and a pointer to the array data. Figure 7
shows the TVarArray record.
If the varByRef bit is set, the variant contains a pointer to a value of
the type specified by VType. Although you arent likely to want to
do so, you can access the value of the variant directly by using the
TVarData type as follows:
if VarType(V) and varTypeMask = varInteger then
I := TVarData(V).VInteger;

This code confirms that the variant V contains an integer, casts V


to a TVarData record, and assigns the value of the VInteger field to
the variable I.

Why Use Variants?


The reason is obvious. If you need a variable that can hold different
types at run time, a variant is the tool to use. However, other characteristics make variants useful in other situations. Variants have two
states that arent values: empty and null. Variant variables are initialized to the empty state. You can tell if a variant is empty in code by
using the VarIsEmpty function:
if not VarIsEmpty(V) then VarClear(V);

This code checks to see if V is empty. If not, the code sets it to empty
using the VarClear function. The following code does exactly the
same thing using the Unassigned constant:
if not VarIsEmpty(V) then V := Unassigned;

Note that when working with variants, the terms empty and unassigned mean exactly the same thing. The VarIsClear function works
exactly like VarIsEmpty. If you use an empty variant in an expression,
Delphi will raise an exception.
Variants can also be null. This is particularly useful in database applications, because a variant variable can store the null state, or the value of a
field in a database table. This means you can write the following code:
if not VarIsNull(V) then V := Null;

The VarIsNull function returns True if the variant is null, and the
Null constant lets you set a variant to null. If you use a null variant in
an expression, the result will be null. To see how variants work with
database data, see the code in Figure 8. This is the code from the Run
buttons OnClick event handler in the Database sample application
that accompanies this article. The code uses the sample Customer
table in the DBDemos database.
The first three lines of code all retrieve the value from the TaxRate
field in the current record and assign the value to the variable E of
type Extended. What isnt obvious is that all three ways of accessing
the field value return the value as a variant. The problem with this is
that the field value could be null, and the Extended data type cannot

On Language
procedure CopyToArray(var Rec: Variant; DataSet: TDataSet);
var
I: Integer;
begin
// Create a variant array whose number of elements equals
// the number of fields in DataSet.
Rec := VarArrayCreate(
[0, DataSet.FieldCount-1], varVariant);
// Copy the values to the array.
for I := 0 to VarArrayHighBound(Rec, 1) do
Rec[I] := DataSet.Fields[I].Value;
end;
procedure CopyFromArray(var Rec: Variant;
DataSet: TDataSet);
var
I: Integer;
begin
// Copy the values from the array to the fields of DataSet.
for I := 0 to DataSet.FieldCount - 1 do
DataSet.Fields[I].Value := Rec[I];
end;

Figure 9: The CopyToArray and CopyFromArray methods.

procedure TfrmSpeed.btnVariantClick(Sender: TObject);


var
V: Variant;
I, J, Start, Stop: Integer;
P: PIntArray;
begin
// Set the array size.
V := VarArrayCreate([0, ArraySize], varInteger);
// Get the starting time.
Start := GetTickCount;
// Lock the array for fast access.
P := VarArrayLock(V);
// Process the array.
for J := 0 to LoopCount do
for I := 0 to ArraySize - 1 do
P^[I] := I * 4 div 2;
// Unlock the array.
VarArrayUnlock(V);
// Get the end time and display the result.
Stop := GetTickCount;
lblVariant.Caption := IntToStr(Stop - Start);
end;

Figure 10: The Variant buttons OnClick event handler.

store the null state. This means that if TaxRate is null, all three of
these statements will cause an EInvalidVariantTypeCast exception.
The next line uses the AsFloat property to return the field value
and assign it to the Extended variable E. This will not cause an
error if TaxRate is null because the AsFloat property converts the
null to zero. All of the numeric As... properties work this way. As
long as you dont need to know that the field was null and not
zero this is a good solution.
The next three lines of code are identical to the first three, except that
the variable V, on the left side of the assignment operator, is a variant.
Because variants can store the null state, all of these statements will run
without error even if TaxRate is null.

Figure 11: The


ArraySpeed tests
main form.

creates a four-element variant array of variants and assigns the values


1, 10, 100, and abc to the four elements.

The next three statements in Figure 8 demonstrate the same problem


with a string field. All three statements return the value of the Addr2
field as a variant. And all three will fail with an EInvalidVariantTypeCast
error, if Addr2 is null because string variables cannot represent the null
state. This is confusing because programming literature frequently refers
to an empty string as a null string. An empty string isnt the same as a
field in a database whose state is null. In a databases field, the null state
means there is no value. The value is unknown and undefined. The last
statement in Figure 8 always will work because the AsString property
converts the null state to an empty string.

If you need to copy the field values from a record in a table to another
record, a variant array makes it easy (see Figure 9). The CopyToArray
method takes a variant and a TDataSet as parameters. The first statement
calls the VarArrayCreate function to create a variant array of variants with
one element for each field in the DataSet. The for loop copies the values
from each field to its corresponding array element.

Unless you want to encounter exceptions at run time, you never should
access a field value in a database table as a variant and assign it to a statically typed variable. If you want to convert nulls to zeroes, or to a null
string as appropriate, use the As... properties to access the field value. If
you need to know if the field value was null, always use variant variables.

You also can create multi-dimensional variant arrays using


VarArrayCreate. The first parameter must contain integer pairs that
define the upper and lower bounds of each dimension. For example:

Using Variant Arrays

creates a two-dimensional array of integers in which the first element of


the first dimension is 1, and the last element of the first dimension is 10.
The first element of the second dimension is 23, and the last is 32. You
also can make each element of a variant array of variants be its own array.
For example:

Perhaps the most common use of variant arrays is as the second


parameter of the TDataSet.Locate method. If youre searching more
than one field, the second parameter of Locate must be a variant array
that contains the values for the field names in the first parameter. You
can create a small variant array and assign values to the array elements
in a single operation that uses the VarArrayOf function. For example:
V := VarArrayOf([1, 10, 100, 'abc']);

19 September 2002 Delphi Informant Magazine

The second procedure, CopyFromArray, takes the same two parameters,


a variant and a TDataSet descendant. Its for loop copies the values from
the array elements to the current record in DataSet.

V := VarArrayCreate([1, 10, 23, 32], varInteger);

V := VarArrayCreate([1, 2], varVariant);


V[1] := VarArrayCreate([1, 100], varVariant);
V[2] := VarArrayCreate([1, 20], varInteger);

On Language
type
TPersonRec = record
FirstName: string[20];
LastName: string[30];
Age: Integer;
end;
TDataArray = array[0..1000000] of Byte;
PDataArray = ^TDataArray;
procedure TfrmBinary.btnPassClick(Sender: TObject);
var
Person: TPersonRec;
V: Variant;
P: PDataArray;
begin
Person.FirstName := 'John';
Person.LastName := 'Doe';
Person.Age := 25;
// Create a variant array of bytes
// the same size as the Person record.
V := VarArrayCreate([0, SizeOf(Person) - 1], varByte);
// Lock the array.
P := VarArrayLock(V);
// Copy the record to the array.
Move(Person, P^, SizeOf(Person));
// Unlock the array;
VarArrayUnlock(V);
// Pass the data.
ShowPerson(V);
end;
procedure TfrmBinary.ShowPerson(V: Variant);
var
Person: TPersonRec;
P: PDataArray;
begin
// Lock the array and get a pointer to its data.
P := VarArrayLock(V);
// Copy the data from the array to the Person record.
Move(P^, Person, SizeOf(Person));
// Unlock the array.
VarArrayUnlock(V);
// Display the fields of the Person record.
ShowMessage(Person.FirstName + ' ' + Person.LastName +
' - Age: ' + IntToStr(Person.Age));
end;

Figure 12: Passing binary data in a variant array of bytes.

creates a one-dimensional array of variants with two elements.


The first element is an array of variants, and the second is an array
of integers.

Making Variant Arrays Scream


The VarArrayLock function and the VarArrayUnlock procedure can make
accessing a variant array of a simple type lightning fast. Figure 10 shows
the code from the OnClick event handler of the ArraySpeed sample
applications Variant button. To use VarArrayLock and VarArrayUnlock,
you need to declare a pointer to an array of the base type used in the variant array. This code shows the type declaration from the implementation
section of the main forms unit:
type
TIntArray = array[0..1000000] of Integer;
PIntArray = ^TIntArray;

The first line declares the TIntArray type as an array of integers.


Because the type declaration doesnt allocate memory, make the
array size larger than the largest array you may ever need. The
second line declares PIntArray as a pointer to TIntArray.
20 September 2002 Delphi Informant Magazine

The first line in Figure 10 calls VarArrayCreate to create the array.


ArraySize is a constant declared in the units implementation section. The call to the Windows API function GetTickCount gets the
number of milliseconds since Windows started. The third line calls
VarArrayLock with the variant array, V, as the parameter. Locking the
array prevents changing the size of the array with the VarArrayRedim
procedure, and returns a pointer to the array data.
The inner for loop iterates through the array and assigns a value
to each element. The outer loop repeats the process enough times
to make the elapsed time large enough to measure. The call to
VarArrayUnlock releases the array so it can be resized. Figure 11
shows the main form with the times, in milliseconds, required to
execute the code in Figure 10 using a variant array, a dynamic array,
and a static array. The variant array accessed with the pointer that
VarArrayLock returns is the fastest of the three.
If you use VarArrayLock on a multi-dimensional variant array, note
that the subscripts of the pointer VarArrayLock returned are in the
opposite order of the subscripts of the variant.

Passing Parameters
Using a variant as a parameter in a function call lets you vary the
type and number of values passed to the function. You also can use a
variant array of bytes to pass any type of data as a parameter. This can
be particularly useful in COM applications, because it allows you to
pass data types that COM doesnt support, such as Pascal records.
Figure 12 shows an example of passing a Pascal record in a variant
array of bytes. The code starts with the type declarations from the
implementation section of the main form in the Binary sample
application. The first type, TPersonRec, is the Pascal record that
will be passed as a parameter in a method call; the second type
declaration, TDataArray, is an array of bytes; and the third type,
PDataArray, is a pointer to an array of type TDataArray.
The first method in Figure 12 is the OnClick event handler of the
Pass Data button. This method starts by assigning values to the
FirstName, LastName, and Age fields of the Person record declared
at the beginning of the method as type TPersonRec. Next, the
method calls VarArrayCreate to create a variant array of bytes
whose size is the size of Person.
Next, the variant array, V, is locked to get a pointer to the arrays
data. Then the Move procedure is called to copy the data from
Person to the array. Move takes three parameters. The first is the
source location in memory, the second is the destination location
in memory, and the third is the number of bytes to copy. In this
example, the source is the Person record variable, the destination
is the array to which P points, and the number of bytes to copy is
the size of Person.
The Move procedure is very handy and very fast, but its also very
dangerous. Make sure you have the correct source, destination, and
size, because Move performs no error checking, so its easy to overwrite
an area of memory you dont intend to change. Once the data has been
copied to the array, the array is unlocked, and the ShowPerson method
is called and passes the array, V, as its parameter.
The ShowPerson method also declares a Person variable of the
TPersonRec type and a pointer, P, of the PDataArray type. It
contains just four lines of code. The first locks the variant array
parameter, V; the second copies the data from the array to Person;

On Language
type
TFloatArray = array of Double;
...
procedure TfrmVarDyn.btnVarDynClick(Sender: TObject);
var
V: Variant;
F: TFloatArray;
begin
V := VarArrayCreate([0, 2], varDouble);
V[0] := 1.1;
V[1] := 2.2;
V[2] := 3.3;
DynArrayFromVariant(
Pointer(F), V, TypeInfo(TFloatArray));
ShowMessage(FloatToStr(F[2]));
end;

Figure 13: Converting a variant array to a dynamic array.

procedure TfrmVarDyn.btnVarStringsClick(Sender: TObject);


var
List: TStringList;
V: Variant;
begin
List := TStringList.Create;
try
List.Add('One');
List.Add('Two');
V := VarArrayFromStrings(List);
ShowMessage(V[1]);
finally
List.Free;
end;
end;

Figure 14: Creating a variant array from a TStringList class.

the third unlocks the variant array; and the fourth displays the
contents of the Person record.
Using this same technique, you can pass anything you can load into
memory as a parameter in a method call. This also works for a call
from a COM client to a method in a COM server. Examples include
the contents of a text or binary file, a JPEG or bitmap image, and
anything else that can be loaded into memory as a stream of bytes.
You can easily move data between dynamic arrays and variant
arrays using the DynArrayFromVariant (shown in Figure 13) and
DynArrayToVariant procedures. These procedures are very handy
if you need to pass the contents of a dynamic array across a COM

21 September 2002 Delphi Informant Magazine

connection. The code in Figure 13 is from the VarDyn sample


application. It shows the type declaration for a dynamic array of
the type Double. It also shows the code from the Var to Dyn buttons
OnClick event handler. The code starts by creating a three-element
variant array of doubles, and assigning a value to each element.
The call to DynArrayFromVariant takes three parameters. The first is
the dynamic array, the second is the variant, and the third is a pointer
to the run-time type information for the array type. The third
parameter is provided by the TypeInfo function, which takes a type as
its parameter (TFloatType, in this case). This is the reason you must
declare a type for the dynamic array. The first parameter, the dynamic
array, must be of the Pointer type, hence the type cast. The code
for the Dyn to Var buttons OnClick event handler is almost identical
except that it calls DynArrayToVariant.
Delphi also provides the VarArrayFromStrings procedure to create
a variant array of type WideString from a TStrings object. Many
VCL components have properties of type TStrings, and this method
makes it easy to convert the contents of one of these properties, or
of a TStringList to a variant array, so you can pass the data across
a COM connection. Figure 14 shows the code from the OnClick
event handler of the Var From Strings button in the VarDyn sample
application. To use VarArrayFromStrings, you must add the Provider
unit to your uses clause.

Conclusion
Variants are a powerful tool for storing all types of data and
passing them as parameters. You can store single values or arrays of
values, and the array elements can be of different types. Variants
are extremely useful for passing all types of data across a COM
connection, as well.
The demonstration projects referenced in this article are available
on the Delphi Informant Magazine Complete Works CD located in
INFORM\2002\SEP\DI200209BT.

Bill Todd is president of The Database Group, Inc., a database-consulting and


development firm based near Phoenix. He is the co-author of four database
programming books, the author of more than 90 articles, and a Contributing
Editor to Delphi Informant Magazine. Bill is a member of Team B, which provides
technical support on the Borland Internet newsgroups. Bill is a nationally known
trainer, has taught Delphi programming classes across the country and overseas,
and is a frequent speaker at Borland Developer Conferences in the United States
and Europe. Readers may reach him at bill@dbginc.com.

Delphi at Work
XML / Interfaces / Delphi 6

By Keith Wood

XML Building Blocks


Part I: Plug-and-Play XML Processing

ML documents provide a platform- and language-neutral way to transfer data


between applications. The hierarchical structure of XML documents lets you indicate relationships between pieces of data. Humans can read XML documents easily,
and applications can deal with them automatically.
The Document Object Model (DOM) defines a
standard way of loading, traversing, and updating an
XML document. Again, its defined in a languageneutral manner, so it can be implemented in different
environments and programming languages. By using
this common definition, its possible to manipulate
any XML document generically.
Delphi has access to various DOM implementations,
such as Microsofts and TurboPowers, but there are
subtle differences between them. Fortunately, Delphi
6 also provides a generic DOM definition that can
wrap any other DOM, allowing you to swap between
implementations with little or no change to your
application. The XMLDOM unit contains the Pascal
versions of the DOM interfaces, a system of registering and retrieving implementations of them, and
some useful routines for working with XML.
To build on top of this standard DOM base, you
can develop other components that work with
DOM documents to produce, alter, or process
them. If these components are defined appropriately, they can be plugged together simply, passing a
DOM from one to the other, and updating or reading it as it goes. These XML building blocks make
XML programming even easier.

CreateDocument

Producer

Figure 1: Plug-and-play XML processing.


22 September 2002 Delphi Informant Magazine

Makers and Takers


The two parts of plug-and-play XML processing
are components that generate new DOMs with
which to work, and components that take an existing DOM and make use of it. These two parts are
connected to create a processing chain that automatically makes a new document and acts upon it
(as shown in Figure 1).
The IXMLConsumer interface (see Figure 2)
defines a single method named DocumentReady
and encapsulates document handlers. (See the
end of this article for details about downloading
the code used in this article.) The method takes a
DOM as a parameter, allowing you to add nodes
to the DOM, filter nodes out of the DOM, process the DOM to produce a new version (including applying an XSL transformation), pass the
DOM back as text or a file, or display the DOM
contents in some manner.
Components that generate DOMs must implement the IXMLProducer interface (also shown in
Figure 2). It defines a property that refers to an
IXMLConsumer instance, to allow the producer to
send its newly created DOM on to another component. The CreateDocument method initiates the
production of the DOM and passes it along.

DocumentReady

Consumer

Delphi at Work
{ Definition for user of XML document in DOM format. }
IXMLConsumer = interface
['{917863FF-96D1-40F9-9868-926D9C299068}']
{ Pass DOM document to be used and start processing it. }
procedure DocumentReady(Document: IDOMDocument);
end;
{ Definition for generator of XML document in DOM format. }
IXMLProducer = interface
['{B53AF472-4B85-4F98-98B4-48501C81AE6A}']
{ Generate new XML document and pass it to consumer. }
procedure CreateDocument;
function GetConsumer: IXMLConsumer;
procedure SetConsumer(const Value: IXMLConsumer);
{ The consumer makes use of the new document. }
property Consumer: IXMLConsumer
read GetConsumer write SetConsumer;
end;

Figure 2: XML generator and processor interfaces.

By defining the producer and consumer requirements as interfaces, you can add them to any existing object, and you can create
new objects that implement them. In fact, by having the one
component provide both interfaces, you can string together a
series of XML handlers and have them each do their things before
passing the updated DOM on to the next step.
A building block can help you write such components.
TXMLBuildingBlock extends TComponent and implements both
interfaces (see Figure 3). Making it a component lets you add these
building blocks to applications visually, and set their properties
interactively. The properties are defined as protected, which enables
subclasses to reuse their abilities and make them available to the
user simply by promoting their visibility. Although this class isnt
declared as abstract, it should be extended before use.
As a document producer, this basic building block does nothing. That
leaves subclasses to override the CreateDocument method as necessary.
However, the DocumentReady method provides key functionality.
The method delegates the actual handling of the DOM document to
the new ProcessDocument method before passing the updated version
on to the consumer attached to this object. Subclasses are expected
to override the ProcessDocument method instead of DocumentReady
directly. The default processing of a document does nothing and just
returns the reference the document passed in.
The NotifyConsumer method is responsible for sending the DOM along
the chain. It ensures a consumer has been defined before passing the
modified document along. The standard IXMLConsumer processing calls
it automatically, but subclasses may invoke it directly to achieve the same
effect. All of this basic handling is shown in the code in Figure 4.
Other common processing requirements are included, which makes
them available to all subclasses. Building blocks often need to create new
DOMs, so the NewDocument method takes the name of the main element
and returns a DOM document with such an element attached. Many
building blocks add a new node to an existing document. To make this
easier to configure, the basic component adds the TagName property and
the OnTagCreate event. The latter should be invoked whenever a tag with
the given name is created, so the user has a chance to amend the resulting
element (perhaps by adding attributes). Use the DoOnTagCreate method to
trigger the event, as the NewDocument method does automatically.
In Delphi 6, a published property may be defined as an interface type,
with the Object Inspector locating appropriate implementers within
23 September 2002 Delphi Informant Magazine

{ Base class for developing consumers and producers. Its


producer part (CreateDocument) does nothing. Override
this for a new producer to actually generate the document
and call DocumentReady. Its consumer part (DocumentReady)
calls ProcessDocument to work on the document, then
passes it along to any attached consumer. ProcessDocument
just returns the original document without change.
Override this for a new consumer. }
TXMLBuildingBlock = class(TComponent, IXMLConsumer,
IXMLProducer)
private
FConsumer: IXMLConsumer;
FOnTagCreate: TXBBTagCreateEvent;
FTagName: string;
function GetConsumer: IXMLConsumer;
procedure SetConsumer(const Value: IXMLConsumer);
protected
property Consumer: IXMLConsumer
read GetConsumer write SetConsumer;
property TagName: string read FTagName write FTagName;
property OnTagCreate: TXBBTagCreateEvent
read FOnTagCreate write FOnTagCreate;
procedure DoOnTagCreate(Element: IDOMElement);
function NewDocument(const TagName: string):
IDOMDocument; virtual;
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
procedure NotifyConsumer(Document: IDOMDocument);
function ProcessDocument(const Document: IDOMDocument):
IDOMDocument; virtual;
public
procedure CreateDocument; virtual;
procedure DocumentReady(Document: IDOMDocument); virtual;
end;

Figure 3: A basic XML building block.

the form. To make this work properly, however, some additional code
is required within the body of the component. When setting such a
property, you need to call the ReferenceInterface method before saving
the new value to release the previous reference, and you need to call it
again afterward to notify others of the new value (see Figure 5). You
should also override the Notification method to handle deletions of
attached components. Within it, check whether the given component
is the implementer of your interface reference, and set that reference
back to nil if it is.

Inputs and Outputs


The most fundamental DOM producer under this scheme simply
reads an existing XML document and loads it straight into a DOM.
The generic DOM definition in Delphi 6 provides several avenues for
generating such a DOM: the Load, LoadFromStream, and LoadXML
methods of the IDOMPersist interface. So, the TXBBParser extends
TXMLBuildingBlock and declares properties for each approach (see
Figure 6). Load takes a file or URL reference from the XMLSource
property, LoadFromStream accepts a document from the XMLStream
property, and LoadXML works off the XMLText property. Only one of
these properties should have a value at any given time, although this is
not enforced.
The CreateDocument method is overridden to generate the new DOM
document. First, the method ensures that at least one of the required
properties is set. Then, it calls the IDOMPersist method corresponding to
the provided XML source. Once loaded, the inherited DocumentReady
is called, which invokes the default ProcessDocument and passes the
unchanged document on to the attached consumer. The Consumer
property is published to let the user set it at design time. Thus, calling
the CreateDocument method starts the chain of XML processing.

Delphi at Work
{ Do nothing; implemented as needed in subclasses. }
procedure TXMLBuildingBlock.CreateDocument;
begin
end;
{ Process document according to this class; then pass
it on to any consumer. }
procedure TXMLBuildingBlock.DocumentReady(
Document: IDOMDocument);
begin
Document := ProcessDocument(Document);
NotifyConsumer(Document);
end;
{ Pass completed document on to any register consumer. }
procedure TXMLBuildingBlock.NotifyConsumer(
Document: IDOMDocument);
begin
if Assigned(FConsumer) then
FConsumer.DocumentReady(Document);
end;
{ Do nothing; overridden in subclasses. }
function TXMLBuildingBlock.ProcessDocument(
const Document: IDOMDocument): IDOMDocument;
begin
Result := Document;
end;

Figure 4: Basic DOM handling.

{ Handle assigned interface so we get notifications


about it. }
procedure TXMLBuildingBlock.SetConsumer(
const Value: IXMLConsumer);
begin
ReferenceInterface(FConsumer, opRemove);
FConsumer := Value;
ReferenceInterface(FConsumer, opInsert);
end;
function TXMLBuildingBlock.GetConsumer: IXMLConsumer;
begin
Result := FConsumer;
end;
{ Tidy up if attached components are deleted. }
procedure TXMLBuildingBlock.Notification(
AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if Operation = opRemove then
{ Need to check based on interfaces. }
if Assigned(Consumer) and
AComponent.IsImplementorOf(Consumer) then
Consumer := nil;
end;

Figure 5: Interface property handling.

Similarly, the TXBBWriter class does the basic transferal of a DOM


document to some textual output (see Figure 7). As before, this
class extends TXMLBuildingBlock, but this time it overrides the
ProcessDocument method, because it is a consumer of DOMs. And,
as before, the IDOMPersist interface provides the necessary abilities
to output the document with its Save and SaveToStream methods.
The Stream and FileName properties let the user specify where the
text form of the DOM is written.
Within the ProcessDocument method, which the inherited DocumentReady
method calls automatically, check that at least one of the destination
24 September 2002 Delphi Informant Magazine

{ Create new DOM from existing XML document. Document can


come from existing file (XMLSource), from a stream
(XMLStream), or from memory (XMLText). }
TXBBParser = class(TXMLBuildingBlock)
private
FXMLSource: TFileName;
FXMLStream: TStream;
FXMLText: TStrings;
public
constructor Create(AOwner: TComponent); overload;
override;
constructor Create(AOwner: TComponent;
const XMLSource: TFileName); reintroduce; overload;
constructor Create(AOwner: TComponent;
const XMLStream: TStream); reintroduce; overload;
constructor Create(AOwner: TComponent;
const XMLText: TStrings); reintroduce; overload;
destructor Destroy; override;
procedure CreateDocument; override;
published
property Consumer;
property XMLSource: TFileName
read FXMLSource write FXMLSource;
property XMLStream: TStream
read FXMLStream write FXMLStream;
property XMLText: TStrings read FXMLText write FXMLText;
end;
{ Read document in from nominated source. }
procedure TXBBParser.CreateDocument;
var
Document: IDOMDocument;
begin
if (XMLSource = '') and
not Assigned(XMLStream) and
(XMLText.Text = '') then
raise EXBBException.Create(
'No source specified for XML document');
Document := NewDocument('dummy');
with Document as IDOMPersist do
if XMLSource <> '' then
Load(XMLSource)
else if Assigned(XMLStream) then
LoadFromStream(XMLStream)
else
LoadXML(XMLText.Text);
DocumentReady(Document);
end;

Figure 6: Loading a document into a DOM.

properties is defined before calling the corresponding save method. A


reference to the original document is returned as the result of the method,
allowing it to be passed along to any consumer defined for this class.
So, now you have a component that loads in a DOM, and you have
another that writes out the contents of a DOM. The point of the building-block approach is to be able to plug some sort of processing into this
chain easily and alter the document on its way through.

Transformations
One common task in working with XML documents is to apply an XSL
transformation to it. A transformation is another XML document containing tags that describe how to convert an incoming document into
another format. The output from the transformation could be straight
text, HTML, or even another XML document.
The TXBBTransform class encapsulates the transformation abilities. The
class extends TXMLBuildingBlock, promotes the Consumer property to
published status, and adds properties to indicate the source of the XSL
document (see Figure 8). The source properties parallel those found in

Delphi at Work
{ Write out DOM to given file or stream. Set one of
FileName or Stream before using this component. }
TXBBWriter = class(TXMLBuildingBlock)
private
FFileName: TFileName;
FStream: TStream;
protected
function ProcessDocument(const Document: IDOMDocument):
IDOMDocument; override;
public
constructor Create(AOwner: TComponent;
const FileName: TFileName); reintroduce; overload;
constructor Create(AOwner: TComponent;
const Stream: TStream); reintroduce; overload;
published
property Consumer;
property FileName: TFileName
read FFileName write FFileName;
property Stream: TStream read FStream write FStream;
end;
{ Write the document out to the specified destination. }
function TXBBWriter.ProcessDocument(
const Document: IDOMDocument): IDOMDocument;
begin
if (Filename = '') and not Assigned(Stream) then
raise EXBBException.Create(
'No filename or stream specified');
with Document as IDOMPersist do
if Assigned(Stream) then
SaveToStream(Stream)
else
Save(Filename);
Result := Document;
end;

Figure 7: Saving a DOM document.

the TXBBParser component, and allow you to load the transformation


from a URL or local file, from a stream, or from a list of strings.
Within the overridden ProcessDocument method, the class checks
that at least one of the source properties has been set and raises an
exception if this is not the case. Otherwise, it loads the XSL document and applies it to the XML document passed in. Because of the
nature of the XML building blocks, the result of the transformation
must be another DOM structure, so it can be passed further down
the chain. However, it is possible to format HTML as an XML
document, leaving only the XML header to strip off.

Linking Blocks
These three building blocks are enough for a simple application.
Together, they let you load an existing XML document into a DOM,
apply a transformation to that DOM, and write out the resulting
structure to another file. Because the classes descend from TComponent
and the properties are published, you can drop these items onto your
form and set them up at design time. Then, you only need to call
the CreateDocument method of the parser to start the whole process.
Alternatively, you can modify the property values at run time before
initiating the DOM processing, as shown below:
{ Initialise and link the components... }
xbbParser.Consumer
:= xbbTransform;
xbbParser.XMLSource
:= 'c:\temp\movie-watcher.xml';
xbbTransform.Consumer := xbbWriter;
xbbTransform.XSLSource := 'c:\temp\movies-only.xsl';
xbbWriter.FileName
:= 'c:\temp\output.xml';
{ ...and start the whole process. }
xbbParser.CreateDocument;

25 September 2002 Delphi Informant Magazine

{ Apply XSL transformation to DOM document. Document can


come from an existing file (XSLSource), from a stream
(XSLStream), or from memory (XSLText). }
TXBBTransform = class(TXMLBuildingBlock)
private
FXSLSource: TFileName;
FXSLStream: TStream;
FXSLText: TStrings;
protected
function ProcessDocument(const Document: IDOMDocument):
IDOMDocument; override;
public
constructor Create(AOwner: TComponent); overload;
override;
constructor Create(AOwner: TComponent;
const XSLSource: TFileName); reintroduce; overload;
constructor Create(AOwner: TComponent;
const XSLStream: TStream); reintroduce; overload;
constructor Create(AOwner: TComponent;
const XSLText: TStrings); reintroduce; overload;
destructor Destroy; override;
published
property Consumer;
property XSLSource: TFileName
read FXSLSource write FXSLSource;
property XSLStream: TStream
read FXSLStream write FXSLStream;
property XSLText: TStrings read FXSLText write FXSLText;
end;
{ Apply transformation specified earlier to document. }
function TXBBTransform.ProcessDocument(
const Document: IDOMDocument): IDOMDocument;
var
XSLDocument: IDOMDocument;
begin
if (XSLSource = '') and
not Assigned(XSLStream) and
(XSLText.Text = '') then
raise EXBBException.Create(
'No source specified for XSL document');
XSLDocument := NewDocument('xslt');
with XSLDocument as IDOMPersist do
if XSLSource <> '' then
Load(XSLSource)
else if Assigned(XSLStream) then
LoadFromStream(XSLStream)
else
LoadXML(XSLText.Text);
Result := NewDocument('out');
(Document as IDOMNodeEx).TransformNode(
XSLDocument, Result);
end;

Figure 8: Applying a transformation.

Figure 9 shows the demonstration program that performs this task. In


this example, the transformation document generates HTML tags in an
XML-compliant manner and writes the results out to the specified file.
Then, that file is loaded into a Web browser component for viewing.
Because the file has an html extension, its rendered as HTML.
Note that the Microsoft XML (MSXML) package is used as the implementation of the DOM in this example, because it comes with Delphi
and provides all the necessary functionality. If you want to use another
package, you may need to obtain an appropriate wrapper for it that
integrates it with the standard Delphi 6 DOM framework.
One of the output possibilities of the TXBBWriter component is to
send the document to a stream. This allows you to use the components within a Web module and write directly to the response

Delphi at Work
The XML building blocks are designed to fulfill this requirement. They
are defined through two simple interfaces, allowing them to be implemented in any number of existing or new classes. One interface describes
objects that generate new DOMs, and the other describes objects that
read or update existing DOMs. By declaring a basic component that
implements both interfaces, you have the basis for chains of objects that
create a DOM and then operate on it in turn. With a little thought and
care, these objects can be designed to be reusable in a number of different
situations, promoting their reuse and stability.
Ive presented an implementation of the two interfaces that serves
as the basis for building-block components. From this, building blocks are created to generate a DOM from an existing XML
document as text (TXBBParser), to write out an altered DOM as
text (TXBBWriter), and to apply an XSL transformation to a DOM
(TXBBTransform). Next month, youll see how the building-block
concept is extended to components that create DOMs from other
sources and that modify the DOM on the way through.
Figure 9: Using XML building blocks to transform a document.

References

stream. The principle of these building blocks is that they can be


reused easily in a variety of situations.

For information about topics in this article, go to the following URLs:


XML specification http://www.w3.org/TR/REC-xml
Document Object Model http://www.w3.org/DOM
XSL transformations http://www.w3.org/TR/xslt

Conclusion
XML documents provide a standard way of defining data and the
relationships between them, making it easier to transfer information between applications and platforms. The DOM specification
supplies a standard way of dealing with XML documents as an object
hierarchy, both for navigating existing documents and for creating
new ones. However, it would be useful to have an additional generic
layer on top of the DOM to provide commonly used functionality in
a plug-and-play manner.

26 September 2002 Delphi Informant Magazine

The files referenced in this article are available on the Delphi Informant
Magazine Complete Works CD located in INFORM\2002\SEP\
DI200209KW.

Keith Wood hails from Brisbane, Australia. He is a consultant using Delphi and Java. He
started using Borlands products with Turbo Pascal on a CP/M machine. His book, Delphi
Developers Guide to XML (Wordware, 2002) covers many aspects of XML from a Delphi
point of view. You can reach him via e-mail at kbwood@compuserve.com.

OP Tech
StringReplace / Optimization / Delphi 4-6

By Peter Morris

Optimizing StringReplace
Part II: Step-by-Step Redesign

ast month, we saw that even Borland can produce a flawed routine specifically,
the StringReplace function. We also examined the Boyer-Moore string-searching algorithm, which does the same job much more efficiently. We know that StringReplace is slow.
Now its time to find out why, and to replace it with a better, faster function of our own.
Borlands implementation of StringReplace is shown
in Figure 1. There are some obvious flaws that make
this routine slower than it should be. In a non-casesensitive replacement, the entire source string is
converted to upper case, causing read and write access
to every character before you even start. But thats a
minor point compared with some other problems.
As with the string-counting routine discussed last
month, StringReplace keeps a copy of your source
string in a temporary variable so it can use Delete

function StringReplace(const S, OldPattern, NewPattern:


string; Flags: TReplaceFlags): string;
var
SearchStr, Patt, NewStr: string;
Offset: Integer;
begin
if rfIgnoreCase in Flags then
begin
SearchStr := AnsiUpperCase(S);
Patt := AnsiUpperCase(OldPattern);
end
else
begin
SearchStr := S;
Patt := OldPattern;
end;
NewStr := S;
Result := '';
while SearchStr <> '' do begin
Offset := AnsiPos(Patt, SearchStr);
if Offset = 0 then begin
Result := Result + NewStr;
Break;
end;
Result :=
Result + Copy(NewStr, 1, Offset - 1) + NewPattern;
NewStr :=
Copy(NewStr, Offset + Length(OldPattern), MaxInt);
if not (rfReplaceAll in Flags) then begin
Result := Result + NewStr;
Break;
end;
SearchStr :=
Copy(SearchStr, Offset + Length(Patt), MaxInt);
end;
end;

Figure 1: Borlands implementation of StringReplace.


27 September 2002 Delphi Informant Magazine

to remove characters from the front of the source


string (due solely to Pos not being able to find
subsequent matches in a string). Finally, every time a
replacement is made, Result is re-assigned. Heres an
approximation of the internal steps of this function:
1) Duplicate the source string and force it to
uppercase (and do the same for the substring).
2) Find an occurrence of the substring in the
source string.
3) Allocate enough memory for Result, plus the
characters before the occurrence, plus the
string to replace the substring.
4) Copy Result to the new memory location.
5) Copy the memory up to, but not including, the occurrence to the end of the new
memory location.
6) Copy the substring replacement to the end of
the new memory location.
7) Free memory used to hold the current contents of Result.
8) Point Result to the new memory location.
9) Use Delete to chop off the characters, up to
and including the current occurrence from the
temporary variable representing your source
string. This is a relatively expensive operation
that involves allocating memory, copying each
character, and de-allocating memory.
10) Repeat step 2.
Imagine a 10MB string with 1,000 occurrences
of a substring to be replaced. In this example,
StringReplace would copy 10,000MB of data
in reassigning Result alone (not considering the
reallocation of the SearchStr variable). In such
circumstances, I have aborted StringReplace after
eight hours of processing, simply because I ran
out of patience.
So how can this routine be improved? It cant.
Whats needed is a better algorithm. Often, its
better to improve the algorithm than to improve
the speed of your code. This is one of the best

OP Tech
examples of such a situation Ive come across. I would suggest following these guidelines:
1) Dont convert the source string to uppercase. This requires read
and write operations for each character in the string, and the string
could be very large. Instead, convert each character to uppercase as
required. Because youre not reading every character when using the
Boyer-Moore technique, this will save time.
2) Use a faster search algorithm. Although it doesnt make a massive
improvement to the overall performance of the routine, it does allow
you to implement a FindNext method easily.
3) Dont keep reassigning Result. If you start off with a memory buffer
thats probably large enough to store your result in the first place,
you can just add to the buffer until you finish or run out of buffer
space (at which point you allocate another larger buffer and copy
your Result to that instead).
4) Finally, resize the memory buffer to the exact number of bytes
you wrote to it, just to ensure you dont have garbage attached to
the end of your string.
All of these guidelines are implemented in the custom BMStringReplace
function, shown in Listing One (available for download). Lets step
through the function:
1) Calculate the length of the source string, old pattern, and new pattern. Theres no point in calling a subroutine to discover these values
when you can just store them in a local variable.
2) If either the source or find string is null there will be no changes, so
you can set the result to the source string and exit the function.
3) Store a pointer to the old pattern (the find string) and the start of
S (the source string). These are used when asking BMPCharPos or
BMPCharPosNoCase to find the next occurrence of a substring.
4) Try to guess how big the resulting string will be in a worst-case scenario. If the NewPattern string is smaller than the OldPattern, then
at worst the result will be the same size as the source string,
i.e. if OldPattern does not occur once within the source string. If the
NewPattern is larger than the OldPattern, assume the source string
consists only of OldPattern repeated many times, i.e. the worst case.
5) Replacing a small piece of text, such as <!Comments!>, with a
large piece of text would cause your estimation of the size of the
result to be very large. If the estimated size is more than 10 times
larger than the source, or if your calculation of the estimated size
causes an integer overflow, you should assume that the size of the
result will be the same as the size of the source string. If this is
the case, increase the size of the result buffer by a factor (Ive used
1.5) each time the buffer is exhausted.
6) Build a jump table (discussed last month). Depending on
whether the search is case-sensitive, youll use MakeBMTable
or MakeBMTableNoCase.
7) Set a local variable to point to your Boyer-Moore search routine.
Depending on whether the search is case-sensitive, it will point to
BMPCharPos or BMPCharPosNoCase. This is done simply because
its much quicker to call a routine directly than it is to have an if
statement within a loop. Every little bit helps.
8) You enter one of two possible loops. If you are replacing OldPattern
with an empty string, you dont need to worry about copying anything
from NewPattern, and you can eliminate a few lines of code within
each loop. (Ill cover the process of replacing a substring with another
rather than simply deleting it.)
9) The next occurrence of the OldPattern is found in the source string.
All characters up to this occurrence are copied into the Result buffer,
and the pointer to the start of the Result buffer is incremented.
10) The contents of NewPattern are copied to the Result buffer, and the
pointer to the start of the Result buffer is incremented.
11) The pointer to the start of the source string is incremented past the
28 September 2002 Delphi Informant Magazine

current occurrence, so the next call to your find routine will return
the occurrence after it. Incrementing a pointer to the start helps you
avoid having to delete part of the source string each time.
12) Repeat step 9 until no more occurrences are found.
13) Resize your Result buffer to the number of bytes written, plus the
number of characters left at the end of your source string. Resizing
your Result buffer actually causes a copy of the entire Result string,
but a single copy is relatively instant.
14) Finally, copy the trailing characters of the source string to the
end of Result.

Not Everyone Speaks English


Some English-speaking tourists have great difficulty understanding the
simple fact that not everyone speaks English. They have the annoying
habit of expecting everyone to speak English everywhere they go. In
the world of computer programming, such an assumption is a luxury
you cannot afford. Simply put, if your application cant support foreign
languages, you eliminate markets. This is one mistake I dont intend to
make. With the small chance that this article may be translated into an
Asian language that requires multi-byte character sets (in which a single
character may be represented by more than a single byte), I intend to
demonstrate an implementation that will work with any locale. If youre
reading this article in China, Japan, Korea, Singapore, etc., welcome!
As you read earlier, improving the speed of the substring location
algorithm improves the speed, but its not the biggest factor. The
largest time savings was brought about by altering a pointer to the
start of the string, rather than by deleting the beginning of a string
each time and having to copy all the contents repeatedly.
If you reexamine the source code of Borlands StringReplace, youll
see that it doesnt use the Pos command. Instead, it uses AnsiPos,
which is just a wrapper for a function named AnsiStrPos, which
works on PChars instead of strings:
function AnsiStrPos(Str, SubStr: PChar): PChar;

If you were to use AnsiStrPos instead of AnsiPos, you would be able to use
a similar technique. With only small changes to the StringReplace function, you would be able to improve the speed dramatically while keeping
the same parameters and functionality. There are really only two changes
I will make: 1) try to predict the size of the result to avoid too many
buffer reallocations; and 2) use PChars to point to the start of strings and
increment those pointers rather than deleting text from the beginning of
the strings. An implementation of such a function, named AnsiReplace, is
available for download (again, see end of article for details).

Conclusion
In this series, weve learned how poorly one standard Delphi function
can perform when put under stress. We also explored and implemented
a version of the Boyer-Moore searching routine. Finally, we learned why
StringReplace performs so badly, and wrote a replacement.
The osFastStrings.pas unit and demonstration project referenced in this article
are available on the Delphi Informant Magazine Complete Works CD
located in INFORM\2002\SEP\DI200209PM.

Peter Morris is 26 years old and married, with two children. He works from home
as a computer programmer for Insite Technologies Ltd., where he writes a wide
variety of non-standard-looking Windows applications. He loved breaking away
from the Windows GUI standards. Now, he writes special graphical effects and
animated components. You can reach Peter at MrPMorris@hotmail.com.

New & Used


By Robert Leahey

PocketStudio 1.1
The PalmOS IDE for Pascal

Well Mr. Leahey, we like your suggestions and have decided that Thoughtsmithy is just
the company to provide for our development needs. This could easily turn into two or
three years worth of work, so we want to make sure your company is well taken care
of. Do you need any equipment new notebook computers, software upgrades? Well
take care of all that. How about a new car? We dont want you putting lots of miles on
yours. Better yet, well provide a driver and valet for you. We have two empty office
spaces your development team could use. Ones on the coast, the other is in the mountains; which would you prefer? Oh, by the way, well need a PalmOS version of this
software for our traveling teams. You can do that right?
Gulp. Why yes; of course we can.
Good, we wouldnt want that little detail to spoil
this contract, would we?

Figure 1: The PocketStudio 1.1 IDE.


29 September 2002 Delphi Informant Magazine

Okay, the meeting didnt go quite like that, and those


definitely were not the terms of the contract, but the
pressure was indeed on for my little company to provide a PalmOS solution for a certain project. Id had
some experience with the industry standard PalmOS
development environment, CodeWarrior, and
knew that in that environment, visual held about
as much meaning as it does in Visual C++ that
is, not very much. Id also tried some of the other
Palm development tools, such as CASL and Satellite
Forms, but these were too much like working in
Visual Basic large run-time libraries, reduced
functionality, etc. I even examined the Java subset for
micro-devices, but it wasnt viable at the time that I
researched it. What I desperately needed was something more Delphi-like: a powerful and truly visual
development environment for the PalmOS.

New & Used


that the decision to make an IDE that looks and feels like Delphi,
but that features different glyphs, is a mistake. I may be feeling
productive, feeling like Im in Delphi, but then suddenly Im lost
when I go to the toolbar.
Figure 2: Delphis toolbar.

Figure 3: PocketStudios toolbar.

I had seen references to just such a beast on some of the Borland


newsgroups: PocketStudio by Pocket Technologies. As a result, Ive
been experimenting with version 1.1 of PocketStudio, a PalmOS
application development environment featuring a 32-bit Pascal
compiler and an extensible IDE. PocketStudio produces optimized
native PalmOS applications (PRC files) with no need for other runtime libraries. In this regard, it is much like Delphi; however, the
depth of similarities varies depending on what facet of the products
were discussing.
Since this is a Delphi publication and because PocketStudio is
touted as a way to leverage your Delphi skills in the PalmOS
development world, well be looking largely at the product as it
appears to Delphi programmers. So the question is, will a Delphi
developer, trying to move to Palm development, benefit from using
PocketStudio versus one of the other Palm development tools?

Pricing
The first thing you may notice is PocketStudios ridiculously low
price. Metrowerks CodeWarrior 8.0 for Palm is currently going
for US$499 (with an enterprise edition available for US$1,299).
Pocket Technologies immediately wins this round by pricing
PocketStudio Standard at US$59.99 and PocketStudio Professional
at US$199.99. Granted, Metrowerks has been at this longer
(version 8), but PocketStudio offers a comparable feature list. And
when you add the language issue (CodeWarrior is C/C++ based)
this becomes a huge plus in the PocketStudio column.

IDE
I have mixed feelings about the PocketStudio IDE (see Figure 1).
On first glance it should be obvious that a good amount of work
has gone into making this environment look and feel like home
to the Delphi developer. Many of the windows with which we
are familiar are present; many keystrokes are duplicated (@ for
instance, will toggle between the code editor and the resource
designer).
If you look a little closer, however, youll see my first reservation
about this interface. The marketing materials for PocketStudio suggest that a Delphi developer will feel at home in the IDE; were
also encouraged to use our existing Delphi skills to be productive in
PocketStudio. Have a look at Figures 2 and 3 the toolbars from
Delphi and PocketStudio respectively.
Pocket Technologies has chosen to use different glyphs in their toolbuttons. The two toolbars have seven commands in common: can
you spot them? This may seem like a trivial issue, especially if youre
not a toolbar-minded person, but I contend that its a very big issue
for any developers who, like me, depend on their toolbars. I think
30 September 2002 Delphi Informant Magazine

That being said, there are some nice enhancements to the PocketStudio IDE that are smart. For instance, the Editor+ technology
offers the ability to collapse and expand method implementations.
There are many features that will be familiar to Delphi programmers, such as Code Completion and various wizards to automate
project and code creation. The Resource Designer is also well done,
with easy access to all the resources in a given form (see Figure 4).
On the whole, Delphi developers will feel at home in the PocketStudio IDE, but there are some exceptions, and developers should
be forewarned not to expect PocketStudio to be Delphi; its not.
That will be even more evident when you begin writing code...

Language
Lets get this out of the way early: PocketStudios flavor of Pascal is
not Object Pascal as used by Delphi. It is not at all object oriented;
there is no VCL or inheritance, there are no interfaces or cool
third-party component sets and you wont be writing any custom
components yourself any time soon.
Does that mean you cant be productive using it? Absolutely not!
The preceding list merely embodies the points that most people
seem surprised by when trying PocketStudio; Pascal without all
those things is still Pascal and you know Pascal. Unless you
know C++, youre going to be more comfortable in PocketStudio
than you will be in CodeWarrior.
Another thing to be aware of is that PocketStudio forces you to
know the PalmOS API more thoroughly than Delphi does the
Win32 API. Its possible to write working applications in Delphi
without ever writing a Win32 API call. With PocketStudio, youll
be writing PalmOS API calls, so be prepared. The reason for all this
is that the Delphi architects have done a marvelous job of encapsulating the Win32 API in the VCL. Its not that your application
isnt calling the API; its just that those calls are typically buried
in the VCL classes. With PocketStudio, we are, for the most part,
missing that layer of abstraction.
However, Pocket Technologies is doing something exciting that is changing some of this. Theyve created something called PSLibraries units
that wrap common PalmOS API calls and provide routines that are more
Delphi-like. Theyre still not objects, but youll find routines that seem
familiar to you. For instance, the PSList unit assists you in managing
TList objects by providing routines such as Add, IndexOf, Remove, Count,
etc. If youre taken aback that this functionality is even being discussed,
then you get the point. The convenience to which you are accustomed
in Delphi is a product of the robust classes in the VCL; but there are no
classes in PocketStudios Pascal.
Let me make it clear that this is not a failing on the part of Pocket Technologies; the PalmOS is not object oriented, and theyve done a marvelous job of trying to make object-like behavior appear where there are no
objects. For instance, in the PSList unit I mentioned, the Insert routine
(which inserts an item into a list) contains 28 lines of code. Thats 28
lines you wont have to write every time you instead call PSLine.Insert.
The best part about the PSLibraries is that Pocket Technologies is
continuing to write more. At the time of this writing, an update to

New & Used


it seemed to me at the time, had a marvelous help system, and for
someone who was as lost as I was in the complexity and potential
of that vast environment, the robust context-sensitive help was
a Godsend. When the time came for me to learn a new tool (i.e.
PocketStudio), I knew that 1 was going to get another workout.
Unfortunately, the products current help system is completely inadequate for the task. I know that the fine developers at Pocket Technologies probably suffer from the same myopia that I have more than once
succumbed to that quantity of documentation equates to quality.
There are many, many topics full of documentation on various PalmOS
API calls, but 90% of the time, when Im in the PocketStudio IDE
and I click 1 thinking, What the heck is this? Im greeted by the
message, The topic does not exist. Contact your application vendor for
an updated file. This does not make for a pleasant learning experience.
This is one thing Id really like to see Pocket Technologies address.

Extensibility
An area where PocketStudio really shines is in extensibility. It was
obviously not lost on the developers that one of the major strong
points of Delphi has been its open architecture, and by providing
PocketScript, they allow the addition of XML-based wizards into the
IDE. As PocketStudio gains acceptance, expect to see more and more
plug-ins based on this architecture.
Figure 4: PocketStudios Resource Designer.

PocketStudio was planned


which would add a few
more units to the 16
that currently make up
the PSLibraries. If youre
not an experienced Palm
developer, and the thought
of making lots of PalmOS
API calls makes you a little
dizzy, take heart in the
existence of the PSLibraries;
but also pick up a good
book on the PalmOS API
youll need it.

Components
PocketStudio comes
standard with 17 components, which are the
standard Palm UI controls:
Figure 5: One of my applications runbuttons, lists, checkboxes,
ning in POSE as a Handspring Treo.
labels, sliders, tables, and
even a graffiti state indicator
among other things. There is also a control called GADGET, which
enables the developer to create a custom UI widget. These controls comprise all you need to create standard PalmOS applications. If you need to
supply a different UI element, youll have to either use a shared library, or
get very familiar with the PalmOS API.

Documentation
We come to what I feel is the Achilles heel of the current version
of PocketStudio. As good as this product seems to be, and as much
potential as it seems to have for opening up the world of PalmOS
development for Pascal developers, it also stands to drive off as many
customers as it gains simply by its lack of quality documentation.
Like many developers, I learned how to program using Delphi 1.0
under the venerable tutelage of 1. The first version of Delphi,
31 September 2002 Delphi Informant Magazine

Debugging
PocketStudio offers integrated debugging in tandem with POSE
(the PalmOS Emulator). POSE creates a virtual PalmOS device on
your PC, allowing you to test applications without the overhead of
installing your application on a physical unit (see Figure 5). Plus, by
downloading various ROM images, you can test your application
against a variety of devices. The debugger offers some of the niceties
youre familiar with in Delphis debugger, such as Tool Tip evaluation, local variables watch, and a registers window.

Quality of Effort
Make no mistake; this first version of PocketStudio is a landmark effort
in quality and power. The drawbacks Ive listed in no way diminish
the value of PocketStudio. This PalmOS development tool should be
considered a serious contender for best PalmOS development environment, regardless of your language background. And if youre a Delphi
developer, you really have no viable reason not to choose this as your

PocketStudio is a PalmOS application development environment


featuring a 32-bit Pascal compiler and an extensible IDE.
PocketStudio produces optimized native PalmOS applications
(PRC files) with no need for other run-time libraries. Its not
Delphi for PalmOS, however, and it does suffer from poor
documentation, but its the PalmOS IDE for Delphi developers.
Pocket Technologies, Inc.
Suite 1700
701 Fourth Avenue South
Minneapolis, MN 55415
E-Mail: info@pocket-technologies.com
Web Site: http://www.pocket-technologies.com
Price: PocketStudio Standard, US$59.99; PocketStudio Professional,
US$199.99; PocketStudio Professional + Training, US$249.99.

New & Used


PalmOS development tool. Yes, there are drawbacks and issues Id like
to see addressed, but this is a first version. In the months and years to
come, I expect to see versions that will make us forget those problems.

Conclusion
Can a Delphi developer use his or her skills to more quickly
develop PalmOS applications using PocketStudio? The answer is
Yes. The developer must understand, however, that this product
is not Delphi for PalmOS. He or she will be disappointed. Ive
seen postings on various newsgroups from people who seemed
to think they would be porting their existing Delphi projects
to PocketStudio and have a working PalmOS application in no
time. There needs to be an awareness: PocketStudio is a powerful PalmOS development environment. And its the one I suggest
heartily. But a developer will have to know the platform for which
they are developing. PalmOS development is completely different
from Windows development, and unless a developer gets a good
grounding in the PalmOS API, he or she is in for a rough time.
Use PocketStudio; as a Delphi programmer, its the one you need for
PalmOS development. Just go into the process with your eyes open.

Robert is a Delphi developer who has been writing code since he discovered AppleBasic on his Apple II+. He now provides custom creative solutions and Delphi tool
training via his company, Thoughtsmithy (http://www.thoughtsmithy.com). He is
working on his Masters degree in Music Theory at the University of North Texas and
currently resides in Texas with his wife and daughters.

32 September 2002 Delphi Informant Magazine

New & Used


By Robert Leahey

DBISAM 3.08
Powerful BDE Replacement in a Small Package

ith version 3, DBISAM maintains its place as the premier BDE replacement, and
just for grins adds client/server capabilities to its list of features.

Im currently finishing up a project for a client.


Theyre firmly entrenched in Microsoft development tools, but after tiring of wrestling with
Crystal Reports, Microsoft Access reports, and
some homegrown efforts, they contacted me about
creating a reporting module for their product.
After tackling their convoluted Access database, I
built a system that creates temp tables outside of
Access which I then use for my reports. The results
have been impressive: My system is able to query
the existing data, create temp tables, and generate
the reports in less time than it was taking to generate the reports directly from the Access data.
My clients are in upheaval. Some of the developers
wonder why theyre not using Delphi. All of them
want to replace Access with the database tool Im
using: DBISAM. When I told them how cheap
it was, they gasped. When I said that the entire
engine was compiled into my module, and that I
didnt have to deal with any distribution headaches, they rioted. They tore down their cubicles;
they chanted, D - B - I - SAM! D - B - I - SAM!
When I told them that it was only available for
Delphi and C++Builder, they wept; they gnashed
their teeth. Yes folks, Im here to tell you about
one of the finest database systems available, and its
right here in our little Delphi camp.
I used to be employed by the makers of a wellknown reporting tool for Delphi. It was during
my time there that I became acquainted with
DBISAM. When testing our product to make sure
it was working well with all the major database
tools, I always looked forward to using DBISAM;
its small, its fast, its powerful, its 100% Object
33 September 2002 Delphi Informant Magazine

Pascal and its well-designed. Its one of the best


examples I know of what can be achieved when
Delphi is wielded by an expert hand.

Appearances Can Be Deceiving


The first thing to know about DBISAM is that
theres more to it than appears at first inspection. After installation, there are four new data
access components on Delphis Component
palette. (There are also five new visual controls
that allow you to recompile DBISAMs DbSys
utility, but more about that later.) Theres
little to suggest that youve just transformed
your database development paradigm. Have a
look at the Manual.pdf file in DBISAMs Help
subdirectory; after about 150 pages of 8-point
type (none of it fluff ) youll begin to realize that
youre dealing with a substantial tool packed
into a relatively small footprint.
Since its introduction in 1998, DBISAM has
been highly regarded as a BDE replacement
option, but has received some criticism for not
fully supporting the ANSI SQL-92 specification. However, the product has done nothing
but improve in the ensuing years. Although its
true that DBISAM still supports only a subset
of SQL-92, it supports the most widely used
elements. With this in mind, a prospective user
should examine the DBISAM documentation to
see if its SQL-92 support will meet their needs.

Why Replace the BDE?


In my time helping to support that third-party
reporting tool, I had a chance to see just how
many Delphi developers use only the default

New & Used


toolset that comes with Delphi. In many cases, those tools meet
their needs and they dont see a reason to change. Some dont
want to pay for a replacement, no matter how many problems
they may have. A notable case in point is the BDE (Borland
Database Engine).
The BDE has a storied past and many redeeming qualities. Speed
and size, however, arent two of those qualities. Neither are the
many issues with configuration and distribution, and that the
default data formats for the BDE (Paradox and dBASE) have
their own sets of problems (need we discuss Paradox user locks
and the horrors of corrupted indexes?). Suffice it to say that, for
many years, several tool vendors have made a decent living simply
supplying a replacement for Delphis default database engine for
developers who were looking for smaller, faster, better, and/or
more reliable. If youve ever had unpleasant experiences working
with the BDE, you might consider trying a replacement, and the
one I recommend is DBISAM.

Features
There isnt room here for an in-depth discussion of DBISAMs
features (theyre numerous and robust), and many of them have
been discussed before in other reviews of the product. Instead, I
encourage you to download the trial version and read the documentation. However, we do have room for an overview of the
products main components.
Once installed, the four data access components added to Delphi
are TDBISAMTable, TDBISAMQuery, TDBISAMDatabase, and
TDBISAMSession. These components are largely direct replacements
for Delphis standard BDE components, i.e. Table, Query, Database,
and Session. All of the standard functionality youre familiar with
is available in the DBISAM components; each of the components
behaves in a similar manner to their BDE counterparts. However,
they also include many DBISAM-specific enhancements. Because of
the similarities, converting an existing BDE application to DBISAM
is very simple; in most cases, a simple one-to-one replacement of
components does the trick. And with the available BDE Transfer utility, you can take your existing BDE-based database and convert the
data to DBISAM tables, getting your legacy BDE application off the
ground with DBISAM in no time.

Distribution Dreams
When you develop an application using DBISAM, you can include
the DBISAM engine either by compiling it directly into your application, or by providing it as a run-time package. If you choose to
deploy it as a package, you need only distribute one BPL file. This is
quite refreshing (and enviable) if youre used to distributing BDE- or
ADO-based applications. If you deploy with the engine compiled
into your application, youve no distribution issues whatsoever; your
database engine is part of your executable.

Test Bench
I have two sets of favorite test data. One database is a set of master/
detail tables (M/D/D/D/D) in which the lowest detail table contains
1,000,000 records. The other reveals my passion for the game of
baseball; its a very large database containing all the statistics for every
baseball player thats ever been in the major leagues over 15,000
players resulting in over 400,000 records of statistics. These two
databases provide a good test bench since the million-record database
is large but doesnt represent real-world data, and the baseball data is
smaller but is more realistic, requiring SQL statements with several
joins and calculations to provide meaningful data.
34 September 2002 Delphi Informant Magazine

Using these databases, I created several queries to test the response


times of our three contestants: Access, Paradox, and DBISAM. I
also created four complex cross-tab reports from the baseball data
to test the engines in a real world setting.
Below are the results of these tests. However, before I get into the
test bench results, I have an interesting observation. My millionrecord master/multi-detail database was created in Paradox, so to
conduct these tests, I needed to convert the database for Access
and DBISAM. To do so, first I used DBISAMs BDE Transfer
utility to convert the Paradox tables to DBISAM tables. The
1,111,100 records were converted in about 90 seconds. Then it
was Access turn. Using its built-in import function, Access was
able to convert about 900,000 of the records before it locked up
nine hours later. I tried again using a custom-built transfer utility
I wrote in Delphi to handle the huge move via ADO. It got to
600,000 records before using up all of my 512MB of RAM. Id
say that in the data-transfer arena, DBISAM beats Access/ADO
hands down. Thats a rather unquantified measurement, but an
interesting point, nonetheless.
In the baseball database tests, I ran four different queries: One
to retrieve all seasons for all batters, performing one calculation;
the second retrieved the same fields, except with a more complex
set of search criteria; the third retrieved all seasons for all batters,
performing two calculations; and the fourth was the same as the
third with the same restrictive search criteria as the second. Note
that Paradox was not able to perform the second, third, or fourth
queries because of its SQL limitations.
Of the three products tested, Paradox/BDE always finished first,
with Access/ADO a close second. DBISAM didnt fare as well,
usually taking 2-3 times as long as the BDE queries. Theres
one caveat to these results: By keeping the databases and queries
identical, I feel that DBISAM wasnt able to perform at its best.
It suffered from the lowest-common-denominator effect. Once
I optimized the tables in these tests for DBISAM and reran the
queries, DBISAMs performance went up remarkably generally
as fast as ADO, and sometimes faster.
I also ran simple table-traversal tests. In this area DBISAM really
shone. It regularly kept up with the BDE, sometimes surpassing it.
This is also where ADO did poorly, usually 10 times, or more, slower
than DBISAM if it was able to finish at all. Half my table tests
involving the million-record dataset caused ADO tests to hang.
In the cross-tab report tests, DBISAM outperformed its competitors in all four reports. The longest report, several thousand pages
of baseball calculations, was generated in seven minutes using the
BDE, 24 minutes using ADO, and 6.5 minutes with DBISAM.
Although the results of these tests may not be impressive on the
surface, remember that even though DBISAM may not have
always been the fastest of the tested products, its times were comparable. And when thats combined with the small footprint and
ease of distribution, its the hands-down winner in my book.

Other Interesting Elements


DBISAM also offers something no other database system can
claim: all the source to its main data management application.
As part of DBISAMs separate add-on utilities download, you get
dbsys.exe, a database management utility; analogous to Database
Desktop in Delphi, this utility is easier to use, and offers more

New & Used

DBISAM is a small database engine that can be compiled into an


applications executable to replace the often unwieldy BDE. Available for Delphi and C++Builder, it features power, compactness,
ease of distribution, great support and documentation, and good
company track record.
Elevate Software, Inc.
168 Christiana Street
North Tonawanda, NY 14120
Phone: (716) 694-1578
Fax: (716) 694-5209
E-Mail: info@elevatesoft.com
Web Site: http://www.elevatesoft.com
Price: US$249 for DBISAM Database System without source
code; US$379 with source code; US$699 for Client/Server edition.
Government, educational, and upgrade prices are available.
features. Im already making plans to start enhancing the application with my own customizations.
Of all the features of the DBSYS application, one of the most
noteworthy would have to be the Reverse Engineering option.
Running this on an existing table will result in all the code
required to recreate the table (with or without data) in SQL,
Object Pascal, or C++.
Perhaps the most compelling feature of version 3.x of DBISAM is
its client/server abilities. Obviously, thats a topic that could fill an
entire review, and we only have a little room left. However, I can say
that this ambitious undertaking is handled in the same exceptionally
high-quality manner as the rest of DBISAM. To find out whether the
client/server features are right for you, youll need to download it and
give it a try, but I think youll find that the client/server features of
DBISAM are every bit as powerful, smart, well-designed, convenient,
and well-supported as the features that have made DBISAM a favorite BDE replacement for several years.

Conclusion
DBISAM has been my favorite desktop database for some time.
Now, with the affordable and high-quality client/server feature set,
it may begin to bump aside the big players in the multi-tier arena as
well. While DBISAM did not take top honors in my time trials, Ill
reiterate that when its respectable performance is combined with
its other great features power, compactness, ease of distribution,
great support and documentation and good company track record
it easily rises to the top of any database engine list.

Robert is a Delphi developer who has been writing code since he discovered AppleBasic on his Apple II+. He now provides custom creative solutions and Delphi tool
training via his company, Thoughtsmithy (http://www.thoughtsmithy.com). He is
working on his Masters degree in Music Theory at the University of North Texas and
currently resides in Texas with his wife and daughters.

35 September 2002 Delphi Informant Magazine

New & Used


By Robert Leahey

ExpressQuantumTreeList Suite
Powerful, Flexible Data Presentation Components

eveloper Express ExpressQuantumGrid took top honors as Best VCL Component in the
recent Delphi Informant Magazine 2002 Readers Choice Awards. That probably didnt
surprise you; it won last year as well. What may surprise you, however, is that lurking behind
the notoriety of its big brother is another very powerful tool from Developer Express which
may appeal to those on a budget: the ExpressQuantumTreeList Suite (EQTreeList for short).
First, if you have the ExpressQuantumGrid Suite
(EQGrid for short), the components that make
up EQTreeList are going to sound familiar. Thats
because you already have them. Of the components in EQTreeList, the QuantumTreeList and
the ExpressEditors Library are part of the Standard edition of EQGrid (for US$299.99), while
these components and the QuantumDBTreeList
are found in the Professional edition of EQGrid
(for US$349.99). However, what Im reviewing
here is EQTreeList (for US$199.99) which basically contains everything in EQGrid Pro, except
for the famous QuantumGrid control. Confused?
Check out the feature matrix in Figure 5.

TdxTreeList
TdxTreeList is one serious data presentation component. This thing takes no prisoners and other
tree controls should fear meeting it in a dark alley.
What have Win32 developers been doing in any
sort of hierarchical display application since the
advent of Windows 95? Dropping a TreeView and

Figure 1: A TreeList control in action. Hank Aaron wasnt a bad player, eh?
A career .928 OPS? Wow.
36 September 2002 Delphi Informant Magazine

a ListView on a form with a splitter between them,


thats what. Whats the ListView for? To display
contextual information, perhaps in multiple columns, about the selected TreeView item.
This set up is fine if, for each TreeView item, you
have numerous ListView items not displayed in the
tree, e.g. files within folders. However, if you simply
want to show multiple columns of information
for each TreeView item, youd normally be stuck
shoehorning some other unwieldy display device
into this metaphor. TdxTreeList solves this problem
(and many others) by offering its services as a
hybrid TreeView/ListView control (see Figure 1).
The left-most items in the control are displayed as
in a normal TreeView, while columns, similar to a
ListView are displayed to the right.

Column Types
Each column in the TreeList control can be one of 18
different types (20 for the DBTreeList). This column
type determines, among other things, what sort of inplace editor will be used to edit a value in the column.
The column types are as follows:
Standard. Generic text display type and base class
to all column types.
Mask. Uses a mask (similar to Delphis
TMaskEdit) to format displayed data.
Button. Displays a customizable button in the
column which your users can click to edit the
value. This fires events to which you can respond.
Date. Displays a drop-down calendar for editing.
Check. Displays a check box editor.
Image. This confusingly named column
actually uses a drop-down list, which can
contain images and text (like an extended
combo box) as an editor.
Spin. Uses a spin button for editing of
sequential values.
Pick. Uses a drop-down list editor much like a
combo box.

New & Used

Figure 2: Popup column using a form for the popup control.

Calc. Displays a calculator as an editor, using the result of the


calculation as the column data.
HyperLink. Data in this column is displayed as clickable text
that generates an event when clicked.
Time. Values in a Time column are edited via a custom time
value editor.
Currency. Displays data as a currency value.
Memo. Offers a multi-line text editor for managing long
strings.
Graphic. Features an in-place editor that displays a picture
within the TreeList cell.
Blob. Allows the display and editing of BLOB data within a
sizable, drop-down window.
MRU. Offers a drop-down list of items, automatically
displaying the most recently used items.
Wrapper. Represents a column that allows different in-place
editors for each cell in the column.
Popup. Represents a column that can use any control,
including a custom form, as a drop-down window. See Figure 2
for an example.

Columns and bands (groups of columns) can be sorted by the


user by drag-and-drop. Its also possible to allow users to display
a column customization dialog box where they can choose what
columns to display. For a real shift to your Tree control paradigm,
try stacking columns under one another, as shown in Figure 3.
The abilities of this control dont end with robust column
control. There are an absurd number of customizations and
features available for this component. A partial list includes:
Column sorting. As you might expect in a full-featured, multicolumn vector control, clicking the column headers will cause
the TreeList to be sorted by that column. In addition, this
control offers sorting by multiple columns.
Automated column setting storage. Whats better than a
robust control that offers high user customization options?
How about one that remembers its own settings, and can
restore them by itself? If your users choose to rearrange their
columns and bands, the TreeList can store those changes in
the Windows registry, or in an INI file.
Previewing. See Figure 4 for an example of the ability to
display a given bit of text as a preview.
Robust control over visual appearance. Numerous options
to control the way the control is painted, how its grid
is rendered, the appearance of indicators, hot tracking,
row selection, buttons, footers, headers, hints, scrolling,
editing, etc. You can make the TreeList appear virtually
any way you like.
70+ events. A robust set of events for wide ranging control
over the component.
37 September 2002 Delphi Informant Magazine

TdxDBTreeList
As impressive as the
TreeList control is,
you havent seen
anything until youve
seen the data-aware
version work its
magic on your datasets. TdxDBTreeList
features all of the
same columns, and
adds two more:
Lookup. Displays
a drop-down
lookup list as an
in-place editor.
Extended
Lookup. Displays
a TdxDBGrid
as a drop-down
lookup editor.

Figure 3: Three rows of stacked columns


per TreeList item.

As you might expect, TdxDBTreeList offers the same visual flexibility


as TdxTreeList, and adds some niceties, such as the ability to
automatically gather image indexes and state image indexes from a
database field, self-referential table display, and an option to load
some or all records. TdxDBTreeList can also act as a data navigation
control, a database cursor position indicator, or both. In other words,
depending on your settings, you can have your tree control change
the position of the database cursor as users click on various nodes,
or it can stay synched with the database cursor, so that if the cursor
is moved by another control, the tree displays the current cursor
position, or it can do both.
While TdxDBTreeList doesnt offer everything that TdxDBGrid
does, its a substantial database component in its own right. It has
many features in common with TdxDBGrid because they share an
ancestor in TCustomdxDBTreeListControl.

ExpressEditors
A great set of components in their own right, the ExpressEditors
that are part of the suite seem almost an afterthought. But
these controls shouldnt be overlooked. You have available, as
stand-alone Delphi components, controls that are essentially the
same as the in-place editors you find in your TreeList cells; any
functionality you like can be found in the corresponding standalone editor. Youll find data-aware and non-data-aware versions
of dxEdit, dxMaskEdit, dxMemo, dxDateEdit, dxButtonEdit,
dxCheckEdit, dxImageEdit, dxSpinEdit, dxPickEdit, dxCalcEdit,
dxHyperLinkEdit, dxTimeEdit, dxCurrencyEdit, dxGraphicEdit,

New & Used

Figure 4: TreeList items with preview text.

Price

EQTreeList

EQGrid
Standard

EQGrid
Professional

US$199.99

US$299.99

US$349.99

QuantumGrid
QuantumTreeList

QuantumDBTreeList

ExpressEditors

Conclusion

EQTreeList is a great, affordable alternative to the EQGrid. Obviously, you should consider going all out and buying EQGrid Professional, but if budget is an issue and you dont need the extra features
offered by the EQGrid, you can get a great deal of bang for the
buck in EQTreeList. The feature/price matrix in Figure 5 provides
a comparison of the available products. The components found in
EQTreeList offer exceptional capabilities for minimal effort on the
part of the developer. Creating applications that are beyond the
no-frills nature of a quick-and-dirty Delphi application becomes a
simpler matter with ExpressQuantumTreeList Suite.

Figure 5: Feature and price comparison matrix.

dxBlobEdit, dxMRUEdit, dxPopupEdit, and dxLookupEdit. For


descriptions of the functionality of these editors, see the list of
column types earlier in this article.
In addition to the stand-alone editors, ExpressEditors includes an
edit style controller component, and a check edit style controller
component. These non-visual components serve as singular interface
points for setting the visual styles of multiple edit components.
The various edit components in ExpressEditors can link to a style
controller component, which can in turn control the visual
appearance of all the edit controls that link to it. The advantage here
is clear: Using the style controller components, you can set the visual
styles of several components at once.

Whats Not So Good


The components in the suite suffer from the same problem that
plagues most powerful developers tools: inadequate documentation. Im talking quality, not quantity; there are many pages of text
for these controls, both in Delphi integrated help files and in PDF
format. There are demo projects as well. But the PDF files have
essentially the same content as the online help, and the demo projects, while tantalizing, tend to show what the controls can do, not
so much how to do it. I really had to dig to figure out some of these
features that could have been made more transparent with the inclusion of some How To topics in the documentation.

ExpressQuantumTreeList Suite offers advanced hybrid tree-list


technology in data-aware and non-data-aware versions, as well
as an extensive library of stand-alone visual components with
many display options.
Developer Express, Inc.
6340 McLeod Dr., Suite 1
Las Vegas, NV 89120
Phone: (702) 262-0609
Fax: (720) 262-0619
Web Site: http://www.devexpress.com
Price: US$199 for single-user license.
38 September 2002 Delphi Informant Magazine

Ironically, another drawback of the TreeList


controls is that therere swimming in options.
While this is good in terms of flexibility, its
a problem, albeit a minor one, for usability.
TdxDBTreeList, for instance, has 62 Boolean
options grouped into four Set-type properties
(OptionsBehavior, OptionsCustomize,
OptionsDB, and OptionsView), in addition to a
dozen or more Boolean option properties like
ShowLines, ShowGrid, ShowFooter, etc. Trying
to find just the right property in the Object Inspector can be an
exercise in frustration when youre new to the components. It seems
absurd to complain about having too many options, so Ill reiterate
that, while this can cause some frustration, it does make for a very
flexible control a worthy trade off.

Robert is a Delphi developer who has been writing code since he discovered AppleBasic on his Apple II+. He now provides custom creative solutions and Delphi tool
training via his company, Thoughtsmithy (http://www.thoughtsmithy.com). He is
working on his Masters degree in Music Theory at the University of North Texas and
currently resides in Texas with his wife and daughters.

File | New
Directions / Commentary

Delphi Bookshelf 2002

f you have a bookshelf close to your development computer, take a moment to examine it. If its anything like
mine, it will include a number of newer books and a handful of old favorites. Well discuss both vintages in this
column, beginning with the newer books. First well examine new editions of established Delphi classics, then more
specialized new books. Well conclude by dusting off a few oldies but goodies that should be a part of the library
of every Delphi developer who wants to master the more advanced topics.
Perennial Classics. If youve read my earlier columns on this subject,
you know my choices as the two Delphi classics two books that have
had editions for almost every new version of Delphi and that have been
consistently at the top of the annual readers poll. Of course Im talking
about Mastering Delphi 6 by Marco Cant (SYBEX, ISBN: 0-78212874-2) and Delphi 6 Developers Guide by Steve Teixeira and Xavier
Pacheco (SAMS, ISBN: 0-672-32115-7). I reviewed earlier editions
of each of these outstanding works last year and also discussed them in
an earlier column. In this column I will summarize some of the main
features with emphasis on newer features.
As I mentioned, I consider Cants work to be better suited for a lessexperienced developer with its clear explanations of basic principles
and techniques, its tour of Delphis IDE, and its introduction to
Object Pascal. But it also includes more advanced topics, such as
working with dynamic-link libraries, and a short section on shell
programming. Like Delphi itself, it is especially strong in its coverage
of database and Internet development topics, including InterBase,
DataSnap, WebBroker, XML, and SOAP. It also includes all the
expected basic topics, and chapters on component writing.
Steve Teixeira and Xavier Pacheco take a similar approach to Cants,
beginning with a thorough introduction to the Object Pascal language. This work puts greater emphasis on cross-platform development (with Delphi and Kylix) and includes a discussion of creating
CLX components. It includes a much more extended discussion of
shell programming and multithreading. It ends with a chapter on
programming wireless devices. As a bonus, the CD includes chapters
from the previous edition not included in this volume.
Tomes and More. The two-volume Tomes of Delphi 3 by John Ayers, et.
al. was an acknowledged milestone in Delphi book publishing. These
were the first serious excursions exposing API-level programming with
Delphi. Considering all of the changes in the world of Windows development in the last several years, an update to these important works was
needed. It has now arrived! Earlier this year Ayers The Tomes of Delphi:
Win32 Core API, Windows 2000 Edition (Wordware, ISBN: 1-55622750-7) appeared. It included most of the earlier Core volume, and many
topics from the Graphical API volume.
A second, new Tomes of Delphi volume on the Win32 Shell API (Wordware, ISBN: 1-55622-749-3) will be available by the time you read this.
I had a chance to examine a pre-publication version while writing this
column. With emphasis on the Windows 2000 operating system, it
greatly expands the Shell API coverage found in the original Graphical
API volume. All the features of the earlier Tomes volumes are included,
39 September 2002 Delphi Informant Magazine

with at least one important addition those functions specific to Windows NT and/or Windows 2000 are indicated in the text.
One of the important new technologies supported in Delphi is XML.
Keith Woods Delphi Developers Guide to XML (Wordware, ISBN:
1-55622-812-0) deals exclusively with this topic in a comprehensive
way. All of the major XML-related technologies are covered, including
DOM and SAX. In many instances Wood discusses alternate Delphi
solutions to working with particular parts of the XML family. For example, after an excellent introduction to the Document Object Model
(DOM) followed by Microsofts implementation of it, he provides
two more chapters that introduce specific Delphi solutions: CUESofts
Document Object Model and the Open XML Document Object
Model, both of which are comprised of native Delphi Objects. If youre
working with XML in Delphi you should find this work invaluable.
Oldies but Goodies. My list of favorite oldies is similar to the Essential Delphi Library of Advanced Works I included in my last Delphi
book column a year and a half ago. Unfortunately, many of the books
are now out of print and difficult to find. Nevertheless, I recommend
adding any or all of them to your library. If you write experts, or work
in other ways with Delphi internals (e.g. RTTI), then you need both
of Ray Lischners early books, Secrets of Delphi 2 (Waite Group, ISBN:
1-57169-026-3) and Hidden Paths of Delphi 3 (Informant Press, ISBN:
0-9657366-0-1). The first deals with a variety of advanced topics, and
the second concentrates on the Open Tools API.
If you write components, Ray Konopkas seminal treatise on this
discipline, Creating Custom Delphi 3 Components (Coriolis, ISBN:
1-88357-747-0) is the fundamental book you must include in your
library. For information on other older and more recent Delphi
books I recommend you read my last column on the subject, Delphi
Book Wrap-up 2000 (in the November 2000 issue), available online
at http://www.DelphiZine.com. Until next time...
Alan C. Moore, Ph.D.

Alan Moore is a professor at Kentucky State University, where he teaches music theory and
humanities. He was named Distinguished Professor for 2001-2002. He has been named the
Project JEDI Director for 2002-2003. He has developed education-related applications with
the Borland languages for more than 15 years. Hes the author of The Tomes of Delphi:
Win32 Multimedia API (Wordware Publishing, 2000) and co-author (with John Penman)
of an upcoming book in the Tomes series on Communications APIs. He also has published
a number of articles in various technical journals. Using Delphi, he specializes in writing
custom components and implementing multimedia capabilities in applications, particularly
sound and music. You can reach Alan on the Internet at acmdoc@aol.com.

Anda mungkin juga menyukai