Sei sulla pagina 1di 55

June 2002, Volume 8 Number 6

Cover Art By: Arthur A. Dugoni Jr.

ON THE COVER
5

On the Net

WebSnap at Work Nick Hodges


Using new user registration as an example, Nick Hodges reveals the real
power of TAdapter, the class at the heart of WebSnap, by creating TAdapter
descendants to perform common tasks. Theres a learning curve to be sure,
but your productivity will skyrocket when building Web applications.

39

Inside OP

Floats vs. Currency Vsevolod Ciumaciov


When does zero not equal zero? If youve ever written a financial application, you know that float types can get you into trouble. Vsevolod
Ciumaciov explains the cause, and the cure the Currency type.

FEATURES

REVIEWS

11

42

Greater Delphi

Word Merge Jason Sweby


From Word 97 to Word 2002 (the Office XP version), Jason Sweby
demonstrates how to use the ubiquitous word processor as an
Automation server, and clarifies the trade offs of early and late binding.

17

On Language

Everyday Interfaces Leo Seaman


Leo Seaman states that The power of interfaces ... is their ability to
shotgun polymorphism into an object hierarchy, then proceeds to prove
it by sharing a wealth of knowledge and source code.

23

Informant Spotlight

Readers Choice Awards 2002 Jerry Coffey


Editor-in-Chief, Jerry Coffey, announces the results of this years awards
in 22 categories, plus Product of the Year, and Company of the Year.
Much is familiar, but there are some surprises.

30

OP Tech

Storing Data in an EXE or DLL Bill Todd


Many of you want to know how to store data in an executable or DLL,
so the data is more secure and the executable is easier to deploy. From
JPEG, to bitmap, to WAV, Bill Todd explains how its done.

35

In Development

Build Your Own Compiler: Part III Fernando Vicaria


Borland QA engineer Fernando Vicaria implements new features in
his do-it-yourself compiler. This months additions include multi-line
comments, command-line parameters, and keywords.
1 June 2002 Delphi Informant Magazine

IP*Works: Delphi Edition 5.0


Product Review by Mike Riley

45

IntraWeb
Product Review by Paul Stockton

48

Easy Compression Library


Product Review by Edward Owen

51

Borland Delphi 6 Developers Guide


Book Review by Tom Lisjac

52

Kylix: The Professional Developers Guide


and Reference Book Review by Brian Burton

52

The Tomes of Kylix: The Linux API


Book Review by Bruno Sonnino

53

The Visual Display of Quantitative


Information, Second Edition
Book Review by Alan C. Moore, Ph.D.

DEPARTMENTS
2

Delphi Tools

54

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

Delphi
T O O L S

New Products
and Solutions

Book Picks
Wireless XML Developers Guide
Mikael Hillborg
McGraw-Hill/Osborne

Hansen North America Presents ASP Express 2.0


Hansen North America
announced ASP Express 2.0
(ASPX) toolkit to the public.
ASPX is designed to simplify
Web application development
for Delphi developers. It turns
Delphi into a powerful Web
development tool by unleashing
the power of XML and XSL in
Delphis applications.
This product supports the latest
version of the Microsoft XML
Parser (MS XML Core Services
4.0 RTM) that supports the
W3Cs final recommendation for
XML Schema and works under
Windows XP. Significant performance improvement of the menu
rendering process is among the
key features in the new version
of ASPX. Another new feature is
TInputFileElement, which now
supports file uploading. Also, a
rebuilt installation program will
make the process of distributing

a lot easier it allows installing and uninstalling ASPX on a


computer without copying files
into system folders.
The ASPX 2.0 documentation
has been significantly improved,
and live ASPX 2.0 sample applications are now available online

at http://www.asp-express.com/
aspexpress.
Hansen North America
Price: Standard, US$199; Professional,
US$499; Premium, US$999.
Contact: aspx@hansenusa.com
Web Site: www.asp-express.com

Extended Systems Ships Advantage TDataSet Descendant Client for Kylix

ISBN: 0-07-219536-3
Cover Price: US$49.99
(525 pages)
www.osborne.com

Mastering XML: Premium Edition


Chuck White, Liam Quin,
and Linda Burman
SYBEX

Extended Systems, a provider


of mobile data management
and wireless connectivity
solutions, announced that its
Advantage Database Server
TDataSet Descendant Client for
Kylix is now available. Kylix
support allows Delphi developers to port existing Advantage
TDataSet Descendant applications to the Linux operating
system without rewriting any
of their database access code.

This feature enables developers


to expand into Web Services
applications, while capitalizing
on the performance, scalability,
and zero-administration technology offered with Advantage
Database Server.
The new Advantage Kylix
components will provide
database access without the
use of Borlands dbExpress just
as the Advantage Windows
components currently provide

database access without the


use of the Borland Database
Engine (BDE).
Extended Systems
Price: The Advantage TDataSet Descendant for
Delphi, C++Builder, and Kylix is provided as
a free add-on to Advantage Database Server.
Advantage Database Server ranges in price from
US$249 for a two-user license to US$7,495 for
an unlimited-user license.
Contact: info@extendsys.com
Web Site: www.advantagedatabase.com

Safin and Kaschenko Announce Formula Compiler 4.02

ISBN: 0-7821-2847-5
Cover Price: US$49.99
(1,155 pages, CD-ROM)
www.sybex.com

2 June 2002 Delphi Informant Magazine

Vladimir Safin and Dmitry


Kaschenko released Formula
Compiler 4.02 (FC) to the
public with source. FC is a set
of Delphi components that
allow mathematical functions
to be quickly evaluated at run
time faster than hard-coded
Delphi. It can be used in any
application that needs the
flexibility of evaluation expressions on the fly, at run time.
Since it parses and compiles
formulas into optimized
machine code, it will significantly benefit applications that
require intensive calculations
to be performed. This includes
database, spreadsheets, gaming,

scientific, and many other


types of applications.
FC supports more than
30 built-in functions and
arithmetic and relational
operators. Developers can
create and maintain libraries
of user-defined functions and
aliases using the TFormulaLib
component (part of FC). These
libraries can be manipulated at
design time and run time and
used in the TFormulaCompiler
component.
FC allows developers to add
their own unique and complex
functions via its OnFunction
and OnVariable events. These
events are triggered if a

formula contains an unknown


user function or variable.
Developers can write event
handlers where this function
will be evaluated and return
the result. FC supports all
versions of Delphi and is freely
available from the Delphi
Informant Magazine Web site
at http://www.DelphiZine.com/
features/1998/06/di199806vs_f/
di199806VS_d.asp.
Vladimir Safin and Dmitry Kaschenko
Price: Free with source.
Contact: (561) 266-6569
E-Mail: vsafin@kdsconsulting.com,
dimak@kdsconsulting.com

Delphi
T O O L S

New Products
and Solutions

Book Picks
XML and Web Services
Unleashed
Ron Schmelzer, et al.
SAMS

TurboPower Introduces ProActivate Licensing Toolkit


TurboPower released
ProActivate, a tool for software
licensing and distribution. With
ProActivate, software developers
can easily and affordably
capitalize on the enormous
potential for increased
revenues through electronic
licensing and distribution.
ProActivate provides everything
necessary to implement a
secure, flexible licensing
process. The ProActivate
Application Manager wraps
a security envelope around
virtually any 32-bit Windows
application almost instantly.
Users transparently gain
access to the contents of the
envelope by executing the
program, along with a binary
license file generated by the
ProActivate License Manager.
Its the license that grants all or
specific rights to the contents
of the security envelope. With
this highly flexible approach,
developers can issue limited

licenses that expire and then,


after payment, supply a new
license that exposes all program
functionality or just the features
that the user chooses to license.
Recognizing that software
consumers expect to try
programs before committing
to a purchase, ProActivate
makes this task especially
easy for developers. Creating
fully-functional, try-beforeyou-buy versions of virtually
any 32-bit Windows software
can be accomplished with no
source code changes. Alternate
licensing schemes are possible
as well, including software
rentals, licensing of specific
features, or licensing for a
specific number of executions.
ProActivate comes with
a database that helps
programmers track their
protected applications and
the licenses theyve generated.
Licenses can be e-mailed
to users directly from the

ProActivate License Manager


application. It also has COM
objects, so that protection
and licensing technology
can optionally be integrated
into existing order-entry and
merchant systems.
For customers requiring even
greater levels of protection,
TurboPower provides the
optional Tumbler Subscription
Plan. Subscribers will
receive quarterly updates
to ProActivates underlying
encryption engine, thereby
reducing the possibility of
software hacking.
TurboPower Software Company
Price: Special introductory price of
US$299. Users of TurboPowers OnGuard
protection library can order ProActivate
for US$199. Licensed users of any other
TurboPower product are eligible for
a 20 percent discount when ordering
ProActivate directly from TurboPower.
Contact: (800) 333-4160
Web Site: www.turbopower.com

RAPWare Releases Easy SOAP 1.1.0


ISBN: 0-672-32341-9
Cover Price: US$49.99
(1,172 pages)
www.samspublishing.com

XML by Example, Second Edition


Benot Marchal
QUE

ISBN: 0-7897-2504-5
Cover Price: US$29.99
(495 pages)
www.quepublishing.com

3 June 2002 Delphi Informant Magazine

RAPWare announced
Easy SOAP 1.1.0, a rapid
application development
environment for creating
Web, thin-client, distributed
applications based on
the SOAP protocol. This
product is designed to
promote Object Pascal
routines to fully-functional
SOAP Web Services, without
the difficult wrapping and
mapping needed by most
other implementations of
SOAP. Easy SOAP creates
these mappings for you, and
you can even use existing
Delphi objects in your Web
Services.
The Easy SOAP Object Pascal
parser locates the routines it
needs to promote to SOAP
Web Services and builds the
Object Pascal source wrappers
automatically. It even creates
the necessary WSDL files
for you. Since you can use
your existing code, you can
start building SOAP-enabled
applications in no time. It gives
you the power to expose back-

end component functionality


as Web Services and also
simplifies the development of
applications that use those Web
Services.
Easy SOAP is completely
based on current standards such
as SOAP, WSDL, and XML,
but hides the complexity of
these technologies from the
developer. This results in a
powerful and intuitive tool that
can improve the productivity of
every Web Services developer,

no matter if youre a beginner


or an experienced developer.
Easy SOAP offers
compatibility with Microsoft
.NET, Apache SOAP, Microsoft
SOAP Toolkit, and other
SOAP toolkit vendors, SOAP
for Delphi 5 and 6, and SOAP
services without a Web server.
RAPWare
Price: US$299 to US$4,499
Contact: info@rapware.com
Web Site: www.rapware.nl

Delphi
T O O L S

New Products
and Solutions

Book Picks
C#: Tips & Techniques
Charles Wright
McGraw-Hill/Osborne

ISBN: 0-07-219379-4
Cover Price: US$49.99
(626 pages)
www.osborne.com

Programming Web
Services with SOAP
James Snell, Doug Tidwell,
& Pavel Kulchenko
OReilly

Pervasive Software Releases Service Pack 4 for Pervasive.SQL 2000i


Pervasive Software, a provider
of embedded and Web database
software that powers businesscritical applications, announced
Service Pack 4 (SP4) for its
high-performance, embeddable
database engine, Pervasive.SQL
2000i. This product provides
full support for Microsoft Windows XP and Novell NetWare
6.0 and clustering technology
for both platforms.
Pervasive.SQL 2000i SP4
expands integration with
Windows XP Professional
and Home operating systems,
providing support for XPs
Fast User Switching feature to

allow increased flexibility in


the deployment model. SP4
also builds on its support for
Microsoft clustering, with stepby-step integration instructions
for low-IT environments.
On the Novell NetWare platform, SP4 solidifies support for
the latest NetWare 6.0 release
and includes support for the
new NetWare Cluster Services.
Support for legacy NetWare
deployments is also enhanced
with NetWare SFT III for true
fail-over support without loss
of connection or state.
Additionally, SP4 includes
SQL syntax enhancements

and improved data type


support, as well as support
for Pervasives latest replication technology, which gives
users bi-directional capabilities with increased speed and
performance. SP4 is available
for download at no cost to all
current Pervasive.SQL 2000i
users from the Pervasive Web
site, and is incorporated into
the updated shrink-wrapped
product.
Pervasive Software
Price: Free to current users.
Contact: (800) 287-4383
Web Site: www.pervasive.com

Anysoft Announces Digital Cortex


Anysoft introduced its
flagship product, the Digital
Cortex system. The Digital
Cortex product line provides a
ubiquitous approach to enable
front-end application enhancement and integration without
requiring access to the underlying application source code or
databases. The Digital Cortex
system non-invasively exposes
Windows-viewable applications
(green screens, Java, desktop,
and Web-based), at run time,
in real time, turning them into
programmable objects. Without
requiring access to source code,
it lays bare an applications data

and functionality, and enables


developers and solution providers to deliver time efficient,
cost-effective integration solutions through development and
deployment systems.
The Digital Cortex technology
solves a broad range of interoperability challenges, including
application integration and
enhancement, automation of
user activities, extension of applications to new delivery models,
creation of software services from
existing applications, and provision of cross application services.
The Digital Cortex product
line consists of development

environment and deployment


configurations, including
Digital Cortex Developer, which
consists of a visual design environment that works in conjunction with popular integrated
development environments such
as Delphi, Visual Basic, Visual
C++, Visual Studio, and PowerBuilder. It uses a set of ActiveX
components, tools, and utilities
that enable you to build and test
Digital Cortex applications.

Status Bar control that acts as


a container capable of holding
nearly any other component
in an unlimited number of
panels; a new XMLStore,
which allows programmers to
save application-specific data
in industry-standard XML
data documents; an improved
Lookout Bar and ReportViews,
which simulate and extend
the look and feel of Microsoft
Outlook; and dozens of additional components, including
calendars, calculators, customizable notebooks, text editors,
analog and digital clocks, outlines, grids, limitless list boxes,
scrolling lists of check boxes,
3-D labels, spinners, state and

MRU management controls,


specialized buttons, and much
more.
Orpheus 4 also comes with
all-new printed documentation in two large volumes, over
1,200 pages total, and full source
code. It supports Delphi and
C++Builder versions 3 and later.

Anysoft Limited Partnership


Price: Starting at US$495

TurboPower Ships Orpheus 4

ISBN: 0-596-00095-2
Cover Price: US$34.95
(244 pages)
www.oreilly.com

4 June 2002 Delphi Informant Magazine

TurboPower announced
Orpheus 4, a major new version
of the companys user interface
components for programmers
using Delphi and C++Builder
compilers. Orpheus 4 has over
120 native VCL components
focused on helping programmers easily create intuitive and
attractive user interfaces for
their programs.
Key features in this release
include new Orpheus Data
Input Validators; new Orpheus
FlexEdit data entry fields with
built-in support for multiline displays, complete border
control, and connections to
Orpheus 4s new Data Input
Validation system; a new

TurboPower Software Company


Price: Orpheus 4 costs US$349. Users
of Orpheus 3.x qualify for a limited-time
upgrade price of US$99. Orpheus 2.x
customers can upgrade for US$199. Users of
any other TurboPower product are eligible for
a 20 percent discount when ordering Orpheus
4 directly from TurboPower.
Contact: (800) 333-4160
Web Site: www.turbopower.com

On the Net
WebSnap / Web Development / Delphi 6

By Nick Hodges

WebSnap at Work
Descending from TAdapter

ebSnap is the new Web-application architecture in Delphi 6. Its a powerful


framework that provides many of the capabilities a Web developer needs to
build dynamic, database-driven Web applications.

type
TnxCustomNewUserAdapter = class(TDefaultFieldsAdapter)
private
FOnValidatePassword: TValidateNewUserItemEvent;
FOnValidateUserName: TValidateNewUserItemEvent;
FOnValidateUserEmail: TValidateNewUserItemEvent;
FOnNewUserIsValid: TNewUserIsValidEvent;
protected
function ImplCanAddFieldClass(AParent: TComponent;
AClass: TClass): Boolean; override;
function ImplCanAddActionClass(AParent: TComponent;
AClass: TClass): Boolean; override;
procedure ImplGetFieldsList(AList: TStrings); override;
procedure ImplGetActionsList(AList: TStrings);
override;
function ExecuteCheckNewUser(aUserName, aUserEMail,
aPassword, aConfirmPassword: string): Boolean;
public
property OnValidatePassword: TValidateNewUserItemEvent
read FOnValidatePassword write FOnValidatePassword;
property OnValidateUserName: TValidateNewUserItemEvent
read FOnValidateUserName write FOnValidateUserName;
property OnValidateUserEmail: TValidateNewUserItemEvent
read FOnValidateUserEmail write FOnValidateUserEmail;
property OnNewUserIsValid: TNewUserIsValidEvent
read FOnNewUserIsValid write FOnNewUserIsValid;
end;
TnxNewUserAdapter = class(TnxCustomNewUserAdapter)
published
property OnValidatePassword;
property OnValidateUserName;
property OnValidateUserEmail;
property OnNewUserIsValid;
// Declared in ancestor.
property Data;
property Actions;
property OnBeforeExecuteAction;
property OnAfterExecuteAction;
property OnBeforeGetActionResponse;
property OnAfterGetActionResponse;
property OnGetActionParams;
end;

Figure 1: Declaration of TnxCustomNewUserAdapter.


5 June 2002 Delphi Informant Magazine

At the heart of WebSnap is the TAdapter class,


which makes it possible to build scriptable
custom components that add functionality to
both Delphi code and HTML. TAdapter is a
generic, flexible class that can be used in almost
any situation in a Web application. Its real power,
however, comes when we build TAdapter descendants that perform a common task. Doing that
makes building Web applications even easier.

Registering New Users


These days, most Web sites want visitors to
register with the site by providing an e-mail
address, a user name, and a password. Youll
probably want such functionality for your own
site, so in this article, well build a custom component, based on TAdapter, that will provide
that functionality quickly and easily. Once weve
built the component, it will contain all the functionality we need to add the proper fields to an
HTML form and to provide custom validation
of users input. Well end up with all the power
of TAdapter in a component that will do exactly
what we need.
Although using TAdapter is quite easy, building
descendants is not as straightforward as regular
component building. There are several issues. First,
well need to decide from where in the hierarchy
we should descend. Then, we need to implement
the actual class. Next, we need to create a class
for each of the specific types of fields we want to
use within the TAdapter. Then we need to link
it all together and properly register the components and classes with the IDE. Well cover each
of these areas as we create TnxNewUserAdapter, a
component that will allow us to gather registration
information from users.

On the Net
Interface

Purpose

IAdapterEditor

Defines functions for the IDE and component editor. Provides the run-time
functionality that allows you to add the
components fields and actions to your
TAdapterPageProducer at design time.

IWebActionsList

Defines functions for adding default


actions to a TAdapter descendant.

IWebFieldsList

Defines the functions for adding default


fields to a TAdapter descendant.

Figure 2: The IAdapterEditor, IWebActionsList, and IWebFieldsList


interfaces.
Event Name

Description

OnValidatePassword

Occurs when the component is


about to check that the password
provided by the registering user is
valid. Component users can validate
the form of the potential new password. For instance, you might want
to make sure all passwords contain
at least six characters.

OnValidateUserName

Occurs when the potential new user


name is validated.

OnValidateUserEmail

Occurs when the potential new e-mail


address provided by the new user is
validated. You can check here to see if
the e-mail address is formed properly.

OnNewUserIsValid

Occurs when a new users information has been validated. The event
passes all the validated information
as parameters, and then you can process the new user as needed. Usually
you will add the new user information
to your user database in this event.

Figure 3: TnxCustomNewUserAdapter events.

Where to Descend?
When building a custom TAdapter descendant, the first issue is finding
the right parent class from which to descend. There are numerous standard WebSnap classes in the hierarchy of the TAdapter class types. Some
deal with normal adapter functionality, and others provide specialty
functionality, such as the ValueList and EndUser components. In this
case, well build a class that will have custom fields and actions. Therefore, well want a class that handles default fields as well as all the other
components that can be part of a TAdapter.
Fortunately, such a class exists: TDefaultFieldsAdapter. Its function is
as its name suggests: Its designed to handle lists of default fields and
actions. For example, the TLoginFormAdapter component descends from
TDefaultFieldsAdapter because it has three default fields and a default
action. Thus, well declare our class as shown in Figure 1.
Note that we declare a custom class with no published properties,
and then we declare the actual class that just publishes the desired
properties. The class descends directly from TDefaultFieldsAdapter
and implements more properly, overrides the existing
implementations of the IAdapterEditor, IWebDataFields, and
IWebActionsList interfaces. These three interfaces perform the
6 June 2002 Delphi Informant Magazine

function TnxCustomNewUserAdapter.ImplCanAddActionClass(
AParent: TComponent; AClass: TClass): Boolean;
begin
Result := inherited ImplCanAddActionClass(
AParent, AClass) or AClass.InheritsFrom(
TnxCustomNewUserSubmitAdapterAction);
end;
function TnxCustomNewUserAdapter.ImplCanAddFieldClass(
AParent: TComponent; AClass: TClass): Boolean;
begin
Result := inherited ImplCanAddFieldClass(
AParent, AClass) or AClass.InheritsFrom(
TnxAdapterNewUserField);
end;

Figure 4: This code allows or disallows a given class of


action or field in the design-time component editor of the
TAdapterPageProducer.
procedure TnxCustomNewUserAdapter.ImplGetActionsList(
AList: TStrings);
begin
AList.Clear;
AList.AddObject(strSubmitNewUserAction,
TObject(TnxNewUserAdapterSubmitAction));
end;
procedure TnxCustomNewUserAdapter.ImplGetFieldsList(
AList: TStrings);
begin
AList.Clear;
AList.AddObject(strUserNameField,
TObject(TnxAdapterUserNameField));
AList.AddObject(strUserEmailField,
TObject(TnxAdapterUserEMailField));
AList.AddObject(strPasswordField,
TObject(TnxAdapterPasswordField));
AList.AddObject(strConfirmPasswordField,
TObject(TnxAdapterConfirmPasswordField));
end;

Figure 5: This code adds the name and a pointer for each of the
default fields and actions.

functions outlined in Figure 2. The class also defines the four events
described in Figure 3.

Implementation
Of course, the real meat of a component is the implementation,
so well examine that next. First, well look at the overridden
implementation of the interfaces. Then well look at how the
events are implemented and called.
The first interface, IAdapterEditor, is implemented as shown in Figure
4. These two functions are similar in that they merely combine the
inherited behavior with a check for proper behavior in the current class.
Their job is to tell the IDE if a given class of action or field can be added
to the current component in the design-time component editor of the
TAdapterPageProducer, i.e. the Web Surface Designer. In this case, the
acceptable classes are any classes the parent class accepts, or any fields
or actions that descend from the fields and actions that are part of the
TnxNewUserAdapter. In other words, these methods ensure that only the
right fields and actions are added to the TAdapter at design time.
Next, the IWebActionsList and the IWebFieldsList interfaces
are implemented as shown in Figure 5. This code is simple.
TDefaultFieldsAdapter maintains lists simple TStrings of

On the Net
the default fields for itself, and this code merely adds the name
and a pointer to the class for each of the default fields and actions
well add to the component. Well get to these classes later.

Implementing the Events


The events listed in Figure 3 provide the most useful functionality of our new component. They allow us to verify and validate
the information the new user is entering, and to enforce rules for
user names, e-mail addresses, and passwords. All the work actually
occurs in the ExecuteCheckNewUser method, which is shown in
Listing One (on page 9).
This code goes through a series of steps to validate the information the new user has entered. First, the code checks to see if
the name is blank, sets a default reason for invalidating the user
name, and then calls the OnValidateUserName event handler if
one is assigned. The default information is there in case no event
handler is assigned, and in case the user name is blank. (In other
words, the component wont allow a blank user name by default.)
OnValidateUserName is of the TValidateNewUserItemEvent type,
which is declared like this:
type
TValidateNewUserItemEvent = procedure(Sender: TObject;
aValue: string; var IsValid: Boolean;
var aReason: string) of object;

The event gives you the chance to check out the information passed by
the new user, and either validate it or invalidate it, and pass a reason back
to the TnxNewUserAdapter for display to the user.
Once the event has occurred, the code checks to see if the event has
decided the input is invalid. If it has, the code adds the reason you gave
to the errors list for the component. If there are any errors in the list
due to the user submitting the page, the page is shown again and the
errors are displayed on the page if the page has a TAdapterErrorList
on it attached to the current adapter. This is why its good to add a
TAdapterErrorList to each TAdapterForm in your page.
Then, the code basically does the same thing for the new users
e-mail address and password. The code also checks to ensure the
two passwords given in the Password and ConfirmPassword fields
are the same. If not, an error is raised.
Finally, if the user name, password, and e-mail address all have
been validated, the code calls the OnNewUserIsValid event (if a
handler is present), and passes all the new information as parameters. In this event, we can add the new user to our databases or
wherever we store that information.
The ExecuteCheckNewUser method we just looked at is nothing
special. Its just an event we added to encapsulate the functionality of
checking the new users information. Well call it manually when we
get around to implementing the fields and actions mentioned earlier.

Implementing the Fields and Actions


Weve talked a couple of times about the default fields and actions
well be using in the TnxNewUserAdapter component, and weve
even seen some of them mentioned in the code. Its finally time to
discuss and implement them.
Of course, the fields and actions are the real purpose behind
building the component; theyre what give the new TAdapter
7 June 2002 Delphi Informant Magazine

type
TnxAdapterNewUserField = class(TAdapterNamedDisplayField)
private
function GetAdapter: TnxCustomNewUserAdapter;
protected
property Adapter: TnxCustomNewUserAdapter
read GetAdapter;
published
property FieldName;
property DisplayLabel;
property DisplayWidth;
end;

Figure 6: The TnxAdapterNewUserField declaration.


function TnxAdapterNewUserField.GetAdapter:
TnxCustomNewUserAdapter;
begin
if (inherited Adapter <> nil) and
(inherited Adapter is TnxCustomNewUserAdapter) then
Result := TnxCustomNewUserAdapter(inherited Adapter)
else
begin
Result := nil;
Assert(False); // NewUserAdapter not found.
end;
end;

Figure 7: This code restricts the class and all descendants to


TnxCustomNewUserAdapter.

descendant its functionality. Just as with TDefaultFieldsAdapter,


theres a special class for descending default fields. Its named
TAdapterNamedDisplayField and implements all the functionality
needed to be a default field in a TDefaultFieldsAdapter. It also lets
you enhance that functionality in descendants, which is exactly
what well do.
Doing so requires two levels of descendants. First, we need to
declare a parent class for all of our fields that knows about the type
of adapter that will contain it. In addition, the class must be able
to determine the specific type of the adapter that contains it. In our
case, that adapter will be of type TnxCustomNewUserAdapter. Thus,
well declare the TnxAdapterNewUserField class as shown in Figure 6.
This class exposes three published properties, but, more importantly,
it overrides the GetAdapter method and provides the Adapter
property, which is of the type we need.
The GetAdapter function is shown in Figure 7. This code merely
grabs the parent adapter and returns a pointer to it, if it is of the
right type. If it cant find the proper adapter, it returns nil. In
other words, this class and all its descendants will always have
a TnxCustomNewUserAdapter as an adapter, or nothing at all.
That is important because all the fields will be operating on the
assumption that their adapter is a TnxCustomNewUserAdapter,
and, if it werent, the code would crash miserably.
From that class, we descend TnxAdapterNewUserPasswordField, which
will create itself as a password input HTML control to hide any input
with asterisks. It looks like this:
TnxAdapterNewUserPasswordField =
class(TnxAdapterNewUserField)
protected
function GetInputStyleType(const AAdapterMode: string):
TAdapterInputHTMLElementType; override;
end;

On the Net
TnxAdapterUserNameField = class(TnxAdapterNewUserField)
protected
function GetDefaultFieldName: string; override;
{ IWebGetFieldValue }
function ImplGetValue: Variant; override;
end;
TnxAdapterUserEMailField = class(TnxAdapterNewUserField)
protected
function GetDefaultFieldName: string; override;
{ IWebGetFieldValue }
function ImplGetValue: Variant; override;
end;
TnxAdapterPasswordField =
class(TnxAdapterNewUserPasswordField)
protected
function GetDefaultFieldName: string; override;
{ IWebGetFieldValue }
function ImplGetValue: Variant; override;
end;
TnxAdapterConfirmPasswordField =
class(TnxAdapterNewUserPasswordField)
protected
function GetDefaultFieldName: string; override;
{ IWebGetFieldValue }
function ImplGetValue: Variant; override;
end;

Figure 8: Classes that descend from TnxAdapterNewUserField and


TnxAdapterNewUserPasswordField.
function TnxAdapterUserNameField.ImplGetValue: Variant;
begin
Result := '';
if Adapter <> nil then
if WebContext <> nil then
Result := WebContext.Session.Values[FieldName];
end;

Figure 9: ImplGetValue returns values stored in the session variable.

The GetInputStyleType function is overridden to return the


htmliPasswordInput value for the control, and tell it to build the
HTML so that the control is a password control, which will put
asterisks in the place of user typing.
From there, we declare four classes: two that descend from
TnxAdapterNewUserField and two from TnxAdapterNewUserPasswordField,
as shown in Figure 8. These classes are declared and implemented in basically the same way, so well look only at the implementation of the first,
TnxAdapterUserNameField.

TnxNewUserAdapterSubmitAction =
class(TnxCustomNewUserSubmitAdapterAction)
protected
procedure ImplExecuteActionRequest(AActionRequest:
IActionRequest; AActionResponse: IActionResponse);
override;
function GetDefaultActionName: string; override;
published
property DisplayLabel;
property OnBeforeExecute;
property OnAfterExecute;
property OnBeforeGetResponse;
property OnAfterGetResponse;
property OnGetParams;
end;

Figure 10: Declaring the action class.

tion submitted, and the form is redisplayed. The new user name
and e-mail address will be saved and displayed, but the password
information will not. As a result, the ImplGetValue method for
the password fields simply returns an empty string.
Next, well implement a single default action for the new TAdapter. It
will process the information as a normal HTML POST event. First,
well create a class similar to the TnxAdapterNewUserField: the
TnxCustomNewUserSubmitAdapterAction class. It has the
same function and basically the same implementation as the
TnxAdapterNewUserField class did: to find and retrieve the correct
owning adapter. Thus, its declared like this:
TnxCustomNewUserSubmitAdapterAction =
class(TImplementedAdapterAction)
private
function GetAdapter: TnxCustomNewUserAdapter;
protected
property Adapter: TnxCustomNewUserAdapter
read GetAdapter;
end;

From there, we declare the actual action well use as the


TnxNewUserAdapter class (see Figure 10). This class publishes some
needed properties from parent components and then overrides
two methods. GetDefaultActionName does the same thing as
GetDefaultFieldName. It returns a string with the default name for
the action. The ImplExecuteActionRequest method is the important
one here because its called when the user presses the button in the
HTML form. Thus, it does all the work of executing the action in
the component. Its implemented in Listing Two (on page 10).

The GetDefaultFieldName function simply returns a string value


that defines the default field name value for the field. These values
are defined in the interface section of the unit and are strings that
match the field names. The most important method here is the
ImplGetValue function; its implementation is shown in Figure 9.

Listing Two contains a lot of code, but its fairly straightforward. First,
it grabs the submitted value for each of the fields. It does that via the
IActionFieldValue interface, which it gets from AActionRequest parameter
via the call to Supports. The IActionFieldValue is an interface that, when
implemented, holds all the information about the fields in an HTML
POST request. Thus, it contains all the values from the controls on the
HTML form submitted by the user, so we can grab that information
with a call to the ValueOfField method for that interface.

First, the code assumes there is no value. But, if the Adapter property and the WebContext variable are both assigned to something,
the form will return the value stored in the session variable. The
fields are designed so the UserName and UserEmail values are
retained between page requests, but so that the password information is not retained. This means the new users password wont
be sent out over the Internet if there is an error in the informa-

We do that for each of the four fields by placing the values in local
variables as we go, and setting them to empty strings if nothing is
sent for a particular value. Once weve done that, we simply call
the ExecuteCheckNewUser method from the adapter, which you
saw above and which verifies the entries and lets you enter them
into your user database. Note that this is an example of using the
Adapter property for these default fields and actions. We know

8 June 2002 Delphi Informant Magazine

Delphi Informant Magazine June 2002 8

On the Net
unit nxNewUserAdapterReg;
interface
procedure Register;
implementation
uses WebComp, nxNewUserAdapter, Classes;
procedure Register;
begin
RegisterWebComponents([TnxAdapterUserNameField]);
RegisterWebComponents([TnxAdapterUserEMailField]);
RegisterWebComponents([TnxAdapterPasswordField]);
RegisterWebComponents([TnxAdapterConfirmPasswordField]);
RegisterWebComponents([TnxNewUserAdapterSubmitAction]);
RegisterComponents('WebSnap', [TnxNewUserAdapter]);
end;
initialization
finalization
UnRegisterWebComponents([TnxAdapterUserNameField]);
UnRegisterWebComponents([TnxAdapterUserEMailField]);
UnRegisterWebComponents([TnxAdapterPasswordField]);
UnRegisterWebComponents(
[TnxAdapterConfirmPasswordField]);
UnRegisterWebComponents([TnxNewUserAdapterSubmitAction]);
end.

Figure 11: Registering the components.

from the GetAdapter function that the Adapter property will hold
an adapter of the proper type, and, as long as it isnt nil, we can call
the ExecuteCheckNewUser method safely.
Note the UserName and UserEmail values, once retrieved, are
stored in the Session.Values property using their names as values.
We saw earlier that these values will be saved between requests,
but that the users password wont be. This is how thats done.

Registering the Components


Of course, as with all components, you cant use
TnxNewUserAdapter until it and its fields and actions are properly
registered in the IDE. You do that with the unit shown in Figure
11. This is fairly standard code, but there are a few things to
note. First, every call to RegisterWebComponents in the Register
procedure requires a call to UnRegisterWebComponents in the
finalization section of the unit.
Second, to keep the IDE happy, always register your fields and
actions before registering the TAdapter descendant that will use them.
If you add this unit to a design-time package and compile it, the new
TnxNewUserAdapter component will appear on the WebSnap page of
the Component palette, and its fields and actions will be available for
use in the Web Surface Designer of the TAdapterPageProducer.
The code accompanying this article has a simple application that
uses the TnxNewUserAdapter component and does some simple
validation on the input. The code illustrates how to use the new
component weve built.

Conclusion
In this article, we built a customized TAdapter-based component that
provides the controls needed to register a new user on a Web site. We
saw how to descend from TDefaultFieldsAdapter to add default fields
to the component, and we saw how to implement the default fields
9 June 2002 Delphi Informant Magazine

and actions that add functionality to the new component. Finally, we


looked at the proper way to register these new classes with the IDE.
Building custom TAdapters isnt the simplest thing in the world,
and there are a few tricks you need to know. Once you start building custom TAdapter descendants, however, your productivity will
skyrocket when building Web applications.
The example project referenced in this article is available for
download on the Delphi Informant Complete Works CD located in
INFORM\2002\JUN\DI200206NH.

Nick Hodges is president of HardThink Inc., a Delphi consulting company. He also is


a member of Borlands TeamB. Nick lives with his family in St. Paul, MN. A frequent
speaker at Borland Conferences, Nick is a member of the Borland Conference Advisory
Board this year. Readers may reach him at nick@hardthink.com.

Begin Listing One ExecuteCheckNewUser


function TnxCustomNewUserAdapter.ExecuteCheckNewUser(
aUserName, aUserEmail, aPassword, aConfirmPassword:
string): Boolean;
var
UserNameIsValid,
PasswordIsValid,
EmailIsValid: Boolean;
aReason: string;
begin
Result := False;
// Validate UserName.
UserNameIsValid := aUserName <> '';
// Default reason for failure.
aReason := 'Your UserName is invalid';
if Assigned(FOnValidateUserName) then
FOnValidateUserName(Self, aUserName,
UserNameIsValid, aReason);
if not UserNameIsValid then
Errors.AddError(aReason);
// Validate e-mail.
EmailIsValid := aUserEmail <> '';
aReason := 'Your email address is not valid';
if Assigned(FOnValidateUserEmail) then
FOnValidateUserEmail(Self, aUserEmail,
EmailIsValid, aReason);
if not EmailIsValid then
Errors.AddError(aReason);
// Validate password.
if aPassword <> aConfirmPassword then
Errors.AddError(
'The two passwords you entered must match');
else
begin
PasswordIsValid := aPassword <> '';
// Default reason for failure.
aReason := 'Your password is invalid';
if Assigned(FOnValidatePassword) then
FOnValidatePassword(Self, aPassword,
PasswordIsValid, aReason);
if not PasswordIsValid then
Errors.AddError(aReason);
end;
if PasswordIsValid and
EmailIsValid and
UserNameIsValid and
Assigned(FOnNewUserIsValid) then
FOnNewUserIsValid(Self, aUserName,
aUserEmail, aPassword);
end;

End Listing One

On the Net
Begin Listing Two ImplExecuteActionRequest
procedure TnxNewUserAdapterSubmitAction.
ImplExecuteActionRequest(AActionRequest: IActionRequest;
AActionResponse: IActionResponse);
var
Value: IActionFieldValue;
ActionFieldValues: IActionFieldValues;
aUserName, aUserEmail, aPassword,
aConfirmPassword: string;
begin
inherited;
if Supports(AActionRequest, IActionFieldValues,
ActionFieldValues) then begin
Value :=
ActionFieldValues.ValueOfField(strUserNameField);
if Value <> nil then
aUserName := Value.Values[0];
else
aUserName := '';
// Only save the UserName in the session. If we
// return to the page, don't send the potential
// password the user entered back to the browser.
WebContext.Session.Values[Value.FieldName] :=
aUserName;
Value :=
ActionFieldValues.ValueOfField(strUserEmailField);
if Value <> nil then
aUserEmail := Value.Values[0];
else
aUserEmail := '';
WebContext.Session.Values[Value.FieldName] :=
aUserEmail;
Value :=
ActionFieldValues.ValueOfField(strPasswordField);
if Value <> nil then
aPassword := Value.Values[0];
else
aPassword := '';
Value := ActionFieldValues.ValueOfField(
strConfirmPasswordField);
if Value <> nil then
aConfirmPassword := Value.Values[0];
else
aConfirmPassword := '';
if Adapter <> nil then
Adapter.ExecuteCheckNewUser(aUserName, aUserEmail,
aPassword, aConfirmPassword);
end;
end;

End Listing Two

10 June 2002 Delphi Informant Magazine

Greater Delphi
Microsoft Word / COM / Automation / Delphi 3-6

By Jason Sweby

Word Merge
Automating Microsoft Word Mail Merge

n his series on Word automation in the September and October 2000 editions of Delphi
Informant Magazine, Ron Gray covered the basics of incorporating Microsoft Words
functionality into your projects using COM technology. COM is a language-independent,
software-component model designed to enable interaction between software components and applications. It enables developers to use other software tools in their own
programs. Although its not initially as intuitive as standard Delphi components, when
used correctly, COM can add powerful capabilities to your applications.

11 June 2002 Delphi Informant Magazine

As Gray described, there are two ways to integrate


a COM server in Delphi. (The COM server is
the application that exposes its functionality for
client applications.) The first way is known as late
binding, in which Delphi assumes no responsibility
for the code youre providing, and all the checking
is performed at run time. The second way, early
binding, involves importing the type library for the
specific program to which you are trying to connect
(or, in the case of Delphi 5/6 and the Microsoft
Office suite, using the standard server components
supplied). Early binding provides the advantage of
activating Delphis Code Insight and Code Completion tools. Or, if its an ActiveX control, early binding
provides a Delphi component to do the work for
you. This approach is known as early binding
because the compiler can pick up the usual errors,
warnings, and hints you normally would expect.
But, as I will describe later, what appears to be an
enormous benefit also can have its drawbacks.

from this article should teach you enough to pick


up any of the tools Word provides (not exclusively
the mail merge), as well as those any other software
with a published COM interface provides, and
integrate them seamlessly into your own projects.
To make use of this article, you should have Delphi
3 or later and Microsoft Word 97 or later.

This article will extend the basics of using Words


COM interface to bring a more specific utility to
your programs: mail merging, taking the information held on your system to produce standard
letters and documents automatically. Ill cover
the essentials briefly, so youll get a step-by-step
picture of how this will develop. But, if you have
not already done so, I strongly recommend reading
Grays two-part series to familiarize yourself with
the techniques described here. What you learn

As the Delphi help explains, Clients [i.e. Delphi] do


not know how a COM object performs its service;
the objects implementation remains encapsulated.
Therefore, any errors in the code (e.g. a misspelled
enumerated type or method) will result in errors in
the program, because they wont be picked up at
design time. However, with a little tip shown later, all
the properties, methods, and parameters required to
perform any task in Microsoft Word can appear on
the screen in front of you.

Early or Late?
Let me explain why I will use the late-binding
automation approach in this article. You can communicate with a COM server using one of two
techniques. The technique that has been used for the
past few years involves using Delphis OLE Automation unit to create a manual link to a COM object as
an OLE variant type, using the published program
ID (see Figure 1). Using this method, you have none
of Delphis Code Insight available to you, and its the
responsibility of the COM client application to call
the available objects and methods correctly.

Greater Delphi
uses ComObj;
procedure TForm1.Button1Click(Sender: TObject));
var
oleWord: OleVariant;
begin
try
oleWord := CreateOLEObject('word.application');
except
on E: Exception do begin
E.Message := 'Microsoft Word is unavailable'.
Raise;
end;
end;
Screen.Cursor := crHourGlass;
Application.ProcessMessages;
oleWord.Quit;
Screen.Cursor := crDefault;
{ This is the standard Delphi way of clearing up the
instance. The VBA equivalent is oleWord = Nothing; }
oleWord := UnAssigned;
end;

Figure 1: Using OLE Automation to create and close a link to


Microsoft Word via the COM server.

As mentioned earlier, the second method would appear to be the


perfect solution if you dont know the object model of a COM
server. But theres a downside to using early binding. Most of the
method calls in the COM language take numerous parameters so
the method can be performed to meet your needs more accurately,
and a greater number of these parameters are optional. However,
the class instantiated by this new approach does not cater to this:
None of the methods are overloaded to allow for more flexibility,
so you have to supply all parameters, and that can be impractical. This is especially true because almost all have to be passed
as variable parameters, meaning you need to create a variable of
type OLEVariant for each parameter in the list. The Delphi 5/6
server components help to ease this issue by overloading some of
the methods, but overloading only works if you need to pass just
the first one or two parameters. It doesnt allow you to provide the
first and last parameters, for example, as the late-binding method
does. (I will demonstrate that later.)
Because Im taking this into account, and because I know how many
optional parameters will need to be passed for our mail-merge project
(and others along the way), this article will stick with the late-binding
method. But I will use an imported type library, as well, so we can
use the enumerated types and constants declared within it. (Delphi
5/6 users dont need to add the type library because the Word97.pas
file is already installed as standard.) This way, the techniques used
here can be applied to other non-Microsoft Office tools and arent
restricted to Delphi 5/6 users.

Making Macros Work


One of the many under-used utilities in Microsoft Word is the
Macro Recorder that can be found under its Tools menu. This tool
will capture a series of mouse clicks and keystrokes in Visual Basic for
Applications (VBA) for the duration of the recording phase, so they
can re-run as a single command, or macro, in the future. Included in
versions of Word, beginning with Word 97, is a Visual Basic Editor
(VBE) a user can invoke by pressing A!, or selecting Tools |
Macros | Visual Basic Editor. Combining these two tools provides a
powerful feature to the COM client developer, and is an easy way
to identify the properties and methods needed to perform a series of
Word functions from our programs.
Figure 2: Using the server components in Delphi 5/6, or installing the COM servers type library, means you can use the Code
Completion and Code Insight tools.

Figure 3: Words Macro Recorder tool window.

The second method, early binding, is provided as standard for the


Microsoft Office suite with Delphi 5/6 on the Server tab of the Component palette. This technique involves making the type librarys
source code of the COM server available to Delphi, which makes
Delphi aware of all the objects, their hierarchy, their properties, and
methods, so you can use them like a standard Delphi component (see
Figure 2). Once installed, you still can use the late-binding approach,
but a new set of methods made available by the type library allow for
a COM client to be initiated as if it were a standard Delphi type, and
Delphi can now perform its standard error checking. (Again, Grays
articles provide further information on using this approach.)
12 June 2002 Delphi Informant Magazine

Even for applications in which this facility is not available, there should
be some documentation on the COM object hierarchy. For Microsoft
Word, its in the Microsoft Word Visual Basic help file (vbawrd8.hlp
for Word 97 users). For Microsoft Access, you can find the hierarchy in
the normal help file (under the DBEngine Object section).
Lets look at an example of how to use this, through a simple findand-replace operation. Open Word and any document. Click the
Tools menu and select Macros | Record New Macro. Call the macro
FindAndReplace and press OK. The macro is now in record mode,
as shown by the tape-recorder controls that appear in a toolbar on
the screen (see Figure 3). Everything you do until you click the Stop
button (on the left) will be recorded into the macro.
Select Edit | Replace. In the Find What edit box, enter My Text. In
the Replace With box, enter My New Text. Then, click the Replace All
button. Whether or not any text was replaced, click the Stop control
button in the Macro Recorder tool window (or select Tools | Macro
| Stop Recording). The macro has now been recorded and saved. To
see the code that was generated, open the VBE. Figure 4 shows the
result. (Thanks to my colleague Richard Quadling for enlightening
me on this matter.)

Greater Delphi
Sub FindAndReplace()
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "My Text"
.Replacement.Text = "My New Text"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
End Sub

The second procedure uses late binding, with which Delphi will not
try to syntax-check the properties, methods, and parameters you use
for a COM object. Note that you cannot use the with statement
around the object; its not a Delphi object, but rather a variable of
type variant. Now, however, you can set any or all of the properties of the Find object in the standard way. And, when you call the
Find.Execute method, only the parameters you want to pass need to
be included. There are three different ways of passing an optional
number of parameters, as you can see here in the Document.SaveAs
method:
expression.SaveAs(FileName, FileFormat, LockComments,
Password, AddToRecentFiles, WritePassword,
ReadOnlyRecommended, EmbedTrueTypeFonts,
SaveNativePictureFormat, SaveFormsData, SaveAsAOCELetter)

Figure 4: This Visual Basic code generated by the Macro


Recorder implements Find and Replace. Note that all the options
we have not specified are shown with their default values.

If you want to pass the first parameter only, supply it as normal:

Words VBE provides all available properties of an object with


a value, even if they are the default values. It cannot harm your
application to provide the same, and, in fact, it may be advantageous
because then you can see what the default values are, and what other
properties are available for you to explore.

Or you can name the parameters you are using and supply values:

Determining the Delphi Approach


As an example of a Word COM function that takes optional parameters,
heres how the Execute method is declared for the Find object in Word:
expression.Execute(FindText, MatchCase, MatchWholeWord,
MatchWildCards, MatchSoundsLike, MatchAllWordForms,
Forward, Wrap, Format, ReplaceWith, Replace)

Thats a lot of parameters! I can think of no standard Delphi


method that uses so many. Furthermore, every one of these parameters is optional. This is a good thing in that we shouldnt have to
pass the arguments we dont need. But if you are using the type
library early-binding approach or Delphi 5/6s TWordApplication
component (WordApplication1.Selection.Find.Execute), youll have
to supply all the parameters to this method. Thats because the
method has not been overloaded to deal with a changeable number
of arguments. On top of that, all of the parameters you would
have to supply are variables, so an OleVariant variable has to be
declared for each parameter.
Listing One (beginning on page 15), shows the difference between the
two approaches. The Button1Click procedure uses the early-binding
approach and the Delphi 5/6 WordApplication and WordDocument
components, with all the required parameters. Button2s OnClick event
handler shows how to perform this using late binding.
The main difference between these two methods is the volume of
information required. Early binding requires all the parameters to
be passed to the Documents.Open and Find.Execute methods, and
they all need to be supplied as variables because of the way the
procedures are declared in the type library. This involves declaring a variable and setting its value for each of the parameters. As
shown in Listing One, you simply can pass the value EmptyParam
(a reserved Delphi variable) instead of passing a variable that will
force Word to use the parameters default value. This is fine if you
know what the default is and want to use it. But you will need a
variable if you dont.
13 June 2002 Delphi Informant Magazine

oWord.ActiveDocument.SaveAs(sFilename);

oWord.ActiveDocument.SaveAs(Filename := sFilename,
AddtoRecentFiles := True);

Or you can leave certain parameters blank, but still provide them all:
oWord.ActiveDocument.SaveAs(sFilename,,,,True,,,,,,,);

Having the optional-parameters feature available merits use of the


late-binding approach. You may differ on this, and there is no right
or wrong. Those wishing to make the most of the facilities provided
by early binding can use it. The properties and methods are the same,
and an example is provided in the article files that run the mail merge
using the Delphi 5/6 server components.

Mail Merge
So far, weve established how to get the objects from the COM server
and begin setting their properties and using their methods. Weve also
established how to pass just the parameters you need. Now, we will put
this information to good use. Well build an automated mail-merging
tool from our own application by using Words COM objects.
Before starting, we need to have a Word document containing
mail-merge fields and a data source that contains the data to merge.
A range of data-source types are available to be imported into a document, including a text document, database fields, or a SQL statement
linking to a database. For ease of use, well use a comma-separatedvalues (CSV) file containing a list of fields and data. This file is
available along with a mail-merge template. (See details at the end of
the article.) The document template is in Word 95 format to provide
as much compatibility as possible.
I wont go into the specifics of setting up a mail-merge letter. Suffice
it to say you only really need to insert MergeField type fields into
your documents with the field names correlating to the field names
in the data source. Microsoft Word provides a great deal of help on
this subject.
If you take a look at the CSV file to be used as the data source
(Figure 5 shows the first few fields from the text file given as an
example), you will notice the first line contains the field names that
appear in the file, in the sequence in which they appear. In doing

Greater Delphi
this, you dont need to make any more effort
in matching up fields in the data source to the
merge fields on the document. Word will pair
them for you.

Title, Forename_1, Forename_2, Surname, Address1, Address2, Postcode


"Mr","Jason","Peter","Sweby","1 St Andrews Way","","Plymouth","Devon","PL2 3YZ"

Figure 5: A snapshot of the example datasource used in the mail merge project.

Start a new project in Delphi and add the


ComObj unit to the uses clause. Place two buttons and a OpenDialog
component on the form and save the project. Because were using late
binding, we dont need to declare so many variables, just those for the
different Word COM objects well be using and one for the file name
of the mail-merge-document template. Enter the code as shown in
Figure 1 for Button1s OnClick event handler.
Whats been created here is an object that provides access to the
Word application. According to the Word COM, a child property of
the Application object is the Document object. Furthermore, an object
named MailMerge is a child of the Document object (see Figure 6).
Its this latter object that provides the gateway to the mail-merging
functionality of Word.
For each COM object, the object hierarchy should give you the properties and methods just as you would expect to see in Delphis help
for a given component. Microsoft Word provides excellent help on its
object model, although you wont necessarily need to work out which
properties and methods are required because you can use the Macro
Recorder to tell you. Open the Word template you have created (or
use the mock Job Offer template made available with this article),
and follow the steps described earlier to get the Macro Recorder in
record mode.
Next, select Tools | Mail Merge to start the Mail Merge Helper. Step 1
sets up the mail-merge source document. Press the Create button and
select Form Letters from the drop-down box. Word asks for the document or template that will contain the mail-merge fields, so select
Active Window, which contains the previously opened template. Step 2
sets up the data source for the merge fields. Press the Get Data button
and choose Open Data Source from the list. Navigate to and open the
text file containing the merge data. With the text document selected
as the data source, pressing the Edit button will illustrate how the field
names in the first line of the file have been used to give each field in
the text file an identity.
The final step is to perform the merge. Press the Merge button on the
Mail Merge Helper screen, followed by another Merge button after
some options. Whether or not anything merged, the commands have
been issued, so click the Stop button on the Macro Recorder. Open
the VBE and you will see code similar to that shown in Figure 7
(depending on which version of Word is installed).
ActiveDocument and MailMerge are both objects of type Document and
MailMerge, respectively. The properties, parameters, and methods in
the macro will perform the same task as the menu and dialog options
pressed to create it. Those we did not set in building up the macro are
default values Word has added. And, although they dont need to be
used in the COM client, its best to include them to ensure the results
are as expected. In addition, they can be altered more easily if they are
in place should your project need to provide more direct control over
the methods. Many of the properties take an enumerated type as their
value, and these will require you to add the type library to the project
if you want to use them.
Delphis Execute method doesnt use a series of parameters. Instead, it
uses the properties of its object to determine how to function. The same
14 June 2002 Delphi Informant Magazine

Application

Dictionaries (Dictionary)

Documents (Document)

MailMerge

MailMergeDataSource

MailMergeFields (MailMergeField)

FileConverters (FileConverter)
Figure 6: The hierarchy of the MailMerge object in Word.

Sub MailMerge()
ActiveDocument.MailMerge.MainDocumentType = wdFormLetters
ActiveDocument.MailMerge.OpenDataSource Name:= _
"datasource1.txt", ConfirmConversions:=False, _
ReadOnly:= False, LinkToSource:=True, _
AddToRecentFiles:=False, PasswordDocument:="", _
PasswordTemplate:="", WritePasswordDocument:="", _
WritePasswordTemplate:="", Revert:=False, _
Format:=wdOpenFormatAuto, Connection:="", _
SQLStatement:="", SQLStatement1:=""
With ActiveDocument.MailMerge
.Destination = wdSendToNewDocument
.MailAsAttachment = False
.MailAddressFieldName = ""
.MailSubject = ""
.SuppressBlankLines = True
With .DataSource
.FirstRecord = wdDefaultFirstRecord
.LastRecord = wdDefaultLastRecord
End With
.Execute Pause:=True
End With
End Sub

Figure 7: The mail-merge setup and execution in a Word macro.

is true with COM objects: The MailMerge properties are set before the
Execute method is called. Theres no overhead in setting them all. Ironically, the MailMerge Execute method takes a parameter, but it doesnt
affect what the method does. The parameter controls Words behavior
while its processing, as explained in the Word VBA help file.
Simulate the code generated by the Macro Recorder in the Delphi project by adding the objects, properties, and methods in the OnClick event
handler for Button2 as shown in Listing Two (on page 16). What youve
achieved is an exact replica of the Word mail-merge facility in Delphi.
This project is an example of a COM client you can adapt to perform
dozens of other Word operations, such as spell checking, converting
documents, or editing global documents.
The code in this article has been tested with the recently released
Microsoft Office XP, and all versions of Word between XP and Word
97. The steps outlined earlier, which build up the mail merge, have
been slightly altered in Word XP (see Figure 8). The process is now
easier because of the Mail Merge Wizard shown in Figure 9. The

Greater Delphi
Begin Listing One Early Binding vs. Late Binding
unit Word;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ComObj, Word97, OleServer;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
oleWord: TWordApplication;
oleWordDoc: TWordDocument;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
end;

Figure 8: Mail merging in Word XP is located under the new


Letters and Mailings menu.

var
Form1: TForm1;
implementation
{$R *.DFM}

Figure 9: Office XPs Mail Merge Wizard is on the right side


of the screen. When a data source has been selected, Word
prompts the user to ensure the fields and field names match.

Visual Basic code, however, is identical. With this in mind, you can
be reasonably sure any work you do with Microsoft Word will work
across most platforms.
You can find an example of the same application using Delphi 5/6s
TWordApplication Server component in the accompanying files.
The projects and supporting files referenced in this article are available on the Delphi Informant Magazine Complete Works CD located
in INFORM\2002\JUN\DI200206JS.

Jason Sweby graduated from the University of Manchester Institute of Science and
Technology in Manchester, England, with a joint-honors degree in computation and
French. He is now a Windows software developer at Carval Computing. The company,
a provider of human-resources systems in Plymouth, England, works with payroll,
personnel, and time-and-attendance projects. You can often find Jason wandering the
Delphi topic area at the Experts Exchange (http://www.experts-exchange.com). You
may contact Carval Computing via the companys Web site at http://www.carval.co.uk
or write to Jason at jason.sweby@carval.co.uk.

15 June 2002 Delphi Informant Magazine

// Early binding.
procedure TForm1.Button1Click(Sender: TObject);
var
FindText, ReplaceText, sFilename, oleType,
PreserveFormatting: OleVariant;
bMatchCase, bMatchWholeWord, bMatchWildCards,
bMatchSoundsLike, bMatchAllWordForms, bForward, bWrap,
bFormat, bReplaceWith, bReplace, bSaveChanges:
OleVariant;
begin
sFilename := 'c:\my documents\mydoc1.doc';
{ If connection fails, Word is not available. }
try
oleWord.Connect;
except
on E: Exception do begin
E.Message := 'Word is not available.';
Raise;
end;
end;
with oleWord do begin
Documents.Open(sFilename, EmptyParam, EmptyParam,
EmptyParam, EmptyParam, EmptyParam, EmptyParam,
EmptyParam, EmptyParam, EmptyParam);
FindText := 'My Text';
ReplacementText := 'My New Text';
with Selection.Find do begin
ClearFormatting;
{ These properties do not necessarily need to be set
here. They are only included to show how the code
can be copied from the Macro code. }
Text := FindText;
Replacement.Text := ReplacementText;
Forward := True;
Wrap := wdFindContinue;
Format := False;
MatchCase := False;
MatchWholeWord := False;
MatchWildcards := False;
MatchSoundsLike := False;
MatchAllWordForms := False;
{ These are the variables that will be passed to the
Execute method. These DO need to be set. }
bReplaceWith := Replacement.Text;
bForward := Forward;
bWrap := Wrap;

Greater Delphi
bFormat := Format;
bMatchCase := MatchCase;
bMatchWholeWord := MatchWholeWord;
bMatchWildcards := MatchWildcards;
bMatchSoundsLike := MatchSoundsLike;
bMatchAllWordForms := MatchAllWordForms;
{ This is an extra parameter of the Execute method
that is not a property of the Find object. }
bReplace := wdReplaceNone;
{ Now call the Execute method, having to pass all
the parameters. }
Execute(FindText, bMatchCase, bMatchWholeWord,
bMatchWildCards, bMatchSoundsLike,
bMatchAllWordForms, bForward, bWrap, bFormat,
bReplaceWith, bReplace);
end;
end;
{ Save the document with the new text. }
with oleWordDoc do begin
ConnectTo(oleWord.ActiveDocument);
SaveAs(sFilename);
bSaveChanges := False;
Close(bSaveChanges);
Disconnect;
end;
oleWord.Disconnect;
end;
// Late binding.
procedure TForm1.Button2Click(Sender: TObject);
var
oWord: OLEVariant;
sFilename, FindText, ReplaceText: string;
begin
sFilename := 'c:\my documents\mydoc1.doc';
try
oWord := CreateOLEObject('word.application');
except
on E: Exception do begin
E.Message := 'Word is not available.';
Raise;
end;
end;
try
{ Word isn't visible by default, so you can run it
without the user knowing. }
oWord.Visible := False;
oWord.Documents.Open(sFilename);
{ This method cannot be placed in a with statement,
because it is an OLEVariant, not an object. }
oWord.Selection.Find.ClearFormatting;
oWord.Selection.Find.Text := 'My Text';
oWord.Selection.Find.Replacement.Text := 'My New Text';
oWord.Selection.Find.Forward := True;
oWord.Selection.Find.Wrap := wdFindContinue;
oWord.Selection.Find.Format := False;
oWord.Selection.Find.MatchCase := False;
oWord.Selection.Find.MatchWholeWord := False;
oWord.Selection.Find.MatchWildcards := False;
oWord.Selection.Find.MatchSoundsLike := False;
oWord.Selection.Find.MatchAllWordForms := False;
oWord.Selection.Find.Execute(Replace:=wdReplaceAll);
oWord.ActiveDocument.SaveAs(sFilename);
oWord.ActiveDocument.Close(wdDoNotSaveChanges);
finally
oWord.Quit;
oWord := UnAssigned;
end;
end;
end.

End Listing One

16 June 2002 Delphi Informant Magazine

Begin Listing Two Mail Merge


procedure TForm1.Button2Click(Sender: TObject);
var
oleWord, oleDocument, oleMerge: OleVariant;
sFilename: string;
begin
try
oleWord := CreateOLEObject('word.application');
except
on E: Exception do begin
E.Message := 'Microsoft Word is unavailable.';
Raise;
end;
end; { try..except }
Screen.Cursor := crHourGlass;
Application.ProcessMessages;
with OpenDialog1 do begin
Filter := 'Word document templates (*.dot)|*.dot';
if not Execute then
Exit
else
sFilename := Filename;
end;
try
oleWord.Visible := False;
oleWord.Documents.Open(sFilename);
oleDocument := oleWord.ActiveDocument;
oleMerge := oleDocument.MailMerge;
{ Set up merge parameters. }
oleMerge.MainDocumentType := wdFormLetters;
oleMerge.OpenDataSource(ExtractFilePath(sFilename) +
'datasource1.txt');
oleMerge.Destination := wdSendToNewDocument;
oleMerge.MailAsAttachment := False;
oleMerge.MailAddressFieldName := '';
oleMerge.MailSubject := '';
oleMerge.SuppressBlankLines := True;
oleMerge.DataSource.FirstRecord :=
wdDefaultFirstRecord;
oleMerge.DataSource.LastRecord := wdDefaultLastRecord;
{ Run the mail merge. }
try
oleMerge.Execute(False);
except
{ Close document & quit Word. }
oleDocument.Close(wdDoNotSaveChanges);
oleWord.Quit;
Raise;
end; { try..except }
{ Close the original merge template. }
oleDocument.Close(wdDoNotSaveChanges);
{ Make newly created document the active document. }
oleDocument := oleWord.ActiveDocument;
oleWord.Visible := True;
oleDocument.SaveAs(Filename := ExtractFilePath(
sFilename) + 'JobOffer.doc', AddtoRecentFiles:=True);
finally
Screen.Cursor := crDefault;
oleWord := UnAssigned;
end; { try..finally }
end;

End Listing Two

On Language
Interfaces / Delphi 6

By Leo Seaman

Everyday Interfaces
Interfaces Can Simplify Common Programming Tasks

nderstanding how to declare interfaces is not the same thing as understanding


all the ways to use them. The power of interfaces, aside from their use in n-tier
programming, is their ability to shotgun polymorphism into an object hierarchy.
This means you can take several different classes,
give them similar functionality, and then reference them using the interface instead of the
actual class type. By writing a class that accepts
an interface property, the class can work with any
other class that implements the interface without
knowing the other class type.
One example of this would be a form that
implements an interface to do special processing,
which can be as simple as focusing a control. Then,
a data module can call the form via the forms
interface to do the special processing. The data
module no longer needs to use the forms unit,
only the interfaces unit. This eliminates possible
circular-reference errors, and the data module can
work with any object that implements the interface,
not just the form. This allows you to change your
application without rewriting every module.
Interfaces are also the cornerstone of multi-tiered

interface

development. COM and CORBA both use


interfaces to accomplish the same goal: a languageindependent method of communicating between
applications and networks. Although this is the
most common use of interfaces in Delphi, they are
very useful in other ways. I will not cover COM
and CORBA in this article, but understanding
interfaces takes a lot of the mystery out of COM
and CORBA programming. Even if you arent planning to use COM or CORBA, you can apply interfaces to solve many common problems gracefully.

Interface Basics
You declare an interface to group a set of methods
together to perform an operation. This is similar
to a class, but with an interface, only the methods
can be listed. The interface syntax is quite simple,
as shown in the Delphi help file:
type InterfaceName =
interface (AncestorInterface)
['{GUID}']
MemberList
end;

uses IBankAccountUnit;
type
TCheckingAccount = class(TInterfacedObject, IBankAccount)
private
FBalance: Currency;
// These three functions are required by IBankAccount.
function Debit(Amt: Currency): Currency;
function Credit(Amt: Currency): Currency;
function GetBalance: Currency;
public
constructor Create(OpeningBalance: Currency = 0);
end;

Figure 1: A class declaration that supports an interface.


17 June 2002 Delphi Informant Magazine

The member list is a list of procedures and functions


that the interface defines. The following restrictions
apply to interface declarations:
The member list can include only methods and
properties; fields arent allowed in interfaces.
Because an interface has no fields, property
read and write specifiers must be methods.
All members of an interface are public;
visibility specifiers and storage specifiers
arent allowed (but an array property can be
declared as the default).

On Language

Interfaces have no constructors or destructors; they cannot


be instantiated, except through classes that implement their
methods.
Methods cannot be declared as virtual, dynamic, abstract,
or override. Because interfaces do not implement their own
methods, these designations have no meaning.
Interfaces can be declared only in the outermost scope of a
program or unit, not in a procedure or function declaration.

If the AncestorInterface isnt specified, the interface will inherit


from IInterface (IUnknown before Delphi 6). IInterface is the
base interface for all interfaces in Delphi. It has three methods
in its member list: _AddRef, _Release, and QueryInterface. These
methods are used for reference-counting of the object, and querying the object to see what interfaces it supports. The GUID only
needs to be declared if the interface is going to be type cast, or
if the interface is used in COM. Only the final example in this
article will require a GUID for the interface.
The interface is used only to declare the member list. No code
is written in the implementation section for an interface. In
that respect, an interface is very similar to an abstract class. The
interface declaration should be placed in its own unit, or in a unit
that only declares interfaces. This allows the interface to be used in
many projects without predetermining the implementation code.
This code shows the declaration of the IBankAccount interface:
type
IBankAccount = interface
function Debit(Amt: Currency): Currency;
function Credit(Amt: Currency): Currency;
function GetBalance: Currency;
end;

After youve declared the interface, you can define classes that
support it. When a class supports an interface, its required to
implement all methods of the interface and all ancestor interfaces. If the class doesnt implement all the methods, Delphi will
generate a compile error. The syntax to support an interface is to
list the interfaces after the ancestor class, as shown in the Delphi
help file:
type ClassName = class (AncestorClass, Interface1,
..., InterfaceN)
MemberList
end;

Theres no limit to the number of interfaces a class can support. The


methods declared in the interface member list must be declared in
the class, or a compile error will be generated. The methods can be
declared with any scoping operator and still will be callable using the
interface. If the class is designed to be used only via the interface,
declaring the methods as protected will prevent the class from being
instantiated and having its methods called normally. The class methods are implemented normally. Figure 1 shows the code for a sample
class that supports an interface.
In Figure 1, the class TCheckingAccount descends from a prebuilt class in Delphi, TInterfacedObject. It is among those classes
that are used to build other classes that support interfaces.
TInterfacedObject implements the methods from IInterface, so any
classes that inherit from TInterfacedObject dont need to declare or
implement the methods _AddRef, _Release, and QueryInterface.
18 June 2002 Delphi Informant Magazine

Now that youve declared the class and interfaces, you can create the
object. The code for creating an interfaced object is similar to that for
creating a normal object. The difference is that the variable into which
the object is stored is declared as the interface, not the class:
var
FBankAccount: IBankAccount;
...
begin
FBankAccount := TCheckingAccount.Create;
FBankAccount.Debit(100);

Because the object is referenced via an interface and is a subclass of


TInterfacedObject, the object is reference-counted. When the interface
variable goes out of scope, or if the variable is assigned to nil, the object
will be destroyed. This eliminates the need to call the Free method, and is
one of the benefits of using an interfaced object as opposed to a normal
object. Because interfaced objects can be reference-counted, sharing an
instance of the object becomes much easier. Heres an example of creating objects into an interface:
// Can reference any object that implements
// the IBankAccount interface.
FBankAccount: IBankAccount;
...
procedure TfrmMain.rgAccountTypeClick(Sender: TObject);
begin
case rgAccountType.ItemIndex of
0 : FBankAccount := TCheckingAccount.Create;
1 : FBankAccount := TCreditCardAccount.Create;
end;
end;

As you can see, one interface variable is being used to hold a reference to
an object that supports the interface. At design time, its unknown which
object will be created, so all calls are done via the interface methods. This
allows us to call any method of the interface, and youre guaranteed that
the object will implement those methods.
In addition, any object that supports the interface can be created and stored into the interface variable. If additional classes
that support the interface are designed later, theyre automatically compatible with this code. The downside is that you can
call only methods of the interface. If the object has additional
methods, they wont be callable through the interface, so
design the interface with that in mind. An example of creating an object and storing it in an interface can be found in the
/SimpleInterfaceExample folder in the sample code that accompanies this article (see end of article for download details).

Factories
A common practice when using interfaces is to have a factory
class generate objects for you. At first, this seems to add unnecessary coding to your project. However, the advantage is being able
to replace or subclass the factory to change the object being created. Also, when creating an interfaced object, only the interface
and factory units are in the uses clause, not the unit that declares
the class being created. This allows you to change and move the
interfaces and interfaced objects between projects with minimal
code changes elsewhere.
Figure 2 shows a simple class factory. The class factory declares a single
class function, Create, that creates an object and returns it as an interface.
A class factory should be written for each interface. The class factory

On Language
should be declared in a different unit from the interface and the class that
supports the interface.
Now, a form can use the class factory to create objects:
procedure TfrmMain.rgAccountTypeClick(Sender: TObject);
begin
case rgAccountType.ItemIndex of
0 : FBankAccount := TCheckingAccountFactory.Create;
1 : FBankAccount := TCreditCardAccountFactory.Create;
end;
ShowBalance;
end;

Youll find an example of using a factory in the /BankAccountFactory


folder in the example code that accompanies this article.

Common Tasks
Now that you understand the basics of interfaces, you can apply
them to common tasks. One such task is to set the focus on a form
to a specific field when a database problem occurs in a data module.
Although there is some support for this in the TField class by setting
the Required property, or by calling the FocusControl method,
such procedures normally dont work well enough in a complex
application. FocusControl, for example, will not focus a control if
it isnt visible. To solve this problem using interfaces, declare an
interface, IFocusField, that specifies a single method to call when a
control should be focused:
IFocusField = interface
procedure FocusField(AField: TField);
end;

Each form that needs to be able to focus a control programmatically should implement the interface:
// TMyForm supports the IFocusField interface.
TMyForm = class(TForm, IFocusField)
...
private
// TMyForm is required to implement this method.
procedure FocusField(AField: TField);
...
end;

When an error occurs in a data module, the data module will need
to call the FocusField method of the IFocusField interface. The data
module must declare an instance variable to store the interface:
TMyDataModule = class(TDataModule)
...
public
// FocusFieldHandler can hold a reference to any
// object that supports the IFocusField interface.
FocusFieldHandler: IFocusField;
end;

When the form creates the data module, it sets the interface field on
the data module to the form:
procedure TMyForm.CreateForm(Sender: TObject);
begin
// Create normally.
FMyDataModule := TMyDataModule.Create(Self);
// Data module can call us via IFocusField.
FMyDataModule.FocusFieldHandler := Self;
end;

19 June 2002 Delphi Informant Magazine

unit BankAccountFactoryUnit;
interface
uses
IBankAccountUnit, CheckingAccountUnit;
type
TCheckingAccountFactory = class
class function Create(OpeningBalance: Currency = 0):
IBankAccount;
end;
implementation
{ TCheckingAccountFactory }
class function TCheckingAccountFactory.Create(
OpeningBalance: Currency): IBankAccount;
begin
Result := TCheckingAccount.Create(OpeningBalance);
end;

Figure 2: A simple class factory.


procedure TMyDataModule.BeforePost(DataSet: TDataSet);
begin
// Check for error condition.
if DataSet.FieldByName('foo').IsNull then begin
// Make sure the interface was set to some object.
if assigned(FocusFieldHandler) then
// Call the object via the interface.
FocusFieldHandler.FocusField(
DataSet.FieldByName('foo'));
// Raise a custom error to display to the user.
raise Exception.Create('Please enter a value for foo');
end;
end;
procedure TMyForm.FocusField(AField: TField);
begin
// Find control that works with this field and set focus.
if AField.FieldName = 'Foo' then begin
PageControl.ActivePage := pgDetail;
dbedFoo.SetFocus;
end;
end;

Figure 3: Using an interfaced object for data validation simplifies


the data module, and allows you to keep the code for handling
data-aware controls on the form.

This allows the data module to call the form via the IFocusField
interface without needing to know the class of the form or what
controls are on the form. This greatly simplifies the data module and
puts the code that deals with the data-aware controls back on the
form where it belongs (see Figure 3). An example is available in the
/FocusFieldExample folder in the code for this article.

Getting Login Information from an Interface


Another common task to perform on a database application is to
request login information from the user. Depending on the type of
application being written, this information can be gathered in several
different ways. If youre writing a two-tiered application, a dialog box
normally does the trick. Writing a Web server application typically
requires a little more work.
In both cases, the information needed is the user name and password,
as well as any other database-specific information. To solve this problem using interfaces, declare an interface, ILoginInfo, that specifies
methods common to login functions:

On Language
ILoginInfo
function
function
function
end;

= interface
Logon: Boolean;
getName: string;
getPassword: string;

For a simple example, create a login form that implements the


ILoginInfo interface:
TfrmLogin = class(TForm, ILoginInfo)
...
private
{ ILoginInfo }
function Logon: Boolean;
function getName: string;
function getPassword: string;
...
end;

The Logon method would display the form modally and get the login
information. As Figure 4 shows, the getName and getPassword methods would retrieve the login information from the form.
Use a factory so you can change the class you use to collect the
login information without re-coding your data module:
TLoginInfoFactory = class
public
class function Create: ILoginInfo;
end;
class function TLoginInfoFactory.Create: ILoginInfo;
begin
Result := TfrmLogin.Create(Application);
end;

Finally, the data module or form that required the login information would declare a variable of ILoginInfo. When the data module
required login information, it would use a factory to create a login
form and retrieve the login information. In Figure 5, a Database
component is used, and on the OnLogon event, you would prompt
for the user name and password.
If the data module is used in a Web-server application later, the same
interface could be used and the factory would be changed to retrieve
the login information from an HTTP request instead of a login
form. An example of this can be found in the /LoginFormExample
folder in the supporting code for this article.

function TfrmLogin.Logon: Boolean;


begin
Result := (Self.ShowModal = mrOK);
end;
function TfrmLogin.getName: string;
begin
Result := edName.Text;
end;
function TfrmLogin.getPassword: string;
begin
Result := edPassword.Text;
end;

Figure 4: Getting the login information from the form.


20 June 2002 Delphi Informant Magazine

Reference-counting Forms
If you noticed in the factory for the last example, the login form
was created, and the owner was passed as the application object.
The form will exist until the Free method is called or the application
closes. Because you didnt declare the Free method in your interface,
you cannot call it. Therefore, the form will exist until the application
ends. This isnt memory efficient, however, and could cause problems
if the login method is called multiple times. There are several solutions for this problem. The simplest solution is to add an owner
property to the factorys Create method, so you can create the form
with an owner other than the application:
TLoginInfoFactory = class
public
class function Create(AOwner: TComponent = nil):
ILoginInfo;
end;
class function TLoginInfoFactory.Create(
AOwner: TComponent = nil): ILoginInfo;
begin
Result := TfrmLogin.Create(AOwner);
end;

Generally speaking, this is a good idea. If a class you create


in a factory accepts parameters for its constructor, the same
parameters should be available through the factory. This is a
good solution, but it doesnt handle all situations. Because you
cant guarantee the object calling the factory will have access to a
valid owner object, you must be able to accept nil as a parameter.
In that case, you should write the class to handle reference
counting. Forms and data modules, unlike TInterfacedObject,
dont support reference counting by default. If you look through
the Delphi source code, youll find that TComponent declares
procedure TForm1.Database1Login(Database: TDatabase;
LoginParams: TStrings);
var
LoginInfo: ILoginInfo;
begin
LoginInfo := TLoginInfoFactory.Create;
if LoginInfo.Logon then begin
LoginParams.Values['username'] := LoginInfo.getName;
LoginParams.Values['password'] :=
LoginInfo.getPassword;
end;
end;

Figure 5: Using the Database components OnLogin event


handler to get the login name and password.

function TComponent._AddRef: Integer;


begin
if FVCLComObject = nil then
// -1 indicates no reference counting is taking place.
Result := -1
else
Result := IVCLComObject(FVCLComObject)._AddRef;
end;
function TInterfacedObject._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
end;

Figure 6: The default implementation for _AddRef in TComponent


and TInterfacedObject.

On Language
TfrmLogin = class(TForm, ILoginInfo)
protected
FRefCount: Integer;
{ ILoginInfo }
function Logon: Boolean;
function getName: string;
function getPassword: string;
{ IInterface }
function _AddRef: Integer; reintroduce; stdcall;
function _Release: Integer; reintroduce; stdcall;
end;

Figure 7: Reintroducing the _AddRef and _Release methods in


your form.

the _AddRef, _Release, and QueryInterface methods. However,


they dont do anything unless you are using COM. See Figure
6 for the implementation of _AddRef in TComponent and
TInterfacedObject.

function TfrmLogin._AddRef: Integer;


{ If we don't have an owner, use reference counting.
If we have an owner, return -1 to show there is no
reference counting. }
begin
if Owner = nil then
Result := InterlockedIncrement(FRefCount)
else
Result := -1;
end;
function TfrmLogin._Release: Integer;
{ If we dont have an owner, decrement the reference count.
If the reference count is zero, free the form. If we have
an owner, return -1 to show that there is no reference
counting. }
begin
if Owner = nil then
begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then
Free;
end
else
Result := -1;
end;

To add reference counting to a form or data module, the _AddRef


and _Release methods must be reintroduced in your form, as shown
in Figure 7. Look at the implementation for TInterfacedObject to
see how it works and write your methods to do reference counting if
there is no owner for the form (see Figure 8).

Figure 8: Implementing _AddRef and _Release in your form.

Now, if the factory creates an object without an owner, it will be


reference-counted. If you use an owner, the form will be destroyed
normally. See the /LoginFormWithReferenceCounting project to see
this technique in action.

This allows you to design your solution to match the problem at


hand. You wont have to design some strange class hierarchy to
try to encapsulate the methods. Designing with the interfaces will
give you a much cleaner design.

Simulating Multiple Inheritance

The downside to this is that the implementations of these interfaces are not inherited. Each of the three classes above would
need to implement all the methods of the interfaces they support.
Besides being inefficient, this also introduces the possibility
of errors by implementing the code in several different places.
Because you dont have an ancestor class from which to descend
that implements the methods of the interfaces, you need to come
up with another way to encapsulate the code. To get around this
problem, you can build helper classes. The job of the helper class
is to implement the methods of an interface and then be embedded into a class that supports the interface. The helper calss can
be descended from TObject because it isnt interfaced:

Another common task for which interfaces can be used is to


simulate multiple inheritance. Delphi doesnt allow multiple
inheritance, which means that every class in Delphi has only one
ancestor class from which it descends. Because you can add as
many interfaces as you want to a class, you can use this to simulate multiple inheritance.
For example, you may need to write classes that handle the following problem: Some objects should be able to place orders,
some objects should be able to return orders, and some objects
should be able to cancel orders. If you need to design classes to
handle this using inheritance, you may not be able to design the
classes you need. For example, if you wanted the following combinations, trying to develop them using inheritance could
be difficult:
Order-entry clerk can place order and return order.
Supervisor can return order and cancel order.
Manager can place order, return order, and cancel order.
Because there is no one attribute that all the roles have in
common, its difficult to determine what to use for a base class.
If you decide to use interfaces, you could design three different
interfaces: IPlaceOrder, ICancelOrder, and IReturnOrder. Then,
you could design classes for the order-entry clerk, supervisor, and
manager that implement the correct interfaces. By implementing
interfaces, you can make as many combinations as you need:
TOrderEntryClerk = class(TInterfacedObject, IPlaceOrder,
IReturnOrder)
TSupervisor = class(TInterfacedObject, IReturnOrder,
ICancelOrder)
TManager = class(TInterfacedObject, IPlaceOrder,
IReturnOrder, ICancelOrder)

21 June 2002 Delphi Informant Magazine

type
TPlaceOrder = class
procedure PlaceOrder;
end;

All classes that implement the interface will declare the helper
class and get it to do the implementation. Delphi provides a
technique for handling this by extending the property keyword.
The property must have a read operator and must declare that
it implements the interface. A write operator is optional. When
declaring the property, include the implements keyword, and you
have an interfaced class that uses a helper:
TManager = class(TInterfacedObject, IPlaceOrder)
private
FPlaceOrder: TPlaceOrder; // Helper class.
protected
// Calls to IPlaceOrder will be handled by FPlaceOrder.
property PlaceOrder: TPlaceOrder read FPlaceOrder
write FPlaceOrder implements IPlaceOrder;
end;

On Language
// Declare interfaces in the private section of class.
private
FCancelOrder: ICancelOrder;
FPlaceOrder: IPlaceOrder;
FReturnOrder: IReturnOrder;
// Create object and assign it to interfaces it supports.
OEClerk := TOrderEntryClerk.Create;
FPlaceOrder := OEClerk;
FReturnOrder := OEClerk;
// Call the method of the interface.
if assigned(FPlaceOrder) then
FPlaceOrder.PlaceOrder;

Figure 9: Calling a method when the interface type is known.

The interfaced class should create the helper in its constructor and free its helper in the destructor. The FPlaceOrder helper
object will now automatically handle any calls to the IPlaceOrder
interface.
The advantage of using the helper class is how quickly it can
be changed. New methods can be added to the interface and
implemented in the helper class, without changing any of the
classes that support the interface. All the classes that support the
interface and use the helper class will pick up the new methods
automatically. An example of this is in the /MultipleInheritance
folder in the code that accompanies this article.

Typecasting Interfaces
Typecasting interfaces is not the same as typecasting objects.
When an interface is typecast, the QueryInterface method
is called, and if the interface is supported by the object, the
interface is returned. If the interface is not supported, an
EIntfCastError exception is raised. For QueryInterface to work,
the interface being typecast must have a GUID. A GUID is
generated by the operating system and is guaranteed statistically
to be unique. The GUID is added after the declaration of the
interface. Whenever a GUID is added to the source code, use
CSG instead of duplicating the GUID in the examples:
IReturnOrder = interface
// Add this using Ctrl+Shift+G.
['{B785D7F1-7303-4FAA-8C26-024E7598CAFE}']
procedure ReturnOrder;
end;

Typecasting interfaces is useful when the run-time type of the interface isnt known at design time. For example, in the prior example,
three interfaces were declared to hold a reference to either the
order-entry clerk, manager, or supervisor objects. This worked fine,
because the number of objects and interfaces was small. However,
as the interfaces and objects increase in number, you would need
to declare each additional interface and assign your objects into the

22 June 2002 Delphi Informant Magazine

// Declare one interface in private section of class.


private
InterfacedObject: IInterface;
// Create the object, and assign it to the interface.
InterfacedObject := TOrderEntryClerk.Create;
...
// Is there an interface?
if assigned(InterfacedObject) then
try
// Call the PlaceOrder method.
(InterfacedObject as IPlaceORder).PlaceOrder;
except
on E:EIntfCastError do // Place order not supported.
MessageDlg('Place order not allowed for this worker',
mtError, [mbOK], 0);
end;

Figure 10: Calling a method using typecasting.

interfaces it implements. As shown in Figure 9, the code to create


an object and assign it to an interface is complex, but calling the
interface is simple because you know the interface type.
Another way of coding this would be to have a single interface of
type IInterface (IUnknown for Delphi 5) declared instead of all the
different interfaces. When the objects are created, they are stored
in the IInterface instance variable. Then, when the exact interface
is needed, a typecast is used to convert the IInterface to the exact
interface required. As Figure 10 shows, this simplifies the code
used to create the objects, but complicates the code used to call
the methods.
After modifying code to use a GUID and typecasting, the project
must be rebuilt instead of compiled. Failing to do a build will
result in the EIntfCastError even if the interface has a GUID. An
example of this can be found in the /MultipleInheritanceGUID
folder in the code associated with this article.

Conclusion
In this article, weve seen several ways to use interfaces in day-to-day
programming. Once you start using interfaces, I think youll find them
an invaluable asset in your programming toolkit. Enjoy.
The nine example projects associated with this article are available
on the Delphi Informant Magazine Complete Works CD located in
INFORM\2002\JUN\DI200206LS.

Leo Seaman is an independent consultant who does training for Web Tech Training
and Development (http://www.WebTechCorp.com). The company has offices in the
United States and United Kingdom and specializes in Delphi training and development. Readers may reach Leo at leseaman@bellsouth.net.

Informant Spotlight
By Jerry Coffey

A Mature Market?
Delphi Informant Magazine
Readers Choice Awards 2002

ne has to ask the question. Things do seem to have settled down. New Delphirelated companies and products used to come out of the woodwork. True, some
would disappear just as quickly, leaving only a forlorn Web grave site, but many
would go on to thrive in the rough-and-tumble Delphi marketplace.
It was nearly impossible to keep up; putting together
each years ballot was a major learning experience,
trying to identify who was new and who had vanished. Even the number of categories used to expand
rapidly each year. The first ballot, in 1996, had 12
categories including Best VBX, if you can believe
it while this year only one new category, Best
Scheduling/Calendar, has been added.
The pace of change has slowed. This isnt necessarily a bad thing, of course; its a natural stage of any
BEST ACCOUNTING PACKAGE

BS/1 - 29%

Bravo - 21%

Accounting for Delphi


29%

23 June 2002 Delphi Informant Magazine

But let me know what you think. Take a look at


the results this year and give me your assessment.
There are some surprises to be sure, but I think
youll see a pattern.

Best Accounting Package

BSS Business Systems - 8%


AdaptAccounts
13%

successful market. And dont get me wrong; the


third-party Delphi marketplace is still vital. This
years ballot contains over 220 products in 22 categories, and the number of votes was the highest
ever. And there are still surprising new companies
and newborn products AToZeds IntraWeb
comes to mind that spring full grown like
Athena from Zeuss head, so its still an interesting
challenge just to compile the ballot list.

Theres no better way to demonstrate how close the


competition is in the Delphi third-party arena than
our first category, which resulted in a tie. BS/1
from Davis Business Systems Ltd., and Accounting
for Delphi from ColumbuSoft, both took 29% of
the vote. ColumbuSoft had first place to itself last
year with 39% percent (with BS/1 runner-up at
21%), so this category is also a good example of
just how quickly things can change in this market
(fourth-place finisher AdaptAccounts was the
winner in 2000). Bravo from Bravosoft has third
place to itself this year with 21%.

Informant Spotlight
BEST ADD-IN
Others - 11%

BEST CHARTING/MAPPING TOOL


Others - 11%

StarTeam - 5%
Class Explorer
6%

CodeRush
41%

ExpressOrgChart
Suite - 20%

VssConneXion
7%
teeChartPro
69%

Multi-Edit - 10%

ModelMaker Code Explorer - 20%

Best Add-in

Best Charting/Mapping Tool

Then there are categories where little has changed over the years
at least in first place. CodeRush from Eagle Software won
easily (this time with 41% of the votes) for the fourth straight
year. However, a newcomer to the category, ModelMaker Code
Explorer from ModelMaker Tools, took runner-up with 20%,
while last years runner-up, Multi-Edit from American Cybernetics, fell to third with 10%.

You really like Steema Softwares teeChartPro! Its won in this category since the category itself was introduced in 1998 this year
with 69% of the votes. Thats five consecutive first-place finishes.
ExpressOrgChart Suite from Developer Express repeats as runner-up
for the second year in a row, with 20%.

BEST BOOK

Others - 18%

Building Kylix
Applications
7%

BEST COMMUNICATIONS TOOL


Others - 14%

IP*Works!
Delphi Edition
11%
Mastering
Delphi 6
50%
Async Professional
75%

The Tomes
of Delphi:
Algorithms
and Data
Structures - 25%

Best Communications Tool


Best Book
This category is unique in that it always consists of a new group
of contenders, i.e. the books published in a given year. And this
year it was no contest and no surprise with Marco Cants
Mastering Delphi 6 (published by Sybex) taking half the votes.
The Delphi-friendly publisher Wordware took runner-up with
Julian Bucknalls The Tomes of Delphi: Algorithms and Data Structures, with 25% of the votes.
In the sub-category of Kylix books, the most votes went to Building Kylix Applications by Cary Jensen, et al., with 7%. This is the
only category into which Kylix intruded even marginally. No
Kylix tools finished in the running another indicator that Kylix
is a non-starter for Borland.
Also, many of you wondered why Borland Delphi 6 Developers
Guide wasnt on the ballot. It was published in 2002 so it doesnt
qualify for this years awards, but Teixeira and Pachecos latest
offering is sure to figure prominently in next years awards.
24 June 2002 Delphi Informant Magazine

As if five years in a row wasnt enough, Async Professional from


TurboPower Software tops this category for the sixth straight
year with a whopping 75% of the votes. And for the first time,
BEST DATABASE CONNECTIVITY

IB Objects - 25%
Others - 35%

ASTA - 19%
Direct Oracle Access
Component Set - 10%

MySQL Direct Access


Components - 11%

Informant Spotlight
IP*Works! Delphi Edition from /n software emerges from the
field to finish a distant second with 11%.

BEST GLOBALIZATION TOOL


PolyGlot Professional (PolySuite) - 5%

Best Database Connectivity


Now this is getting repetitious. The winner and runner-up in this
category are identical for the third year in a row (the lifetime of
the category), with Jason Whartons IB Objects taking 25% of the
votes, and ASTA from ASTA Technology Group taking 19%. The
third place finisher is different this year, with MySQL Direct Access
Components from microOLAP Technologies LLC, replacing two-year
third-place finisher Direct Oracle Access from Allround Automations.

MultLang Suite - 8%
MULTILIZER
VCL Edition - 37%
Localizer - 25%

BEST DATABASE ENGINE


TsiLang Components Suite - 25%
Others - 11%

DBISAM
Database System
27%

FlashFiler - 20%

Best Globalization Tool


Its the second year for this category, and MULTILIZER VCL Edition from MULTILIZER Ltd. repeats as the winner with 37% of the
vote. There was a tie for runner-up with TsiLang Components Suite
from SiComponents (which placed third last year), and Localizer
from Korzh.com each getting 25%.

Apollo - 15%

Advantage
Database Server
27%

BEST HELP-AUTHORING PACKAGE


Others - 12%
Doc-O-Matic - 7%

Best Database Engine


The changes were small but important in this category, with
DBISAM Database System from Elevate Software moving into a
tie for first place with last years winner, Advantage Database Server
from Extended Systems. Both took 27% of the vote. (Last year it
was Advantage with 28% and DBISAM with 25%.) FlashFiler from
TurboPower Software remains in third place for the second year
running with a respectable 20% (it finished second in 2000).
BEST DATABASE TOOL
Others - 11%

InfoPower - 36%
Rubicon Full
Text Search
8%

Help & Manual


29%

ForeHelp - 9%

RoboHELP Office
17%

HelpScribble - 26%

Best Help-authoring Package


Theres a new champ in this category, where Help & Manual from
EC Software, came out of nowhere to take 29% of the vote. This
pushed last years winner, HelpScribble from JGsoft, into a close
second with 26%, and made last years runner-up RoboHELP
BEST IMAGING TOOL

QuickDesk - 8%
Others - 17%
IB Expert - 13%
ExpressDBTree
Suite - 18%
ImageEn - 12%

Best Database Tool


This makes it three straight for InfoPower from Woll2Woll
Software, this year with 36% of the votes. And its the second year
in a row as runner-up for ExpressDBTree Suite from Developer
Express, with 18%. It marks another second-place finish for
Developer Express, but there are plenty of categories to go.
25 June 2002 Delphi Informant Magazine

LEADTOOLS
Imaging Toolkit - 29%

ImageLib
Corporate
Suite - 42%

Informant Spotlight
settle for third with 17%. Help & Manual must be something
special, because HelpScribble and RoboHELP had been one and
two for three consecutive years.

BEST MODELING/CASE TOOL


Others - 13%

Best Imaging Tool


This categorys results are yet another photocopy of last years:
ImageLib Corporate Suite from Skyline Tools Imaging took first
with 42% of the votes, and LEADTOOLS Imaging Toolkit from
LEAD Technologies took second with 29%. The only other tool
to garner double digits in this category was third-place finisher
ImageEn from HiComponents with 12%.

ModelMaker
40%

Bold for Delphi


13%

CDK - 13%

BEST INSTALLATION PACKAGE


Rational Rose - 21%

Others - 5%
DeployMaster - 7%

InstallShield
47%

with 40%, and Rational Rose from Rational Software was runner-up
with 21%. Last year, however, the numbers were 27% and 22% respectively, so ModelMakers doing something right for Delphi developers.
BEST REPORTING TOOL

Wise for
Windows
Installer - 41%

Others - 8%
ExpressPrinting
System - 7%
ReportPrinter
Pro - 11%

Best Installation Package


Unsurprisingly, this category is completely dominated by the two
installation giants: InstallShield Software with 47% of the votes, and
Wise Solutions with 41%. And its been that way from the first Readers Choice Awards in 1996, although Wise did manage to come out
on top once in 2000.

ReportBuilder
54%

FastReport - 20%

BEST LIBRARY

Best Reporting Tool


Others - 15%

EIPack - 10%

SysTools
39%

Not much new here either, with Digital Metaphors ReportBuilder


crushing the competing reporting tools for the fourth year running,
this time with 54% of the votes. FastReport, from FR-Software &
A.Tzyganenko, was runner-up with 20% a much stronger finish
than it had last year with 12%. Incidentally, this is a popular category
with over 53% of you voting for a reporting tool. Only the VCL Component Set category is more popular.
BEST SCHEDULING/CALENDAR

ExpressBars Suite
36%
Others - 16%

Best Library
TurboPowers SysTools has topped this category since the categorys inception in 2000. Its margin of victory, however, has narrowed significantly.
In 2000 it won with 74% of the votes, and in 2001 with 53%. This year
it beat runner-up, ExpressBars Suite from Developer Express (in second
place again!), by just three percentage points, 39% to 36% respectively.

Best Modeling/CASE Tool


This is the third year for this category, and the top finishers are the same
as in last years balloting: ModelMaker Tools ModelMaker took first
26 June 2002 Delphi Informant Magazine

PIM Flash - 11%

Jazmine
Calendar/PIM
Widgets - 18%

TMS
TPlanner/TDBPlanner
51%

Informant Spotlight
Best Scheduling/Calendar
This is a brand new category; in fact it wasnt even added until a week
into the voting. Not surprisingly therefore, it was the least popular with
only 9% of you voting in the category (actually its in a virtual tie for
last place with Accounting Package). TMS TPlanner/TDBPlanner from
TMS Software took inaugural honors handily with a dominating 51%
of the votes. Jazmine Calendar/PIM Widgets from Jazmine Components
made a respectable second-place showing with 18%.
BEST TESTING/DEBUGGING TOOL
reACT - 2%

Others - 19%
ASPack - 30%
VMware
Workstation
6%
Xceed Zip - 6%
FinalBuilder - 9%

AQtest - 9%

OnGuard - 18%
Beyond Compare - 12%

AQtime - 10%

NuMega
BoundsChecker
10%

BEST UTILITY

Sleuth QA
Suite - 45%

one with 31%, and ASPack number two with 24%. Beyond Compare
from Scooter Software made a good showing in third with 12%.

Best VCL Component


CodeSite - 24%

Best Testing/Debugging Tool


This category must be trapped in amber. Not only did Sleuth QA
Suite from TurboPower Software, and CodeSite from Raize Software finish one-two for the third year in a row, the percentages have
remained remarkably similar as well. This year it was 45% and 24%
respectively; last year it was 45% and 23%, and in 2000 it was 49%
and 29%. Now thats consistent testing results.

Developer Express tops this crowded category for the second straight
year with its ExpressQuantumGrid, getting 39% of the votes. Only
TurboPowers Orpheus managed to win in this volatile category two years
in a row, in 1998 and 1999, so its an impressive achievement. (That
was before the Best VCL Component Set category was created in 2000).
The runner-up is TAdvStringGrid from TMS Software with 18%, and
Abbrevia from TurboPower finished a close third with 16%.
BEST VCL COMPONENT
ExpressQuantumGrid
39%

Others - 20%

BEST TRAINING
TRichView - 7%
Others - 25%

Jensen Data
Systems - 26%
Abbrevia - 16%

TAdvStringGrid - 18%

The DSW
Group - 9%

Web Tech Training


& Development - 14%

InfoCan
Management
26%

Best Training
Heres something new. Jensen Data Systems came from nowhere
to tie past-winner InfoCan Management with 26% of the vote.
This was all the more remarkable because Jensen Data Systems has
never even placed before, while InfoCan has taken the top spot
the last four years running. Web Tech Training & Development
finished a decent third with 14%.

Best Utility
More action! ASPack Softwares ASPack took top honors with 30% of
the votes, while TurboPower Softwares OnGuard took runner-up with
18%. In other words, they traded places; last year, OnGuard was number
27 June 2002 Delphi Informant Magazine

Best VCL Component Set


This category has been dominated by TurboPowers Orpheus for the
past two years, but this year brings a change. Two-year runner-up
Developer Express finally broke through to win with its ExpressQuantumPack taking 22% of the votes. TMS Software is runner-up with its
TMS Component Pack and 16%, while Orpheus drops into third with
13%. By the way, this is the most popular category on the ballot; 56%
of you vote for Best VCL Component Set.

Best Web Development Tool


Theres nothing settled in this category, which features what must
be considered the years most exciting new product. Buzz worthy
newcomer, IntraWeb from AToZed Software, makes an impressive
debut in first place with 41% of the votes, knocking last years
winner, Internet Professional from TurboPower Software, into
second with 27%. That leaves third place to last years runner-up,
ASP Express from Marotz Delphi Group, with 15%.

Informant Spotlight
BEST VCL COMPONENT SET
ExpressQuantumPack
22%
Others - 29%

TMS Component
Pack - 16%

Raize Components
9%
1stClass - 11%

Orpheus - 13%

BEST WEB DEVELOPMENT TOOL


Others - 10%
WebHub - 7%
IntraWeb - 41%
ASP Express
15%

Company of the Year


TurboPower Software did so well in the 2000 balloting that I
decided by editorial fiat (which is big fun let me tell you) to designate them as Company of the Year. That year, they placed first
in six categories, and second in one. In other words, TurboPower
won in a full third of the categories, so it was a pretty easy call to
make. Company of the Year has now become a tradition, and TurboPower Software is still the champion with three first-place, and
two second-place finishes. Their closest competitor is Developer
Express, nipping at TurboPowers heels with a win in two categories and three as runner-up.

Conclusion
Hats off first to the creative, hard-working Delphi third-party
vendors who are the subject of these awards. The products they
create and the risks they take to bring them to the market are
what these awards are all about. Congratulations to every company on the ballot; without you there would be no Delphi community. And if your product wasnt on the ballot this year, please
let me know. This is intended as an exhaustive list of all commercial Delphi-related software. Freeware or open source software,
however, does not qualify for this ballot at least not until we all
start programming for free.
And finally, thank all of you in the Delphi community for voting.
These awards belong to you; we just tabulate the results and
reflect them back. Im honored to be part of the process, and a
member of the Delphi community the best bunch Ive worked
with in my 20+ years of programming.

Internet Professional - 27%

Product of the Year


Its dj vu all over again in this penultimate category as well.
Digital Metaphors ReportBuilder retains the crown with 11% of
the votes (last year it had 10%), and Extended Systems Advantage
Database Server repeats as runner-up with 7% (the same percentage it had last year).

28 June 2002 Delphi Informant Magazine

Jerry Coffey is Editor-in-Chief of Delphi Informant Magazine. You can reach him at
jcoffey@DelphiZine.com.

Informant Spotlight
Contacting the Winners
Best Accounting Package
Accounting for Delphi
ColumbuSoft
Phone: (800) 692-2150
Web Site: http://www.columbusoft.com
BS/1
Davis Business Systems Ltd.
E-Mail: info@dbsonline.com
Web Site: http://www.dbsonline.com
Best Add-in
CodeRush
Eagle Software
Phone: (310) 441-4096
Web Site: http://www.eagle-software.com
Best Book
Mastering Delphi 6 by Marco Cant
Sybex
Web Site: http://www.sybex.com
Best Charting/Mapping Tool
teeChartPro
Steema Software
E-Mail: info@steema.com
Web Site: http://www.steema.com
Best Communications Tool
Async Professional
TurboPower Software
Phone: (800) 333-4160
Web Site: http://www.turbopower.com
Best Database Connectivity
IB Objects
Jason Wharton
E-Mail: jwharton@ibobjects.com
Web Site: http://www.ibobjects.com
Best Database Engine
Advantage Database Server
Extended Systems
E-Mail: info@extendsys.com
Web Site: http://www.extendsys.com
DBISAM Database System
Elevate Software
E-Mail: sales@elevate.com
Web Site: http://www.elevatesoft.com

29 June 2002 Delphi Informant Magazine

Best Database Tool


InfoPower
Woll2Woll Software
Phone: (800) 965-2965
Web Site: http://www.Woll2Woll.com

Best Scheduling/Calendar
TMS TPlanner/TDBPlanner
TMS Software
E-Mail: info@tmssoftware.com
Web Site: http://www.tmssoftware.com

Best Globalization Tool


MULTILIZER VCL Edition
MULTILIZER Ltd.
E-Mail: info@multilizer.com
Web Site: http://www.multilizer.com

Best Testing/Debugging Tool


Sleuth QA Suite
TurboPower Software
Phone: (800) 333-4160
Web Site: http://www.turbopower.com

Best Help-authoring Package


Help & Manual
EC Software
E-Mail: support@ec-software.com
Web Site: http://www.ec-software.com/
hmpage.htm

Best Training
InfoCan Management
Phone: (888) INFOCAN (463-6226)
Web Site: http://www.InfoCan.com

Best Imaging Tool


ImageLib Corporate Suite
SkyLine Tools Imaging
Phone: (818) 346-4200
Web Site: http://www.imagelib.com
Best Installation Package
InstallShield
InstallShield Software
Phone: (847) 240-9111
Web Site: http://www.installshield.com
Best Library
SysTools
TurboPower Software
Phone: (800) 333-4160
Web Site: http://www.turbopower.com
Best Modeling/CASE Tool
ModelMaker
ModelMaker
E-Mail: support@modelmaker.demon.nl
Web Site: http://www.modelmaker.demon.nl
Best Reporting Tool
ReportBuilder
Digital Metaphors
E-Mail: info@digital-metaphors.com
Web Site: http://www.Digital-Metaphors.com

Jensen Data Systems


E-Mail: cjensen@jensendatasystems.com
Web Site: http://www.jensendatasystems.com
Best Utility
ASPack
ASPack Software
E-Mail: support@aspack.com
Web Site: http://www.ASPack.com
Best VCL Component
ExpressQuantumGrid
Developer Express
E-Mail: info@devexpress.com
Web Site: http://www.devexpress.com/
index.shtm
Best VCL Component Set
ExpressQuantumPack
Developer Express
E-Mail: info@devexpress.com
Web Site: http://www.devexpress.com/index.shtm
Best Web Development Tool
IntraWeb
AToZed Software
E-Mail: sales@atozedsoftware.com
Web Site: http://www.atozedsoftware.com
Product of the Year
ReportBuilder
Digital Metaphors
E-Mail: info@digital-metaphors.com
Web Site: http://www.Digital-Metaphors.com

OP Tech
Data Storage / Resource Files / Delphi 2-6

By Bill Todd

Storing Data in an EXE or DLL


Make Apps More Secure and Easier to Deploy

surprisingly common question from Delphi programmers is: How can I store
data in an EXE? With resource files, Microsoft anticipated this need in the original design of Windows. Using a text editor and the Borland Resource Compiler, you
can add any kind of data to a Windows resource file, and then link that file into an
EXE. Once you have the data in an EXE, Delphi provides all the tools you need to use
it in your application.
You may be familiar with storing strings, icons,
cursors, or other Windows objects in resource files.
In this article, well look at some less traditional
types of data, including a ClientDataSet, the
contents of a ListBox, an integer array, bitmap and
JPEG images, sounds, and any type of ASCII data.
You can store any data in any format in a resource
file, and access it from your application using the
same techniques covered in this article.

ClientDataSet, as shown in Figure 1. Clicking the


Save CDS button saves the ClientDataSet to a file
named CUST.CDS.

The first step is to get the data in a disk file in the


required format. For a ClientDataSet, this requires
a program that uses the ClientDataSets SaveToFile
method to save the data to disk. One of the
sample applications that accompanies this article
is called CREATEFILES.DPR (see end of article
for download details). It displays the Customer
table from the sample DBDEMOS dataset using a

to save the contents of the ListBox in the file


ITEMS.LB. The Save Array buttons OnClick event
handler is shown in Figure 2. This method declares a
10-element integer array and a TFileStream instance,
and then assigns values to the first three elements
of the array. The next line creates a TFileStream
instance and creates the IARRAY.DAT file. The call
to AFile.Write writes the array to the file.

The CreateFiles programs main form also contains


a ListBox and a Save ListBox button. The buttons
OnClick event handler calls:
ListBox1.Items.SaveToFile('Items.lb');

The Write method takes two parameters. The


first is the buffer that holds the data you want to
write, and the second is the number of bytes to
write. The buffer can be any location in memory
to which your program has access. In this case, the
buffer is the integer array. The Write method starts
with the first byte of the buffer and writes the
contents of each successive byte, until the number
of bytes passed as the second parameter have been
written to the file.

Figure 1: Creating the data files with the sample application.


30 June 2002 Delphi Informant Magazine

The Write method doesnt perform any range


checking. Instead of passing SizeOf(A) as the
second parameter, you could pass 100 and the
Write method would happily write the 40 bytes
occupied by the array and the 60 bytes that fol-

OP Tech
procedure TfrmMain.btnSaveArrayClick(Sender: TObject);
var
A: array[0..9] of Integer;
AFile: TFileStream;
begin
A[0] := 1;
A[1] := 2;
A[2] := 3;
AFile := TFileStream.Create('IArray.dat', fmCreate);
try
AFile.Write(A, SizeOf(A));
finally
AFile.Free;
end;
end;

Figure 2: The Save Array buttons OnClick event handler.

CustCds RCDATA Cust.cds


ListBox RCDATA Items.lb
Array RCDATA IArray.dat
Bitmap BITMAP Demo.bmp
Jpeg RCDATA Demo.jpg
Sound RCDATA Tada.wav
Text RCDATA Text.txt

Figure 3: The DEMO.RC file.

lowed the end of the array regardless of what those bytes contained.
Using the SizeOf function ensures that youll always write the whole
array, and nothing but the array, even if you change the arrays size.
To demonstrate using other types of data, the sample application
also includes a WAV file, a bitmap image, and a JPEG image. I
didnt need a Delphi program to create these files. They came
from other sources.
The next step is to create a Windows resource file that contains all
of the data files. First, you have to create the resource contents file
shown in Figure 3. This file, DEMO.RC, is an ASCII text file, so
you can create it with the Delphi editor, Notepad, or any other text
editor. Each line consists of three parts. The first is the resource name
or identifier number. I prefer names, but you can use an integer
number if you prefer.
The second item on each line identifies the type of data. Figure
4 shows the resource types defined by Microsoft. The values in
the left column are the names of the predefined constants for the
resource types. For example, the constant for the RCDATA type is
RT_RCDATA. In each case, the name of the constant is the name of
the resource type with RT_ attached. The third part of each entry is
the name of the file that contains the resource.
To compile the resource file, open a command prompt window and
enter the command:
BRCC32.EXE DEMO.RC

to invoke the Borland Resource Compiler. Figure 5 shows the result


of compiling the DEMO.RC file used in this article. Note that you
must have the Delphi6\bin directory in the search path for this to
work. If the bin directory isnt in your search path, youll have to type
the full path to BRCC32.EXE. To see the syntax and switches for
BRCC32.EXE, just type BRCC32.EXE at the command prompt and
press R. BRCC32.EXE will create the compiled resource file in
31 June 2002 Delphi Informant Magazine

Value

Meaning

RT_ACCELERATOR

Accelerator table

RT_ANICURSOR

Animated cursor

RT_ANIICON

Animated icon

RT_BITMAP

Bitmap resource

RT_CURSOR

Hardware-dependent cursor
resource

RT_DIALOG

Dialog box

RT_DLGINCLUDE

Dialog include file name

RT_FONT

Font resource

RT_FONTDIR

Font directory resource

RT_GROUP_CURSOR

Hardware-independent cursor
resource

RT_GROUP_ICON

Hardware-independent icon
resource

RT_HTML

HTML

RT_ICON

Hardware-dependent icon resource

RT_MANIFEST

Windows XP: Fusion XML Manifest

RT_MENU

Menu resource

RT_MESSAGETABLE

Message-table entry

RT_PLUGPLAY

Plug and Play resource

RT_RCDATA

Application-defined resource (raw


data)

RT_STRING

string-table entry

RT_VERSION

Version resource

RT_VXD

VXD

Figure 4: Predefined resource types (from Microsoft docs).

the same directory as the RC file. The compiled resource file will have
the same base name as the RC file and the extension RES.
With the compiled resource file in hand, the next step is to create
an application that uses the resources. The sample application that
accompanies this article is called USEDATA.DPR. To get Delphi
to link the resource file into your program, use the $R compiler
directive shown in Figure 6. Figure 6 shows the beginning of the
implementation section of the main forms unit. Any unit associated
with a form will already contain a $R directive that links the forms
DFM file into the application. Instead of a full file name, this $R
directive uses the * wildcard to stand for the name of the unit. The
{$R *.dfm} directive tells Delphi to include the resource file that has
the same base name as the unit and the extension DFM. Figure 6
also shows that a second $R directive has been added to include the
DEMO.RES file. The order of the $R directives doesnt matter.
Figure 7 shows the main form of the USEDATA sample application
with the ClientDataSet and ListBox items resources loaded. Figure 8
shows the code for the Load CDS buttons OnClick event handler, which
declares an instance variable named RStream of type TResourceStream. A
ResourceStream is a stream object that reads data from a resource.
The first statement in the event handler creates an instance of the
TResourceStream class. The constructor takes three parameters.
The first is the Windows instance handle of the EXE or DLL
whose resources you want to read. This allows you to put your

OP Tech

Figure 9: The sample applications data


module.

to the resource in the RC file. The last


parameter is the constant for the resource
type that you assigned to the resource.
Figure 5: Compiling the resource file with BRCC32.EXE.

implementation
uses
MainD, ImageF, MMSystem;
{$R *.dfm}
{$R Demo.res}

Figure 6: Use the $R directive to include the resource in your EXE.

Figure 7: The main form of the sample application.

procedure TfrmMain.btnLoadCdsClick(Sender: TObject);


var
RStream: TResourceStream;
begin
RStream := TResourceStream.Create(
HInstance, 'CUSTCDS', RT_RCDATA);
try
dmMain.cdsCust.LoadFromStream(RStream);
finally
RStream.Free;
end;
end;

Figure 8: The Load CDS buttons OnClick event handler.

resources in a separate DLL, and load it when you need it by


calling the Windows API function LoadLibrary. LoadLibrary
returns the HInstance handle of the DLL, which you can use as
the first parameter of TResourceStream.Create to read resources
from the DLL. The second parameter is the name you assigned
32 June 2002 Delphi Informant Magazine

Figure 9 shows the applications data module.


The data module contains a ClientDataSet
and a DataSource. The ClientDataSet isnt connected to a provider
as a source of data. Instead the OnClick event handler in Figure 8
calls the ClientDataSets LoadFromStream method and passes the
ResourceStream as its parameter. This reads the resource data into the
ClientDataSet and opens the ClientDataSet so the data appears in
the grid.
The OnClick event handler for the Load Listbox button, shown
in Figure 10, is almost identical. The Items property of a ListBox
component is of type TStrings. TStrings also has a LoadFromStream
method, so the code creates a ResourceStream object instance, clears
the Listbox.Items property, and then calls its LoadFromStream method
to load the list from the ResourceStream. You can use this technique
for any object that has a LoadFromStream method.
Figure 11 shows the OnClick event handler for the Load Array
button. Again, a ResourceStream object provides access to the data in
the resource. The ResourceStream.Read method reads the data from
the resource into the array. Read takes two parameters. The first is
the buffer to hold the resource; in this case, the integer array. The
second parameter is the number of bytes to read. You can use the
ResourceStream.Size property for the second parameter, but if you
want to read the entire contents of the resource, its much safer to
use SizeOf to supply the size of the memory buffer. This ensures that
youll never overwrite other areas of memory if the resource contains
more data than you expect.
The code in Figure 11 displays the array by clearing the ListBox on
the main form, and then iterating through the array, converting each
element to a string and adding it to the ListBox. Figure 12 shows the
main form with the array contents shown in the ListBox. While the
first three elements contain the values assigned to the array before it
was saved, the other elements contain random values because Pascal
doesnt automatically initialize variables unless they are members of
an object. If youre storing an array in a resource and all of the elements wont be assigned a value, you need to assign some value, e.g.
minus one, to the elements that are empty so the program using the
resource can tell which array elements contain data and which dont.
The View Images button on the main form displays the image shown in
Figure 13. Figure 14 shows the View Bitmap buttons OnClick event handler. Storing bitmaps in resources is so common that the Bitmap object
has a LoadFromResourceName method. Just pass your programs instance
handle and the name of the bitmap resource as parameters. Note that
you dont have to specify a resource type (RT_BITMAP is assumed).

OP Tech
procedure TfrmMain.btnLoadListBoxClick(Sender: TObject);
var
RStream: TResourceStream;
begin
RStream := TResourceStream.Create(
HInstance, 'ListBox', RT_RCDATA);
try
ListBox1.Items.Clear;
ListBox1.Items.LoadFromStream(RStream);
finally
RStream.Free;
end;
end;

Figure 10: The Load Listbox buttons OnClick event handler.

procedure TfrmMain.btnLoadArrayClick(Sender: TObject);


var
RStream: TResourceStream;
A: array[0..9] of Integer;
I: Integer;
begin
RStream := TResourceStream.Create(
HInstance, 'Array', RT_RCDATA);
try
RStream.Read(A, SizeOf(A));
// Load the array into the ListBox.
ListBox1.Items.Clear;
for I := 0 to High(A) do
ListBox1.Items.Add(IntToStr(A[I]));
finally
RStream.Free;
end;
end;

Figure 11: The Load Array buttons OnClick event handler.

Figure 12: The array resource displayed in the ListBox.

Loading a JPEG image is a bit more complex. The OnClick event handler for the View JPEG button is shown in Figure 15. To display a JPEG
image in an Image component, you have to create a TJpegImage object
and assign it to the Graphic property of TImage. The Jpeg variable used
in Figure 15 is declared as type TJpegImage in the forms type declaration.
The TJpegImage class has a LoadFromStream method, so a ResourceStream
object is created and the image is read from the ResourceStream by a call
to the TJpegImage.LoadFromStream method. The JpegImage instance
is assigned to the Image1.Picture objects Graphic property to make the
image visible in the Image object.
Playing a WAV file stored in a resource requires a different approach.
The Windows API function that plays sound files is SndPlaySound. It
takes two parameters. The first is a string that can hold a registry key or
33 June 2002 Delphi Informant Magazine

Figure 13: The View Image form.

an entry in WIN.INI that specifies a system sound to play, a file name


that contains the sound to play, or a pointer to a location in memory
that contains the sound. The second parameter is an integer whose bits
serve as flags. If the first parameter is a pointer to a memory location, the
second parameter must include the SND_MEMORY flag.
Figure 16 shows the code from the Play Sound buttons OnClick
event handler. This method declares two variables of type THandle,
FindHandle, and ResHandle, and one pointer variable named Sound. The
first line of code calls the Windows API function FindResource to get the
location of the sound resource. FindResource takes the Windows instance,
the name of the resource, and the resource type as parameters. If the
value returned by FindResource isnt zero, then the resource was found.
The next step is to get the resource into memory where
SndPlaySound can access it by calling LoadResource. LoadResource
takes the instance handle of the EXE or DLL that contains
the resource as its first parameter, and the handle returned by
FindResource as its second parameter. If the value returned by
LoadResource isnt zero, the resource was successfully loaded.
The final step is to call LockResource to get a pointer to the
resource in memory. LockResource takes the handle returned by
LoadResource as its only parameter. The call to SndPlaySound takes
the pointer to the resource, Sound, as its first parameter, and the
flags SND_ASYNC and SND_MEMORY. SND_ASYNC tells
SndPlaySound not to return until the sound has finished playing.
SND_MEMORY indicates that the first parameter is a pointer to
a memory location that contains the sound.
The last button on the sample applications main form is the Load Text
button. Figure 17 shows its OnClick event handler. This code provides an
example of loading any amount of text into a string variable. Once again
a ResourceString object is used to access the resource. Since the memory
that holds the text in a string variable is dynamically allocated on the
heap at run time, you have to allocate a string big enough to hold the
resource before you read it.
The code in Figure 17 calls SetLength to set the string variable, S, to the
size of the resource by passing RStream.Size as SetLengths size parameter.
The call to RStream.Read reads the resource into the string variable.
Note that you cannot use the string variable S as the first parameter to
RStream.Read, because long string variables are actually pointers. If the
string is empty, the string variables value is nil. If the string isnt empty,
the string variable points to the block of memory on the heap that

OP Tech
procedure TfrmImage.btnViewBitmapClick(Sender: TObject);
begin
Image1.Picture.Bitmap.LoadFromResourceName(
HInstance, 'Bitmap');
end;

Figure 14: The View Bitmap buttons OnClick event handler.

procedure TfrmImage.btnJpegClick(Sender: TObject);


var
RStream: TResourceStream;
begin
RStream := TResourceStream.Create(
HInstance, 'Jpeg', RT_RCDATA);
if not Assigned(Jpeg) then
Jpeg := TJpegImage.Create;
try
Jpeg.LoadFromStream(RStream);
Image1.Picture.Graphic := Jpeg;
finally
RStream.Free;
end;
end;

Figure 15: The View JPEG buttons OnClick event handler.

procedure TfrmMain.btnLoadTextClick(Sender: TObject);


var
RStream: TResourceStream;
S: string;
begin
RStream := TResourceStream.Create(
HInstance, 'Text', RT_RCDATA);
try
// Allocate a string big enough to hold
// the contents of the stream.
SetLength(S, RStream.Size);
// Read the resource into the string.
RStream.Read(S[1], Length(S))
finally
RStream.Free;
end;
// Display the string.
ShowMessage(S);
end;

Figure 17: The Load Text buttons OnClick event handler.

contains the length of the string, the strings reference count, and the
string data. Since you want to load the contents of the resource into the
data area, the first parameter of RStream.Read must be S[1], i.e. the first
character of the string variables data.

Conclusion
procedure TfrmMain.btnPlaySoundClick(Sender: TObject);
var
FindHandle, ResHandle: THandle;
Sound: Pointer;
begin
// Get the location of the resource in the EXE file.
FindHandle := FindResource(
HInstance, 'Sound', RT_RCDATA);
if FindHandle <> 0 then begin
// Load the resource into memory.
ResHandle := LoadResource(HInstance, FindHandle);
if ResHandle <> 0 then begin
// Get a pointer to the location of
// the resource in memory.
Sound := LockResource(ResHandle);
// Play the sound.
if Assigned(Sound) then
SndPlaySound(Sound, SND_ASYNC or SND_MEMORY);
end; // if
end; // if
end;

Figure 16: The Play Sound buttons OnClick event handler.

34 June 2002 Delphi Informant Magazine

Resources can be a handy way to include any type of data in your


application. Embedding data in a resource file reduces the number of
files you have to distribute, and makes it more difficult for a user to
damage or gain access to the data outside the application. By including
the resource in a DLL, you can realize all of these advantages, and give
yourself the ability to easily distribute new data without having to change
or redistribute the EXE.
The projects and other sample files referenced in this article are
available for download on the Delphi Informant Complete Works CD
located in INFORM\2002\JUN\DI200206BT.

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


and development firm based near Phoenix. He is co-author of four database
programming books, author of more than 90 articles, a contributing editor to
Delphi Informant Magazine, and a member of Team B, which provides technical
support on the Borland Internet newsgroups. Todd 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.

In Development
Compiler Construction / Delphi 2-6

By Fernando Vicaria

Build Your Own Compiler


Part III: Keywords and Command-line Parameters

his is the third part of my compiler construction and concepts series. Last month,
we added a few fundamental parts to the parser, such as embedded white spaces,
multi-character tokens, and code optimization. This month, in Part III, well give the
compiler the ability to recognize command-line parameters and introduce some
fundamental keywords to the language. Well also make it able to read the source
program directly from a file on disk, as opposed to line-by-line via the keyboard.
Delphis inline assembler, also known as BASM
(for built-in assembler), doesnt allow us to
reserve any memory storage directly, or at least
not in a safe way. Therefore, it will become
a little harder to debug the generated code
without adding more code manually, so well
use Delphi to debug and test the assembly code
generated by the compiler as much as possible.

var
x: Integer = 2;
i: Integer;
function foo: Integer;
asm
// Get whatever is in x...
MOV EAX, x
// and add 2 to it.
ADD EAX, 2
end;
function RunAsmCode: Integer;
asm
MOV EAX, 5
PUSH EAX
CALL foo
POP EBX
ADD EAX, EBX
MOV
i, EAX
end;

Figure 1: Debugging routines and variables.


35 June 2002 Delphi Informant Magazine

Lets consider, for example, the debugging routines shown in Figure 1. To debug a statement
such as:
i := 2 + foo;

you would first declare the function foo so the


RunAsmCode function could see it, then declare
the variable i, generate the code for the statement by running the parser, and cut-and-paste
the generated code inside RunAsmCode. Then
you would run the parser again, this time with
the new code inside RunAsmCode, and check
the result of i. If you dont change anything, the
result should be 9. You can check that directly
from the return value of RunAsmCode, or from
the contents of the variable i.
If you have Borlands Turbo Assembler (TASM),
an optional command-line parameter can be used
to not only generate the assembly code, but also
assemble and link it into an executable that you
can run from the DOS prompt. In this case, the
main difference is that the compiler will act as a
two-pass compiler and generate the final executable. In both cases, the only two types allowed are
Integer and ShortString. You wont be able to do
much with them right now, but this will change as
you start adding built-in I/O routines (similar to
those in Delphis system.pas unit).

In Development
procedure SkipBlank;
begin
// Ignore blanks.
while IsWhite(Lookahead) do begin
LookBack := Lookahead;
Read(F, Lookahead);
if (Lookahead = LF) then
Inc(LineNo);
// Handle comments.
if Lookback = '{' then begin
while Lookahead <> '}' do begin
Read(F, Lookahead);
if (Lookahead = LF) then
Inc(LineNo);
end;
Read(F, Lookahead);
if (Lookahead = LF) then
Inc(LineNo);
end;
end;
end;

Figure 2: The SkipBlank procedure.

Program program NAME ProgramBlock


ProgramBlock [VarDecl] [ConstDecl] [Func] MainBlock
MainBlock begin Block end .
Block Stmt [Stmt]
ConstDecl const
VarDecl var
FuncDecl function | procedure
Figure 4: Extended language grammar.

Command-line Parameters
As you might have noticed in the new code for SkipBlank, weve
shifted from reading the code directly from the screen to providing the compiler with a source file. A typical call to the compiler
would look like this:
C:\Temp\fvc3.exe source.txt /s output.txt /o /t /c

In this example, /c indicates that the compiler should output the


generated assembly code to the screen; /s tells the compiler to save
the output assembly code into a file (its immediately followed by
the name and path to that file); /o calls the code to the compilers
optimization routines; and /t calls TASM to compile and link the
generated .asm file. Note that the /s parameter isnt the .asm file
that the compiler will always generate, but an additional file with
only the part of the code that goes in the code segment of the .asm
file. Its used mostly for debugging, because it can be cut and pasted
directly into debugging routines inside the code.
Figure 3: Help for the compilers command-line options.

Whether or not you have an assembler to complete the process with a


second pass and output the final executable, the compiler will generate a file with the same name as the source program you selected,
giving it the .asm extension. Dont worry if all this sounds a bit
unclear, because now well go through the process step-by-step.

Comments
Lets start by making the compiler recognize and ignore comments in the
source code. Keep the traditional Pascal format for multi-line comments:
begin
{ A comment that extends
through one or more lines. }
end;

The comments can appear at any point in the source file, even
between tokens:
x := { assignment to x } 3 + 5;

The compiler should still be able to read this assignment statement as


if the comment wasnt there. The first thing you need to change is the
set that defines white characters, adding '{' to the list:
White

= [TAB, SP, '{'] + LFCR;

The SkipBlank procedure, shown in Figure 2, also needs to be


changed to take into account the inclusion of comments.
36 June 2002 Delphi Informant Magazine

You can easily add more options to the list of parameters. The
code necessary to deal with the command-line parameters
contains one new enumerated type, a constant that maps the enumerated type, and a case statement inside the Initialize function,
shown in Listing One (on page 38). To list all available options,
just call the compiler from the DOS prompt with no parameters
(see Figure 3).

New Language Elements


The language definition for the compiler has changed a bit since we
started (see Figure 4). The additions extend the grammar from the
previous two parts of this series. The format is loosely based on the
BNF (Backus-Naur Form) method and should serve our purposes
for now. You can see the full grammar definition for the language
inside this months code (see end of article for download details).

Declaring Variables and Constants


Lets add a few more options to the case statement in the
ProgramBlock procedure, to reflect the possible declaration of
variables or constants. (Later in this series, well extend this further
so we can declare and define our own procedures and functions.)
As youll see in the next section, variables and constants are
recorded in a pair of TStringList objects for use later. Unlike Pascal,
this language syntax will allow a maximum of one var section and
one const section (although the order doesnt matter).
Take a look at the code for ProgramBlock in Figure 5. After the
compiler recognizes the beginning of a valid new program source,
with the program keyword followed by the name of the program,
there are three valid possibilities for the next token: var, const, or
begin. Any other token will be considered an error.

In Development
{ Procedure for the nonterminal ProgramBlock. }
procedure ProgramBlock;
begin
GetNextToken;
case GetTokenIndex(NextToken) of
tVar : begin
VarList := TStringList.Create;
VarDecl;
GetNextToken;
if GetTokenIndex(NextToken) = tConst then
begin
if not Assigned(ConstList) then
ConstList := TStringList.Create;
ConstDecl;
end
else if GetTokenIndex(NextToken) <> tBegin then
Error('Unexpected token or character');
end;
tConst: begin
ConstList := TStringList.Create;
ConstDecl;
GetNextToken;
if GetTokenIndex(NextToken) = tVar then
begin
if not Assigned(VarList) then
VarList := TStringList.Create;
VarDecl;
end
else if GetTokenIndex(NextToken) <> tBegin then
Error('Unexpected token or character');
end;
tProc : Error('"procedure" keyword not accepted yet');
tFunc : Error('"function" keyword not accepted yet');
tBegin: { Do nothing. };
else
Error('Unexpected token or character');
end;
MainBlock;
end;

Figure 5: The ProgramBlock procedure.

program MyProg;
var
a, b: Integer;
c: string;
const
d = 'Hello world!';
e = 22;
begin
{ This is a multi-line
comment. }
a := 8 / 4 + e; { Another comment. }
b := { Mid-statement comment. } 1 + 4 * foo();
end.

Figure 6: Test source code for the compiler.

The Symbol Table


The symbol table were using isnt what is normally referred to as
a symbol table in compiler literature. It wont be used to keep track
of all the identifiers or tokens found in the source program, nor will
it list where and how many times an identifier appears in the source
code. Instead, it uses a couple of TStringLists to keep track of the
variables and constants declared in the source:
// List of variables declared in source code
// will act as a simple symbol table.
VarList, ConstList: TStringList;

A quick test in the main ProgramBlock has been added to debug and
37 June 2002 Delphi Informant Magazine

check the contents of the lists. Just make sure you set and compile
the project with the DEBUG condition defined.

Allocating Memory
Memory allocation will be done directly in the generated assembly
code, using place holders in the templ.asm file and the TStringList
objects (VarList and ConstList). There are many other ways to do
this, such as using streams or even keeping the whole template file
hard-coded inside the compilers code. Some of these alternatives are
more efficient than what were using, but the approach is to provide
the user a way to alter the template file so it can assemble under any
assembler. Dont worry too much about the contents of the template
file; it will be explained in more detail next month.

Calling the Assembler


The ultimate goal of a compiler is to generate the executable for
your source program. Youll need an assembler such as TASM or
Netwide Assembler (NASM). The code generated by the compiler
will be inserted into a template file, templ.asm, and saved with
the same name as the source file but with an .asm extension. Then
the compiler calls the assembler and linker to produce the final
executable file. It also has the same name as the original source
file, but this time with the .exe extension.
This last step is done via a batch file that can be easily altered for those
that might use a different assembler and linker. For TASM and TLINK
(Borlands linker), the make.bat batch file should look like this:
tasm /MX /ZI /z /m3 /p FILENAME
tlink32 /Tpe /ap /v /c FILENAME,,,IMPORT32.LIB

IMPORT32.LIB contains information concerning Windows symbols and APIs (its found in the include directory of TASM). Make
sure make.bat is in the same directory as the compiler and replace
FILENAME with the name and path of your program source.
Youll use TASM in all of the code in this series, but it shouldnt
be too difficult to convert it to NASM, or even Microsofts Macro
Assembler (MASM). Just make sure the template file is compliant
with your favorite assembler, and modify the batch to point to the
correct programs.
NASM is a free assembler that can be found at http://nasm.octium.net.
Its a portable assembler for the Intel 80x86 microprocessor series,
which uses the traditional Intel instruction mnemonics and
syntax. A list of other free assemblers and linkers can be found at
http://www.thefreecountry.com/developercity/asm.html.

Your First Program


Its time to run your first program with the new compiler. It still
doesnt do much, but at least its starting to take shape (see Figure 6).
Until we add some I/O routines, we wont be able to interact with
the program. A bit of code has been added to the template so we can
verify that the program is actually running the code. Well add some
real I/O support to the compiler next month.
Call the compiler with the following parameters to see the output for
this example:
C:\fvc3 source.txt /o /t

A pause statement is included at the end of the batch file, so you can
see the results of the assembling and linking processes over the .asm

In Development
file. This will prevent the DOS window from closing until you hit a
key on your keyboard (this technique was used during debugging).
Once the compiler has been fully tested, you can remove it.

Conclusion
Things are taking shape with our compiler. Weve added a few command-line options, and some new keywords (program, begin, end, var,
and const) to the language syntax. The compiler can also understand
comments embedded in the source code, and read the source program
from a file on disk. In addition, the main output files are now the assembly code (the .asm file), and a 32-bit, protected-mode, DOS executable.
The language is finally starting to look a little more real. Next month,
well add some control structures, such as if and while, as well as some
basic I/O routines to get a fully operational compiler.
The project referenced in this article is available for download on the Delphi
Informant Complete Works CD located in INFORM\2002\JUN\
DI200206FV.

Fernando Vicaria is a QA Engineer at Borland Software Corporation in Scotts


Valley, CA. Hes also a freelance technical author for Delphi and C++Builder
issues. Fernando specializes in the VCL and CLX frameworks. He can be reached at
fvicaria@borland.com.

Begin Listing One Initialize function


type
{ Command-line parameters. }
TParamType = (ptUnknown, ptOutputToScreen, ptSaveToFile,
ptOptmize, ptGenerateEXE);
const
Parameters: array[Low(TParamType)..High(TParamType)] of
string = ('UNKNOWN','/C','/S','/O','/T');
{ Initialize parser. }
function Initialize: Boolean;
var
i, FileParamNo: Integer;
SkipNext: Boolean;
begin
Result := False;
AsmCodeLst := TStringList.Create;
FileParamNo := -1;
SkipNext := False;
try
// Check parameter to see if generated code
// must be optimized.
if ParamCount > 0 then
// Scan all entered parameters.
for i := 1 to ParamCount do begin
if i = 1 then
begin
InputFileName := ParamStr(i);
if not FileExists(InputFileName) then
Error('Could not find source file: ' +
InputFileName);
Continue;
end;
if not SkipNext then
case GetParamIndex(ParamStr(i)) of
// Send output code to screen.
ptOutputToScreen: begin
OutputToScreen := True;
end;
// Optimize code.
ptOptmize: begin

38 June 2002 Delphi Informant Magazine

if not OptimizeCode then begin


OptimizeCode := True;
AsmOptLst := TStringList.Create;
end;
end;
// Save generated code to file.
ptSaveToFile: begin
SaveCodeToFile := True;
FileParamNo := i + 1;
OutputFileName := ParamStr(i+1);
SkipNext := True;
end;
// Save generated code to file.
ptFileToCompile: begin
UseSourceFile := True;
FileParamNo := i + 1;
InputFileName := ParamStr(i+1);
SkipNext := True;
end;
// Generate executable with TASM and TLINK.
// Please note, TASM and TLINK must be in
// your system's path.
ptGenerateEXE: begin
GenerateExeFile := True;
end;
// Unknown parameter.
ptUnknown: begin
if FileParamNo = i then
begin
if FileExists(InputFileName) then
Continue
else
Error('Could not find source file!');
end
else
Error('Bad parameter passed!');
end;
end; // case
else
SkipNext := False;
end // for i := 1 to ParamCount...
else
begin
Writeln;
Writeln('Syntax: ' +
'FVC3 source [/c] [/s output] [/o] [/t]');
Writeln('/c: ' + TAB +
'Output generated code to screen');
Writeln('/s: ' + TAB +
'Save code segment to disk');
Writeln('/o: ' + TAB + 'Optimize generated code');
Writeln('/t: ' + TAB + 'Generate executable');
Exit;
end;
if OutputToScreen then
Writeln;
AssignFile(F, InputFileName);
SetLineBreakStyle(F, tlbsCRLF);
Reset(F);
GetNextChar;
Result := True;
except
AsmCodeLst.Free;
AsmOptLst.Free;
Abort(SInitErr);
end;
end;

End Listing One

Inside OP
Currency Type / Floating-point Format / Delphi 2-6

By Vsevolod Ciumaciov

Floats vs. Currency


When Zero Doesnt Equal Zero

verybody knows that comparing numbers stored in floating-point format can lead
to inaccurate results especially when the numbers represent money. To remedy
this, Delphi 2.0 introduced a new type named Currency.
The idea is that Currency isnt susceptible to
rounding errors the way floating-point types are,
and should therefore be used in place of Single,
Real, Double, and Extended where money is
involved. Unfortunately, theres very little information about the Currency type in the Delphi
documentation. Ive asked colleagues and looked
through professional literature, and I have yet
to find a complete and reasonable explanation.
So I did some investigating on my own, and this
article is the result.

should equal zero, but the hint window displays


the value as -1.1102907873e-17.

Floats

Perform Step Over by pressing 8 to execute


one of four assembler commands and compose
the Delphi statement: dCounter := dCounter +
0.05;. Take a look at the FPU window (shown
in Figure 3). Now, the ST0 register contains the
following hexadecimal number: 3FFA CCCC
CCCC CCCC CCCD. (Note that the FPU
window should be in Display As Words mode. You
can switch modes by right-clicking within the
window and choosing Display As Words.)

Consider the example procedure shown in


Figure 1. When you run it, the following message will appear: dCounter <> 0. Now, set a
breakpoint at the line if dCounter = 0, and
run the procedure again. Put the mouse above
the dCounter variable. Obviously dCounter
procedure TForm1.Button1Click(Sender: TObject);
var
dCounter: Double;
begin
dCounter := 0;
dCounter := dCounter + 0.05;
dCounter := dCounter + 0.05;
dCounter := dCounter + 0.05;
dCounter := dCounter - 0.05;
dCounter := dCounter - 0.05;
dCounter := dCounter - 0.05;
if dCounter = 0 then
ShowMessage('dCounter = 0')
else
ShowMessage('dCounter <> 0');
end;

Figure 1: Experimenting with floats. Will zero equal zero?


39 June 2002 Delphi Informant Magazine

Now set one more breakpoint at the first statement reading: dCounter := dCounter + 0.05;
and re-run the procedure. After execution has
stopped at the first breakpoint, open the CPU
window by pressing CAC, and the FPU
window by pressing CAF. Make these
two windows easy to view and activate the CPU
window as shown in Figure 2.

Lets look beyond our example for a moment.


Delphi generates the code, which uses only FPU
to perform calculations with numbers in floatingpoint format. It doesnt matter what precision you
use in your program, Single, Double, or Extended,
because FPU stores and processes all numbers only
in Extended format. This format is defined by the
IEEE (Institute of Electrical and Electronics Engineers) and has the following expression:
(-1)s x2E-16383 x(m63.m62..m0), where S = sign (bit
No. 79), E = exponent (bitsq No. 78..64), and
m63.m62..m0 = mantissa (bits No. 63..0).

Inside OP
Take off the fractional part and
obtain the following:
2-6 x112 = 310/6410=0.04687510
Repeat the same steps with different levels of precision:
2-10 x1100112=5110/
102410=0.049804687510
2-14 x11001100112=81910/163841
=0.0499877929687510
0
The greater the number of digits
participating in the calculation,
the more precise your result.

Figure 2: The
CPU window
at the first
breakpoint.

The maximum possible number


of digits you may use in the trans
formations through the calculator
is 41. The calculation at 41 digits gives: 2-41 x 110011001100110011
00110011001100110012=10995116277710/219902325555210=0.04
999999999972715110
Analysis of the mantissa shows that this number has the recurring
decimal: 1.10011001100110011001100110011001100110011001
10011001100110011012
Based on these findings, I have determined that its impossible to
achieve absolute precision of the number 0.05 in the floating-point
format. The number is always an approximate value.
You can use the following rule to determine whether a number can be
presented in the floating-point format with absolute precision: Multiply
your number by 2 until it is greater than or equal to 1. If the result does
not have a fractional part, the number can be presented precisely in the
floating-point format. For example, if you put .5 rather than .05 in the
code shown in the beginning of this article, you will get the right result.

Currency
Figure 3: Loading .05 into the ST0 register as a Double.

(Visit developer.intel.com/design/pentium4/manuals/245470.htm
for detailed information on how FPU stores floating-point numbers.)
Now, transform the hexadecimal number located in the ST0
register into decimal format using the above formula. Microsofts
calculator, in Scientific mode, might be very helpful. Figures 4
and 5 can assist you with this procedure.

Now, lets return to that example. Change the dCounter variable


from Double to Currency. Set the breakpoint at the second line of
dCounter := dCounter + 0.05; and run the program. After execution has stopped, call the CPU and FPU windows again. Execute Step
Over and switch the FPU display mode into Display As Extended, as
shown in Figure 6.
This mode automatically performs the transformations executed
previously. You can see that .05 is presented as 500. All numbers

After substitution of all the components in the formula, we have


the following: (-1)0 x216378-16383 x(1.1001100110011001100110011
00110011001100110011001100110011001101

Hexadecimal

Binary

3FFA

0011.1111.1111.1010

CCCC

1100.1100.1100.1100

The reduced number has the following expression: 2-5 x1.1001


1001100110011001100110011001100110011001100110011001
10011012

CCCC

1100.1100.1100.1100

CCCC

1100.1100.1100.1100

CCCD

1100.1100.1100.1101

Or, it has this expression: 0.000011001100110011001100110011001


100110011001100110011001100110011012
Now, we need to transform the obtained result into decimal format.
Consider the following transformation procedure: 2-6 x11.0011001
1001100110011001100110011001100110011001100110011001
1012
40 June 2002 Delphi Informant Magazine

Figure 4: ST0
register values in
hexadecimal and
binary.

Sign

02 = 010

Exponent

0111111111110102 = 1637810

Mantissa

1.100110011001100110011001100110011001
1001100110011001100110011012

Figure 5: Substituting ST0s value in IEEE formula.

Inside OP

Figure 6: Loading .05 into ST0 register as Currency type.

in Currency format are stored as integers. Consequently, all calculations are executed with integers as well. Therefore, if you use the
Currency format rather than the floating-point format, you will
not face any problems concerning the accuracy of calculations.
However, Currency only allows the fractional part of four digits,
maximum, to be stored.

Conclusion
Should you calculate a satellites take-off-path, a quantity of atoms in
air cubic meters, or a soccer field in square inches, the floating-point
format would be best. However, should you want to develop a balance sheet for accounting purposes or develop a financial-reporting
package, the floating-point format is problematic. In such cases, the
fixed-point format type Currency is essential.
The project referenced in this article is available for download on the Delphi
Informant Complete Works CD located in INFORM\2002\JUN\
DI200206VC.

Vsevolod Ciumaciov is a programmer and analyst. He has been using Delphi for
almost six years. Readers may reach Ciumaciov at Ciumaciov_vs@yahoo.com.

41 June 2002 Delphi Informant Magazine

New & Used


By Mike Riley

IP*Works: Delphi Edition 5.0


34 Internet-enabled Components

ts rare these days to discover a rich Windows-client application that is not Internetenabled in some way. Sometimes an application simply has a hyperlink in an About
dialog box. Other times, a product has a complex feature, such as a newsgroupreading application that contains an optional routine to send the programs creator
SMTP e-mails when the application has encountered a bug. Whatever the feature, the
Internet has invaded the traditional client-application space.
To shield developers from the low-level complexities of writing interfaces to the WinSock library,
/n software has published its own solution. They
surveyed the Internet-protocol landscape for the
most popular applications and communication
methods, then wrote wrappers that make what once
was in the domain of TCP/IP experts available to, in
this case, Delphi developers who are trying to solve
business problems. With that objective in mind, /n
softwares fifth attempt provides the companys most
comprehensive Internet-component library yet.

Component Cornucopia
IP*Works 5.0 supplies developers with 34 Internetenabled components that assist with everything
from HTTP file transfers to telnet sessions (see
Figure 1). Version 5.0 has a few more component
objects than past versions, most notably for Simple
Object Access Protocol (SOAP) messaging and
Extensible Markup Language (XML) parsing.
Because most developers are familiar with the
basic services by now, such as FTP, MIME, and
Ping, this review will focus on the more complex and intriguing components provided in the
IP*Works collection. Incidentally, there are no

Figure 1: The IP*Works tab installed into the Delphi IDE offers 34 components.
42 June 2002 Delphi Informant Magazine

Internet-communication-encryption components
in the standard IP*Works package. Programmers
searching for Secure Sockets Layer (SSL) support will need to upgrade to the more expensive
IP*Works SSL edition. Although the SSL edition contains plenty of secure communication
components, I was dismayed with the omission
of a Secure Shell (SSH) component, especially as
insecure public telnet servers are being replaced
by SSH-only services. Nevertheless, almost every
other secure communication-standard component
is available in the SSL edition.

Heavy-handed Installation and Usage


Microsofts Windows XP installation process has
changed the way people install software. Because of
that change, /n softwares products require software
activation over the Internet before they can be used.
This activation comes in the form of a registry file
thats keyed to a specific machine. In other words,
once the license is activated on one machine, it will
work only on that machine. This license information is compiled into every application using any
IP*Works component, so the compiled program will
execute on other computers without requiring the
Internet registration process.

New & Used

Figure 3: The
rudimentary
and overused
(but functional)
XMethods.net
StockQuote Web
Service, made
easily accessible
via IP*Works
SOAP component.

Figure 2: The product provides sample code for 30 applications


that demonstrate how to use the components.

SOAP and XML Support


As I mentioned, the two most interesting offerings in the IP*Works
kit are the SOAP and XML components. IP*Works provides source
code to demonstrate how to use the components (see Figure 2).
The SOAP library builds off the XML parsing classes, so its inclusion
is a natural extension of IP*Works SAX-2 compliant XML capability. The XML parser provides straightforward, node-walking access
to XML documents. The parser only verifies documents that are well
formed. As a result, it doesnt validate documents using Document
Type Definitions or XML Schemas. For Delphi developers requiring these capabilities, check out TurboPowers flexible XML Partner
Professional (http://www.turbopower.com/products/xmlpartner)
instead. However, besides just parsing, IP*Works XML component
also employs XML Path Language capabilities to allow querying of
XML documents. This is quite useful in programmatic situations that
interact with XML, especially when very fast look-ups of child node
data are required.

If your application calls for powerful, cross-platform Internetapplication clients and servers, IP*Works 5.0 will help you build
your solution faster. Its fast; it has 34 easy-to-use components
with comprehensive properties, methods, and events; its scalable;
it provides useful, easy-to-follow demos in Delphi-specific code
for every component in the package; and its the only package
(except for the components bundled with the Delphi 6 Enterprise
edition) that provides a commercial-grade SOAP-implementation
component. On the down-side: it requires two DLLs for run-time
deployment; close to 70 percent of the packaged components are
available for free or in shareware form; it offers no SSH components (theyre also absent in IP*Works 5.0 SSL edition); the Kylix
edition is a separate, considerably more expensive (US$545) package; and the XML-parser component is rudimentary.
/n software Inc.
P.O. Box 13821
Research Triangle Park, NC 27709
Phone: (919) 544-7770
Web Site: http://www.nsoftware.com
Price: US$345 for a single-developer license.

43 June 2002 Delphi Informant Magazine

The SOAP component included in the package is one of the first


SOAP components available commercially for Delphi, other than the
half-baked implementation in Borlands Delphi 6 Enterprise edition.
Unfortunately, the SOAP component included in IP*Works is even
more rudimentary than those Borland created. IP*Works doesnt
create an object interface to the SOAP service. Instead, it uses a series
of method calls to shake hands with a SOAP server. This involves:
establishing the parameters to pass, setting the MethodURI property,
naming the SOAP service URL, sending the request, and reading
the response. Check out my sample SOAP application screen shot in
Figure 3 to see how this is implemented.
This approach has problems. All the parameters and the MethodURI
and URL values have to be determined manually, either by reading
the SOAP services published interface, or by deciphering the services
Web Services Definition Language (WSDL) file (if any is provided).
At least with Borlands version, a WSDL wizard attempts to capture
this detail automatically. Another problematic omission from
IP*Works SOAP object is that it doesnt recognize or use WSDL
documents, even though /n software advertises the component as
SOAP 1.1-compliant. The component also does not support complex
return types as a result of its generic ReturnValue method, which
returns a simple string. Perhaps in a patch, /n software can construct
this capability. It would help to minimize the amount of effort
required to interact with SOAP services.
Another issue plaguing almost every other commercial SOAP
solution available today is lack of interoperability. SOAP is all
about solving interoperability issues, but the silver bullet has
yet to be realized. IP*Works SOAP component drives home the
fact that not every well-intended SOAP service works with every
well-intended SOAP client. I suppose that with a little effort,
these services eventually might work. IP*Works provides low-level
exposure to its SOAP properties, such as XPath, XElement, and
XText, which allow for granular traversal of the SOAP response.
But I would rather have IP*Works do that heavy lifting, especially because the company is being paid to solve these problems.
(Incidentally, one tip you might need, and which you can discover
on /n softwares knowledge-base page, is this: When using the
component to query an Apache SOAP server, the components
content type must be set explicitly to text/xml before calling an
action. I discovered this the hard way after spending too much
time attempting to troubleshoot the problem manually.)

The $350 Question


After reviewing the advantages the IP*Works collection offers, the
most prominent question is whether the product is worth its price.

New & Used


After all, a number of alternatives are available for free. For example,
Delphi editions ship with the FastNet components that take care of
about half of what IP*Works does.
Additionally, the work of Franois Piette and his Internet Component
Suite are available at http://overbyte.alexid.fr/eng/products/ics.html, and
the Indy project is available at http://www.nevrona.com/Indy. The Indy
project is the most comprehensive of the free, open-source alternatives.
Combined, these alternatives address nearly everything that IP*Works
does. (Indy is even working on a SOAP component, although it was
only available in an unstable alpha form at the time of this review.)
So, is IP*Works worth buying? If your projects are to remain in
the exclusive domain of the Delphi programming environment,
and if the program will be used by an audience that can tolerate
minor performance irritations and potentially quirky behavior, then
IP*Works isnt necessary. On the other hand, if your Internet-application projects are going to be written in multiple languages on
multiple operating systems, IP*Works is effective.
Because /n software has written an easy-to-use, consistent interface
to its component set, developers can move from Delphi to Java, and
then to C#, and be assured that the same properties, methods, and
events are contained in the same components. Considering just how
broad /n softwares support for languages and platforms is, there
isnt a major platform (with the exception of the Mac OS) that the
product doesnt support. Plus, Delphi-only developers get the added
benefit of having access to SOAP messaging without having to
purchase Borlands expensive Delphi 6 Enterprise edition. The SOAP
component isnt perfect, but neither is Borlands considerably more
expensive implementation.
For developers seeking a commercial-grade Internet-component
package that will serve their needs beyond the confines of the Delphi
IDE, IP*Works 5.0 is one of the only sufficient solutions available on
the market today.

Mike Riley is a chief scientist with RR Donnelley, one of North Americas largest
printers. He participates in the companys emerging technology strategies using a
wide variety of distributed network technologies, including Delphi 6. Readers may
reach him at mike_riley_@hotmail.com.

44 June 2002 Delphi Informant Magazine

New & Used


By Paul Stockton

IntraWeb
Delphi Web Development Made Easy

ntraWeb is a set of VCL objects that extend the capabilities of Delphi so you can
develop Web applications much as you would standard executables. Create Web
forms, add components, write events, and your project is done. In brief, AToZed
Softwares IntraWeb allows true RAD Web development.
IntraWeb installs smoothly. The process is
automated and includes putting all components
onto the Component palette and making necessary modifications to Delphi. A full installation
requires about 7MB of disk space. The uninstall
is just as clean, and removes all traces of IntraWeb
from Delphi without any manual cleanup.
I was impressed by IntraWebs demos, but
anyone can write good-looking demos. After
all, demos are sales pitches, and we all know
how much a sales pitch speaks about the actual
product. I was genuinely amazed when I saw the
source code to the demos, and began to understand why IntraWeb is so special.

I was expecting to see HTML, JavaScript, Web


modules, and state management, just as I would
with WebSnap. What I saw instead was normal
Delphi code and that was all. If I hadnt seen it
run in a Web browser first, I would have thought
it was a normal Delphi application. I still wasnt
convinced, however; there had to be a catch. So
I built a demo similar to the one I am about to
show you. I figured if I could write a Web application as easily as the demos made it look, then
IntraWeb was for real.

Testing It Out
To create a new IntraWeb application, select File
| New | Other | IntraWeb | Stand Alone Application.

Stand-alone projects have an embedded


HTTP server, and therefore dont need Internet
Information Server, Apache, or any other Web
server. Such projects are ideal for debugging, but
they also can be installed as Windows services
and deployed as they are. If a stand-alone project
is run as an application (i.e. not as a service), a
debugging interface will appear. This is similar to
WebSnaps Web Application Debugger, but its
much simpler and more convenient to use.

Figure 1: The main form at design time.


45 June 2002 Delphi Informant Magazine

All IntraWeb projects start with two units: a


blank application form (Web form) and a server
controller. The server controller has properties for
configuring session timeouts, ports, and so on.

New & Used


procedure TformMain.AddLinkClick(Sender: TObject);
var
i: Integer;
Dest, Src: TIWListbox;
begin
if Sender = AddLink then
begin
Dest := ReserveList;
Src := AvailableList;
end
else
begin
Src := ReserveList;
Dest := AvailableList;
end;
for i := Src.Items.Count - 1 downto 0 do
if Src.Selected[i] then begin
Dest.Items.Add(Src.Items[i]);
Src.Items.Delete(i);
end;
CountLabel.Caption := IntToStr(
AvailableList.Items.Count) + ' available theatres.';
end;

Figure 3: IntraWebs debugging interface.

Figure 2: The AddLink OnClick event handler.

Once I had my main form, I created some basic controls so that my


form looked like Figure 1.
Then I created the event shown in Figure 2 and assigned the
OnClick event of both links to it. As you can see, it isnt very complicated. The point is that its all Delphi code. This is the way all
IntraWeb applications appear. So when the application is run, the
debugging interface will appear (see Figure 3).
From the debugging interface, press 9 (or select Run | Execute
from the menu) to launch the browser automatically. Figure 4
shows what you will see in the browser, with the Add link added. I
couldnt believe it! I had just written a functional Web application
in less than five minutes. Instant Web application; just add source
code! I know it sounds too easy, but it really doesnt get any harder.
Want to show a new form? Use the Show method. It doesnt get
easier than that.
What youve seen so far is referred to as the form layout manager, and
thats what IntraWeb uses by default, i.e. you design the form as you
would a Delphi application, and the Web page will look the same.
IntraWeb, however, has multiple layout managers, and you even can
create your own. One of the alternate layout managers is a template
processor that allows you to assign an HTML file to a form created
using an HTML editor such as Microsoft FrontPage or Macromedia
Dreamweaver. IntraWeb will merge the form and the HTML file
during the rendering process. This allows your graphics people to
design the look and allow you to handle the programming.

Complex Capabilities
Although the demo we just looked at is very simple, dont let
this fool you into thinking IntraWeb can do only simple things.
IntraWeb contains many demos, but my favorite is the dynamic
chart shown in Figure 5. This chart is live in the browser. The user
can play with the data without needing to request data from the
server. A user can choose columns, rows, and even functions. The
currently supported functions are Sum, Count, Minimum, Maximum, and Average. The dynamic chart is limited to bar charts and
has other limitations. For more advanced charting needs, IntraWeb
has support for TeeChart as well.
46 June 2002 Delphi Informant Magazine

Figure 4: The application in Internet Explorer.

IntraWeb supports two modes: application and page. Weve been


looking at application mode, which is best for creating Web applications. In application mode, IntraWeb handles all the details for
you session management, link management, everything. This
mode is the easiest, by far, but its at the expense of some flexibility.
Page mode allows you to handle each page separately, and requires
more work for the developer because you must handle state and links.
However, page mode can be integrated with WebBroker or WebSnap to
augment them. IntraWeb comes with two page-mode demos.

Deployment and Documentation


Were discussing Web applications, so deployment is an important
consideration. In addition to the stand-alone debugger shown
previously, IntraWeb applications can be deployed as self-contained
Windows services, ISAPI DLLs, or Apache DSOs (Windows only).
Deployment types can be altered easily by changing five lines of code
(or less) and then recompiling. Aside from those few lines of code,
the applications are identical.
Documentation is the one weak part of IntraWeb. The documentation consists of 45 pages in Adobe Acrobat format, a help file,
and documentation on the Web site. The Adobe Acrobat files
consist of IntraWeb Manual (35 pages) and Intro to IntraWeb (10
pages). IntraWeb Manual contains some specifics, hints, techniques, and other documentation specific to IntraWeb. It contains
a lot of useful information, but it needs to be expanded, specifi-

New & Used


Technical Support
IntraWebs technical support is probably better than any I have
experienced from a vendor. I hope other vendors will take note.
Technical support is provided by newsgroups. In the months
that I have been part of AToZed Softwares newsgroups, I cannot
remember a single message that has gone unanswered. Most messages are answered within minutes, and its rare for messages to go
without a reply for more than a few hours, even on Sundays.
Users also report bugs by using the newsgroups. Nearly all bugs
are fixed within a few days, and certainly all critical ones are. The
release versions tend to be quite stable, but there is parallel development on newer betas, which is where most of the bugs appear.
IntraWeb 4 is the current release, but IntraWeb 5 is expected as a
beta soon.

Figure 5: A dynamic chart.

cally in the areas of form management, page mode, and custom


component creation. AToZed Software promises that these topics
and others are to come, and the company regularly releases documentation updates.
IntraWeb also includes demos that demonstrate nearly every
feature of IntraWeb, including database access, application mode,
page mode, ISAPI DLL deployment, Apache DSO deployment,
templates, WebBroker integration, and WebSnap integration.
The help file documents many of the IntraWeb specifics, but many
of the topics are blank. Most of the blank topics are those such as
TIWListbox.ItemIndex, which are the same as their Delphi counterparts and thus not exactly vital to documentation. Theres no
substitute for documentation, but I rarely needed it anyway. Developing IntraWeb applications is so much like developing normal
Delphi applications that things just come together naturally.

IntraWeb already has a strong third-party program and support from


several vendors. These include a Bold (http://www.boldsoft.com)
integration from Centillex Software, and TeeChart support from
Steema Software (http://www.teechart.com). TMS Software
(http://www.tmssoftware.com) and Used-Disks Inc. also provide
add-on component packs with additional IntraWeb components.

Pros and Cons


A fully functional evaluation edition of IntraWeb is available. The
evaluation edition has no time limit and very modest restrictions;
you can develop a fully functional application but you cannot
deploy it. Theres a lot to like about IntraWeb:
true RAD Web development
existing Delphi knowledge can be reused easily
doesnt require Web-development experience
extremely flexible and expandable framework
open and flexible API
third-party vendor support
superb technical support
The only major downside for IntraWeb is that its documentation
could use some improvement.

Conclusion
With IntraWeb, you can leverage your existing Delphi skills to
develop fully functional Web applications and deploy them to a
standard browser with no more effort than developing a normal
Delphi application. In short, IntraWeb is Delphi.Web. If youre
involved in Web development or plan to be, you need to try
IntraWeb and see if it fits your needs. The product is compatible
with Delphi 5 and Delphi 6, both Professional and Enterprise. Kylix
and C++Builder versions are expected soon.
AToZed Software
P.O. Box 126
Erindale Center
ACT 2903
Australia
Phone: +61 2 62912702
Fax: +61 2 62912702
Web Site: http://www.atozedsoftware.com
Price: Developer Edition, US$499; Enterprise Edition, US$599.
There are no run-time, royalty, or deployment fees.

47 June 2002 Delphi Informant Magazine

IntraWeb is nothing short of phenomenal. Ive been able to build


robust Web applications that I couldnt have without IntraWeb.
When I first started with IntraWeb, I barely knew HTML. IntraWeb
also allows full control on several layers if you want to access HTML
or JavaScript. Delphi makes Windows development incredibly easy
with its VCL wrapper on top of the Windows API. IntraWeb does
the same for Web development, with its architecture on top of
HTML, JavaScript, and HTTP.
I havent been this excited about a product since Delphi debuted
and freed developers from the bondage of the Windows API by
delivering true RAD development without the limits of Visual
Basic. Why Borland has not implemented WebSnap this way is a
mystery to me.

Paul Stockton has been a programmer with Barwick Systems Limited for 15 years.
He develops database applications for many large organizations in the United
Kingdom using both IBM AS/400s and PC systems. He is a jack-of-all-trades with
PCs, whether hes building them, configuring networks, doing Web or graphic
design, or dabbling with Delphi.

New & Used


By Edward Owen

Easy Compression Library


New Tool Simplifies Compression, Encryption

dding compression and decompression capabilities to an existing project


hasnt always been simple. AidAim Software has changed all that with its Easy
Compression Library, a collection of compression and encryption classes for Delphi
and C++Builder developers.
And Easy Compression Library (ECL) also
makes the processes themselves faster. The
library was developed specifically to make
adding compression and encryption capabilities
to applications more convenient, and it greatly
accelerates the application-building process.
But the feature that sets ECL apart from similar
products is One Stream for compression and
decompression, the new technology introduced
by AidAim Software.

Powerful New Technology


Usually, compression libraries offer two stream
classes to programmers: TCompressionStream and
TDecompressionStream. TCompressionStream is
write-only and strictly sequential and uses Seek to
move the stream pointer. TDecompressionStream
is read-only and unidirectional, and only forward
Seek is allowed. In addition, it is not possible to
receive and set the data size.
AidAim Software provides the technology that
allows the same stream for compression and
decompression. This universal stream class
supports all the properties and methods of the
usual TStream, such as for reading data, writing
data, seeking in any direction, and getting and
setting the data size. The class also supports
some additional functions, such as for getting
the compressed data size and compression
ratio and for indicating progress. Such a
stream is completely compatible with TStream
and provides transparent compression and
decompression, so programmers can work with
it as well as the usual TStream without being
48 June 2002 Delphi Informant Magazine

concerned about the data size, compression


algorithm, or compression ratio, although these
parameters are available.
The use of one stream instead of two means
you can accelerate not only the building of the
program, but also the process of embedding
the compression and encryption functions
in the existing application. Simply replace
your TFileStream or TMemoryStream with its
analog in ECL that supports all the methods,
properties, and behavior of TFileStream
and TMemoryStream, and your application
will receive a powerful instrument for data
compression and encryption (see the examples of
ECL use at the end of the article).
Flexible compression algorithms are another strong
feature of ECL. It provides four compression modes
based on two different compression algorithms:
for fast compression, the eclFastest and eclNormal
modes; and for strong compression, the eclGood
and eclMax modes. The library displayed the best
results in almost all the tests AidAim Software
conducted for speed and compression ratio when
compared with other well-known compression
libraries and popular archives, such as WinZip and
WinRar. (See test results at http://www.aidaim.com/
articles/ecltest1.php.) As an example, in the test for
compression of a graphic bmp file, a scanned photo
with a complicated texture, ECL was clearly ahead
of all the competition.
One additional significant feature of ECL
is strong encryption. In addition to data

New & Used


var
fs: TFileStream;
inBuf, outBuf: PChar;
offset, inSize, outSize: Integer;
begin
// Allocate buffers; set offsets and sizes.
// Create a file.
fs := TFileStream.Create('test.dat',fmCreate);
// Write some data.
fs.WriteBuffer(outBuf^,outSize);
// Seek to needed position.
fs.Seek(offset, soFromBeginning);
// Read some data.
fs.ReadBuffer(inBuf^,inSize);
// Close file.
fs.Free;
end;

var
fs: TECLFileStream;
inBuf, outBuf: PChar;
offset, inSize, outSize: Integer;
begin
// Allocate buffers; set offsets and sizes.
// Create compressed, encrypted file.
fs := TECLFileStream.Create('test.dat', fmCreate,
'Password', eclFastest);
// Write some data.
fs.WriteBuffer(outBuf^,outSize);
// Seek to needed position.
fs.Seek(offset, soFromBeginning);
// Read some data.
fs.ReadBuffer(inBuf^,inSize);
// Close file.
fs.Free;
end;

Figure 1: Simply replace TFileStream with TECLFileStream to add compression and encryption capabilities.

compression and decompression, ECL offers data-encryption


capabilities. ECL uses the Rijndael symmetrical encryption
algorithm, now the Advanced Encryption Standard (see http:
//csrc.nist.gov/encryption/aes). The standard ECL version uses
Rijndael with a 256-bit key, and the version with a 128-bit key is
available for users in countries with export restrictions. Moreover,
ECL comes with additional built-in tools for key protection.
Encryption can be enabled or disabled instantaneously by using
the Encrypted property. Encryption disabling is possible only if the
file is opened with the right key.

Small Footprint
ECL compiles into executables, so no DLL or OCX files are
required. The minimum footprint of the library is 45K. When all
the encryption features and compression algorithms are enabled,
the ECL footprint is approximately 100K. Users of the version
with the source codes can easily configure the library to turn off
unnecessary features. That would reduce the size of the library to
its minimum size in case the encryption and compression eclGood
and eclMax algorithms arent activated.
ECL makes it possible to work with standard files in the eclNone
mode, which allows users to switch easily between the ECL format
with data compression (encryption) and the standard format. To

Easy Compression Library is a library of TStream descendants with


compression, decompression, and encryption capabilities. This
unique product is based on the new One Stream technology from
AidAim Software that simplifies and accelerates the applicationbuilding process.
AidAim Software
555 Vine Ave., Suite 110
Highland Park, IL 60035
E-Mail: support@aidaim.com
Web Site: http://www.aidaim.com
Price: Easy Compression Library Com (without source code),
US$95; Easy Compression Library Pro (with source code), US$195.

49 June 2002 Delphi Informant Magazine

turn on the compression mode, you only need to open the existing
file and set the appropriate value of the CompressionLevel property.
In the same way, you can switch from the compression mode to the
usual format by setting the CompressionLevel property to eclNone.
The library works in Delphi 4, 5, and 6 and in C++Builder 4 and 5.
One of the most attractive features is progress indication; ECL offers
progress indication of any time-consuming operation with streams.
You only need to write the event-handler procedure and assign it to the
OnProgress event of the necessary stream. Progress is measured in the
floating-point format. ECL stream classes make it possible to display
the progress of both single Read and Write operations, and potentially
slow, complex operations such as the LoadFromStream, SaveToStream,
LoadFromFile, and SaveToFile operations.

Buffer Compression and Decompression


In addition to various stream classes, the library contains the
ECLCompressBuffer and ECLDecompressBuffer functions that allow
it to compress or decompress any buffer. You shouldnt need to
memorize any parameters except CompressionLevel. The functions
mentioned are written in C++ and are compiled to object files
to increase the execution speed. ECL Pro contains all the source
code, including low-level compression and decompression
functions in C++, and the implementation of the encryption
function in Object Pascal and assembler.
ECL contains all the necessary information in WinHelp format.
Additionally, the delivery set includes several examples of how to
use the library, from simple progressions to complicated instances.
AidAim Softwares responsive technical-support service allows you
to get quick answers within 24 hours directly from the developers.
If you have any questions, send e-mail to support@aidaim.com.

An Example
Lets look at some examples of using ECL that illustrate its many
features, and the advantages of using this product in application
development. One example of the librarys use is for TFileStream with
encryption and compression. Assume you used the TFileStream for data
access, and now youd like your file to be compressed and encrypted. As
you can see in Figure 1, the required changes are minimal.
Another example is for low-memory consumption. Assume you
used TMemoryStream for working with some object in memory.
Using TECLMemoryStream consumes less memory.

New & Used


A third example is for Binary Large Object (BLOB) field
compression. When using the BLOB fields in a database, you can
reduce the database size by three to 10 times by compression of
the Memo and Graphic fields. Replacement of the TBlobStream
with the TECLStream works well in this case. You can find an
example in the Graphic demo from the ECL delivery set.
AidAim Software plans to add a multi-file archive, ZIP format
support, and a self-extracting executable in future versions of ECL.

Conclusion
Easy Compression Library is a superb compression and
encryption library based on the unique One Stream technology
that allows the same stream for compression and decompression.
This is the library of TFileStream, TMemoryStream, and other
TStream descendants with compression, decompression,
and encryption capabilities. Compression is transparent. All
the methods, properties, and behavior of TFileStream and
TMemoryStream are supported, so its very simple to replace them
in the application code.

Edward Owen is a software developer specializing in database-management


software and cryptography. Readers may reach him at ed_owenus@yahoo.com.

50 June 2002 Delphi Informant Magazine

TextFile

Borland Delphi 6 Developers Guide


With the introduction of
COM+ in Windows 2000, Kylix
on the Linux platform, and the
variety of Snaps in Delphi 6
Enterprise, Delphi developers
have been given a new set
of tools that provide a lot of
exciting application possibilities.
But along with these impressive
capabilities youll also find
complexities, learning curves,
and how-to questions that the
product documentation never
seems to address.
To help explain and ease the
transition into the new and
re-labeled aspects of these
technologies, Steve Teixeira and
Xavier Pacheco (with contributing authors Bob Swart, Dan
Miser, David Sampson, Nick
Hodges, and Ray Konopka)
have released Borland Delphi 6
Developers Guide. This is the
fifth edition in a series that has
consistently provided quality
and value to the Delphi community. As with the previous
editions, thumbing through
this revision is like wandering
around the Grand Canyon:
Its broad, deep, and genuinely
impressive! And despite its size
and scope, you wont find many
extra words or frivolous topics.
Even at 1,100+ pages, its a concise and entertaining read from
cover to cover.
Although much evolution
and refinement with the
51 June 2002 Delphi Informant Magazine

book and with Delphi has


occurred over the last six years,
this version is only a quarter
inch thicker than my copy of
Delphi 2 Developers Guide. In
the areas where Delphi hasnt
changed, the write-ups have
remained similar to the earliest
releases in the series. To make
room for new material and
keep the books weight down,
a few of the oldest topics
were dropped. However, this
information is available via
the PDF version of Delphi
5 Developers Guide thats
provided on the CD, so youre
essentially getting two books
for the price of one.
The table of contents and
organization of the book
have been completely revised.
Among the new topics are
sections on cross-platform
coding and CLX component
design, a chapter on
Microsofts ADO (formerly
ADOExpress, now called
dbGo), and a large Internet
section that covers ASP,
WebSnap (WebBroker++), and
wireless development. The
Delphi packages section has
also been expanded and moved
into a chapter of its own.
Theres a comprehensive
new section on enterprise
development that includes
BizSnap, DataSnap (formerly
MIDAS), CORBA, SOAP,

and a crash course on Delphi/


EJB integration. The security
and transactional features of
COM+ are also thoroughly
explained, along with one of
the best write-ups on Microsoft
Message Queue (MSMQ)
that Ive seen yet. There
were also a few details in this
section on the black art of
configuring DCOM, a fast but
wily protocol that enterprise
developers frequently
stumble over when setting up
distributed MIDAS/DataSnap
systems.
The useful content on the
CD weighs in at a respectable
145MB. Theres 87MB of
evaluation and miscellaneous
software, 14MB of cleanly
written and well commented
examples, and 44MB of PDFs
that provide an electronic
version of the book and its
previous edition.
A small database was omitted
from the CD, but this
and future corrections can
be downloaded from the
books support site at http:
//www.xapware.com/ddg.
The site also has an upload
form where comments and
corrections can be submitted.
For once I agree with the
publishers rating, which
recommends the book to
intermediate and advanced

programmers. The first


100 pages are dedicated to
Delphis history, development
fundamentals, and the Object
Pascal language; this section is
oriented more as a refresher for
the experienced programmer
than as a novice tutorial.
Borland Delphi 6 Developers
Guide is well organized,
exceptionally complete,
and superbly written. Its
an ideal companion for the
new functionality in Delphi
6, and I recommend it
unconditionally.
Tom Lisjac
Borland Delphi 6 Developers
Guide by Steve Teixeira and
Xavier Pacheco, SAMS,
www.samspublishing.com.
ISBN: 0-672-32115-7
Cover Price: US$64.99
(1,169 pages, CD-ROM)

TextFile

Kylix: The Professional Developers Guide and Reference


In the preface to Kylix: The
Professional Developers Guide and
Reference, Jon Shemitz expresses
his goal to make the book an
enduring classic. Only time will
tell if he achieves this goal, but
one thing is certain: This book is
thorough, readable, and provides a
high level of detail.
This book targets software developers experienced in languages
other than Delphi or Kylix who
are beginning the transition to
Kylix. Shemitz assumes the reader
is comfortable writing applications and knows at least one
other language; inexperienced
developers new to object-oriented
design and programming may be
disappointed.
The detailed treatment of the
language and tools, as well as

the insight into the workings


of the optimizer and APIs, will
help an experienced C++ or Java
programmer make the transition smoothly. Delphi developers should find the Linux- and
Kylix-specific information helpful
as well. This book is filled with
interesting low-level details,
hints, and techniques to improve
code quality and avoid potential
problem areas. The Linux sections
point out the differences between
the Windows and Linux APIs that
should ease the Linux learning
curve considerably.

ing, and Qt. Shemitz presents


two projects, using code and text
to explain the rationale behind
the designs. There is more text
than code throughout the book,
and the prose is insightful and
easy to read.

Topics covered include Object


Pascal syntax and techniques; the
Kylix IDE; CLX components;
CLX component development;
and Linux system issues such
as the file and process models,
regular expressions, shell script-

Developers in the market for a


thorough treatment of Kylix and
an excellent overview of Linux
issues for Kylix development
should read this book.

Database and network development arent covered, and the book


covers Kylix 1 rather than Kylix
2, so some of the Kylix is not
Delphi sidebars may be out of
date. Hopefully a second edition
will cover these topics with equal
thoroughness.

Brian Burton

Kylix: The Professional


Developers Guide and Reference by Jon Shemitz, Apress,
www.apress.com.
ISBN: 1-893115-89-5
Cover Price: US$59.95
(943 pages)

The Tomes of Kylix: The Linux API


When Kylix launched, it seemed
to open a new world for Windows
developers: Theoretically, we could
now develop portable applications just by recompiling the
source code. Theoretically. In the
real world, however, many things
required the Windows API, and
to be ported should have
an equivalent in Linux. But where
could we get information about
the Linux API? Kylix doesnt come
with Linux API help, and most
Kylix books barely address the
Linux API, if at all. So, developers
either needed to understand the
man pages, read C-oriented Linux
programming books, or seek help
from more experienced Linux
developers who are usually skilled
in C/C++ not Delphi or Kylix.
The Tomes of Kylix: The Linux API
fills a huge gap, demonstrating
52 June 2002 Delphi Informant Magazine

aspects of Linux API programming from a Kylix developers perspective. The first of eight chapters
serves as an introduction to Linux
and Kylix. Thereafter, each chapter illustrates a specific aspect of
Linux programming. The chapters
begin with introductory text and
example programs to explain the
main concepts. Most chapters
conclude with an API Reference
section with examples.
Some chapters contain a larger
sample program that illustrates
the concepts discussed. For
example, Chapter 3, Linux Input
and Output, includes a dBASE
file reader that lets you import
a dBASE file into a TClientDataset. At the end of Chapter 4,
Processes, Stephens presents a
daemon sample, the Linux equivalent of an NT service.

This book covers the main


aspects of the Linux API and is
a very good reference for Kylix
developers. The accompanying CD contains the books
source code, GLibCSource
code, the Kylix 2 trial edition,
and the Open Edition of Kylix.
The source code on the CD
has minor glitches regarding
WideStrings; hopefully there
will be revisions on the authors
Web site. It would also be nice if
the CD contained the reference
and examples in HTML.
The Tomes of Kylix: The Linux
API isnt an introductory book;
it wont teach you how to use
Kylix or Linux. But it is a good
reference book that belongs on
your shelf.
Bruno Sonnino

The Tomes of Kylix: The


Linux API by Glenn Stephens, Wordware Publishing,
www.wordware.com.
ISBN: 1-55622-823-6
Cover Price: US$59.95
(531 pages, CD-ROM)

TextFile

The Visual Display of Quantitative Information, Second Edition


A picture is worth a thousand
words. Nowhere have I seen this
clich demonstrated better than
in Edward R. Tuftes seminal treatise The Visual Display of Quantitative Information. While graphs,
charts, and other visual structures
can make data easy to understand,
they also can distort the very data
theyre supposed to clarify. With
clear explanations and wonderful
pictures, Tufte explores the range,
from best to worst, of practices
involved in transforming quantitative information into visual
representations.
While not focused specifically
on computer graphics, this

53 June 2002 Delphi Informant Magazine

marvelous work applies to any


visual presentation of data in
computer applications. The
author covers graphical excellence and graphical integrity,
and their foundation; decries
the disparity in quality between
text and graphics in some quarters; and suggests how designers
can improve the situation. A
key point Tufte makes is that
graphical designers should possess both quantitative skills and
aesthetic sensitivity.
Bringing to bear his lifelong
study of the discipline, Tufte
includes a historical perspective
of graphical data representation

and shows how to improve wellestablished forms of graphical


designs through simplification.
For anyone involved in rendering numerical data in a visual
form on computers or in
print this work is essential. I
recommend it highly.
Alan C. Moore, Ph.D.
The Visual Display of Quantitative Information, Second Edition
by Edward R. Tufte, Graphics
Press, www.edwardtufte.com.
ISBN: 0-9613921-4-2
Cover Price: US$40
(197 pages)

File | New
Directions / Commentary

Interview with Jeff Duntemann

legend in the Borland development community, Jeff Duntemann has had an impressive and varied career as
a writer of fiction and non-fiction, editor, and publisher. After creating and editing Borlands magazine Turbo
Technix in the late 1980s, Jeff took over the Structured Programming column in Dr. Dobbs Journal, which he
wrote from 1989-1993. In 1989 he co-founded The Coriolis Group to publish PC Techniques magazine, and later
a line of popular technical books on programming, networking, and certification. PC Techniques became Visual
Developer Magazine in 1996, with Ray Konopka writing a regular column, first on Turbo Pascal and later on Delphi.
Jeff has written numerous popular technical books, including Complete Turbo Pascal, Borland Pascal from Square
One, and Assembly Language Step-by-Step, and was the lead author for The Delphi Programming EXplorer. He has
written and published science fiction for many years, and was on the final Hugo ballot in 1981. You can learn more
about Jeffs ideas on a variety of topics and contact him through his Web site at http://www.duntemann.com.
Delphi Informant: You worked as a technical writer at Borland in
the early years. Please share some of your history at the company,
how you came to join Borland, and some of your more memorable
experiences.
Jeff Duntemann: Philippe Kahn recruited me away from PC Tech
Journal, the Ziff-Davis magazine where I was Technical Editor from
1985 to early 1987. I interviewed toward the end of 1986, and he
roared me around Santa Cruz in his Porsche, talking too fast while I
left nailprints in the armrests. The job was actually to create a slick
programming magazine for him; I didnt do technical writing for
them until after I left the company. The Santa Cruz area is gonzo
city; I was constantly bumping elbows with Communists and nudists
and radical feminists and anarchists and many species of druggie. The
local radicfems regularly burned men in effigy in the town square. It
was street theater par excellance, especially on Halloween.
Doing Turbo Technix was great fun. It was big (160 pages), slick, full
of source code, and hosted some of the best writers in the business.
Borland sent it free to all registered users of its developer products,
and it wasnt sold in stores. This sent their registration rate through
the roof, and at the end we were mailing almost 225,000 copies of
the damned thing, all without a nickel of subscription revenue. Alas,
it was expensive, and what ads we could sell didnt float it. After just
six issues (and with a seventh finished and ready to print) they killed
it. I mourn that un-printed seventh issue; the cover story was a preemptive multitasking engine written in Turbo Pascal.
It was by far the best job I ever had. I used to eat lunch out on the
patio with Anders Hejlsberg, Gary Whizin, and the rest of the Turbo
gang, and argue about things like whether reserved words should be
bolded or capitalized. Anders allowed me to look at the source code
54 June 2002 Delphi Informant Magazine

for Turbo Pascal 3.0 once. It was almost completely uncommented


assembly language, and I wonder if anybody but him every truly
knew how it worked. Philippe took the Turbo Technix staff out on
his solid-teakwood yacht once (I drove for awhile!) and we went to
numerous parties at his incredible house up in the mountains. Those
were the companys glory days. Its a much more sober operation
now, and Im glad I was there when I was.
DI: It seems everyone is talking about Microsofts new programming environment, .NET. What do you think about .NET and what
impact do you think it will have on Delphi?
JD: .NET could be the best thing that ever happened to Delphi.
Consider: In the wake of 9/11, a great many people are thinking about security, and both Windows XP and .NET are under
the microscope. Microsofts security model basically cooks down
to: Trust us. We know what were doing. Most of the recent
e-mail and Web browser worms are due to flaws in Microsofts
long-standing distributed computing machinery, and people are
coming to realize just how vast and unknown that landscape is.
When you program with .NET, youre trusting the integrity of
an immense amount of very new code, none of which you can
examine. Many people who were willing to do that last year wont
do it this year. With Delphis native code architecture, you control
and can examine more of the execution space. Under DCMA, its
questionable whether you can even legally trace into Microsofts
virtual machine looking for security flaws and dont assume
theyll thank you if you find any.
DI: Besides 32-bit support in Delphi 2, what do you feel was the
most important new feature added to Delphi since the original
version?

File | New
JD: I remember Delphi 1 so poorly that Im not entirely sure what it
didnt have. The repository, probably. Maybe action lists. Maybe WinSight. What this means, of course, is that Borland got it almost precisely
right the first time. This is rare; the only other product I can say that
about is Visio. No one feature or group of features has dominated its
evolution since V1.0. The awesome integration of the entire package will
probably always remain Delphis best new feature.
Im still exploring D6, which is very deep. The new stuff on the high end
thats gotten so much buzz is still pretty immature in my view, so Im
still waiting to see how it changes over time. Im not a big champion of
doing things through the Web protocols, and that colors my view a little.
HTTP is a lousy way to move data around, and HTML is a lousy way to
show data. So Delphi features like WebBroker dont make the cut for me.
DI: What are your favorite Delphi add-ons: components, libraries, or tools?
JD: Orpheus 4, hands down. TurboPower rocks always has, always
will. Ditto Elevate Softwares DBISAM database engine. I also like the
Dream Company InfoTree components, which are at the heart of my
Aardmarks utility. And while its unclear whether its a tool or not, Id be
hard pressed to do any significant Delphi work these days without Torrys
Delphi Pages on the Web.
DI: You have been involved as a writer and editor of Visual
Developer Magazine and other popular technical journals. It seems
that such periodicals are becoming an endangered species. What
is your assessment of the current situation and what advice would
you give magazine publishers?
JD: Sigh. Magazines are in trouble, in part because programming
has gotten so interconnected and so deep. Doing DOS was easy,
and you could write articles with source code that both taught
something useful and could be covered at magazine length. Thats
no longer the case. Really gutsy technical coverage of programming has to be done at book length. The little stuff we used to put
in magazines is now free on the Web.
But its worse than that: Magazines need huge ad bases to prosper,
and products with user bases smaller than VBs cant always support the ad base that a dedicated magazine requires. The generalinterest programmer rags (like mine) died because programmers
specialize these days. They have to; there arent enough hours in
the day to be good at more than one or two languages or RAD
environments. As for advice, I dont know. If I felt I knew how to
win at the magazine game, Id still be playing it.

Finally, the very best way to be sure youve learned something yourself is to try to explain it to someone else!
DI: As a writer and editor, I suspect you are also a voracious
reader. What have you been reading lately? Do you have any
recommendations in the must-read category?
JD: Steven Pinkers How the Mind Works and The Language Instinct.
Kevin Kellys seminal Out of Control (I read that every couple of
years). The Lord of the Rings (ditto.) I read a good mix of science/
technology, theology (mostly Catholic theology), history, and
interdisciplinary works like Guns, Germs, and Steel and The Wealth
and Poverty of Nations. I read way less science fiction than I used to,
because publishing consolidation has tilted the field toward shallow,
interminable TV-like series that simply bore me.
Recommendations? Bowling Alone. Five Great Catholic Ideas. The Tipping
Point. Papal Sin. The Deep Hot Biosphere. A Distant Mirror. The Emperors
New Mind. The Bad Popes. Rare Earth. I also subscribe to Wired, Sky
& Telescope, and The Atlantic, and pick up odd books on odd topics
here and there, from herbs to the paranormal to the fourth dimension
to garage bands of the 60s. When Im not writing (or building things,
which is writing on a different kind of paper) Im reading. TV isnt even
on the radar.
DI: To conclude, are there any areas Ive not touched upon you
would like to talk about additional information you would like
to share with readers?
JD: Ive gone on way too long as it is. All Id like to do is make a suggestion: That Delphi people create small utilities and post them on Tucows
and places like that, with source code. Free or not free it doesnt
matter. (TurboPower has always shipped source with their products,
which are not free.) The idea is to remind those who download the
utilities that Delphi exists, and that you can create useful things with it in
almost no time compared to command-line C/C++ development, which
takes forever. When Aardmarks goes into general release, it will include
the source for my portions of it, at least (much of it is components). If
Delphi is to survive, it needs as broad a user base as we can give it. The
things that Delphi produces are the best advertisement that Delphi can
ever have. Programmers respect nothing so much as results. Exposing the
Big Lie (that Pascal is a kiddie language without power or utility) is the
single most important task for any Pascal/Delphi programmer. If we can
do that, we win. If we cant (or wont) do that, nothing else matters, and
we might as well hand the world to Bill Gates in a shopping bag.
This is a drastically abridged version of this interview. Please visit
http://www.DelphiZine.com/opinion to read the full-length version.

DI: As weve discussed, you have done a great deal of writing and editing.
What advise would you give an aspiring technical writer?

Alan C. Moore, Ph.D.

JD: First: Read! Read lots. Read the sorts of things you want to
write. Reading helps you develop a voice and shows you how the
other guys do it. Second: Write! Write lots. You dont become a
programmer by not programming, and writing is just programming
for the semantic compiler inside the human brain. Selling technical writing is tougher than it used to be (we have fewer magazines
now) but its way easier than selling, say, science fiction. People who
have a specialty should contact the magazines serving that specialty
and ask the editor what he or she needs. Thats how I started. Thats
how many other people have started. Writing is an excellent way
to get street cred in the industry, speaking gigs, and other perks.

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
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.

55 June 2002 Delphi Informant Magazine