You are on page 1of 41

August 2002, Volume 8 Number 8

Cover Art By: Arthur A. Dugoni Jr.

ON THE COVER

WEB FEATURE

www.DelphiZine.com/Features

Greater Delphi

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


Cary Jensen provides a guided tour of the XML Mapper utility that ships with
Delphi 6 Enterprise Edition, including example applications that demonstrate
how to create XML transformation files from XML files or XML data packets.

FEATURES
9

On the Net

Building Web Services: Part II Bill Todd


Bill Todd converts a Windows Web Services server he created last
month (in Part I) to run under Linux. He also shares a Web Service that
provides large amounts of binary or text data to a client, a database
application using a SOAP server, and a lot more.

15

OP Tech

Optimizing StringReplace: Part I Peter Morris


Okay, so the StringReplace function introduced in Delphi 4 is inefficient
especially when it comes to handling larger amounts of text. Peter
Morris shows us how to create a much more capable tool for the task
based on the Boyer-Moore string-searching algorithm.

19

Informant Spotlight

Borlands .NET Plans Jon Shemitz


From Delphi 7 to Delphi for .NET, Jon Shemitz provides an in-depth
report of Borlands plans to support Microsofts .NET Framework with
Delphi. Topics include Just In Time (JIT) compilation, garbage collection,
language changes, and much more.

25

Delphi at Work

Building a Browser Paul Joseph King


Paul King demonstrates how to create a custom Web browser based on
the Internet Explorer 6 ActiveX control, giving you significantly more
control over the browsers features, and its look and feel.
1 August 2002 Delphi Informant Magazine

In Development

Build Your Own Compiler: Addendum Fernando Vicaria


Based on your suggestions, Borland QA engineer Fernando Vicaria
has updated his do-it-yourself compiler even adding a micro
run-time library. Visit www.delphizine.com/features/2002/08/
di200208fv_f/di200208fv_f.asp to get the latest information and
downloads.

REVIEWS
31
34
38

ExpressLayout Control

Product Review by Ron Loewy

ModelMaker 6.11

Product Review by Robert Leahey

ActivePatch 1.1

Product Review by Mike Riley

DEPARTMENTS
2

Delphi Tools

40

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

Delphi
T O O L S

New Products
and Solutions

Book Picks
.NET Common Language
Runtime Unleashed
Kevin Burton
SAMS

ISBN: 0-672-32124-6
Cover Price: US$59.99
(997 pages)
www.samspublishing.com

Microsoft .NET XML


Web Services
Robert Tabor
SAMS

ISBN: 0-672-32088-6
Cover Price: US$39.99
(464 pages)
www.samspublishing.com

2 August 2002 Delphi Informant Magazine

EPocalipse Software Releases SourceConneXion


EPocalipse Software shipped
SourceConneXion, a product that
integrates all SCC API-compliant source control products
directly into Delphi and
C++Builder. Its features include
auto checkout, for automatically
checking out a file when you
start editing it; show history, for
viewing the history of any file
in your project; share files, for
sharing files from other projects
inside the IDE; multiple
provider support, for automatically loading the appropriate
provider according to project;
and an editor context menu and
toolbar, for easier access to the
most used commands.
You can define your file groups
and operations will be made on
every file in the group (i.e. always
checking out PAS and DFM files
together). The following products
are known to support the SCC
API: SourceOffSite, StarTeam,

PVCS, Perforce, TeamCoherence,


Visual SourceSafe, CVS (with
the Jalindi Igloo add-in), IBM
TeamConnection, Synergy CM
(Continuus), CS-RCS, MKS
Source Integrity, QVCS, and
ClearCase (most source control
products support the SCC API

and SourceConneXion should


work with them without any
problem).
EPocalipse Software
Price: US$69.95
Contact: info@epocalipse.com
Web Site: www.epocalipse.com

Extended Systems Announces


XTNDConnect Mobile Objects 1.2

HK-Software Announces
IBExpert Personal Edition

Extended Systems released


XTNDConnect Mobile Objects 1.2.
The application software development kit, which offers a set of
tools to build mobile applications
for the enterprise, now supports
client application development
utilizing Microsofts Embedded
Visual Studio. Providing a framework to build mobile applications for both online and offline
scenarios, XTNDConnect Mobile
Objects allows developers to build
(or even use existing) generic
COM server objects in common
languages such as Visual Basic,
C++, or Delphi. Client applications can be developed utilizing
AppForge, Metrowerks CodeWarrior, and now Microsofts Embedded Visual Studio. These product
capabilities enable developers who
are building mobile applications to
leverage their existing programming knowledge without having
to learn a new API.
Optimized for wireless networks,
such as an 802.11b/WiFi, XTNDConnect Mobile Objects works
with natively compiled client
applications to provide superior
speed and reliable offline capabilities, while maintaining consistent

The IBExpert product line


from HK-Software has a new
member: IBExpert Personal
Edition. IBExpert is the integrated development environment for working with the
Borland InterBase or Firebird
Database Server. This new
version is a free entry-level
tool. Its dedicated to people
who use InterBase or Firebird
and arent satisfied with the
IBConsole. IBExpert Personal
Edition covers all functions
from IBConsole and adds some
more features for SQL scripting, database administration,
and programming.
The commercial version of
IBExpert has more features,
such as CASE ER diagrams
with reverse engineering, a
stored procedure and trigger
debugger, performance tools,
and a grant manager. A fully
functional 45-day trial version
can be downloaded from
http://www.ibexpert.com.

support for cellular (GPRS, GSM,


and CDPD), IR LAN, and TCP/
IP connections. To ensure the
highest level of security for wireless
and offline applications, Extended
Systems used Certicoms Elliptical
Curve technology to develop
and integrate its patent-pending
Streamline Security system into
XTNDConnect Mobile Objects.
The system offers one-pass
authentication, versus a typical
handshake model, resulting in
optimum speed and security.
Other features include strong
functionality for working with
databases through the ability to
transport recordsets between server
objects and client applications,
an ADO recordset style interface
to work with data that is sent or
received between the server and
client, state or stateless connection
models, Simple Object Access
Protocol (SOAP) access to server
objects, and support for Pocket
PC, Palm, and Windows clients.
Extended Systems
Price: US$395 per software development
kit and US$595 per server.
Contact: (800) 235-7576
Web Site: www.extendedsystems.com

HK-Software
Price: Free
Contact: info@h-k.de
Web Site: www.hksoftware.net/download

Delphi
T O O L S

New Products
and Solutions

Book Picks
Learn Java with JBuilder 6
John Zukowski
Apress

ISBN: 1-893115-98-4
Cover Price: US$54.95
(625 pages, CD-ROM)
www.apress.com

SAMS Teach Yourself .NET


Windows Forms in 21 Days
Chris Payne
SAMS

ISBN: 0-672-32320-6
Cover Price: US$39.99
(883 pages)
www.samspublishing.com

3 August 2002 Delphi Informant Magazine

Tiriss Releases New Component for Accessing xBase Tables


Tiriss announced version
1.60 of CB4 Tables, its Delphi,
C++Builder, and Kylix wrapper for
using Sequiter Softwares CodeBase
Library instead of the Borland
Database Engine (BDE). The
main differences with the previous
versions is the support for Kylix 2
(Linux), C++Builder 6, and OEMANSI translations on Windows.
CB4 Tables is a set of components
that can be used anywhere you
want to access dBASE IV, FoxPro,
or Clipper tables using CodeBase,
but dont want to use the BDE.

TCB4Table, the TTable replacement of CB4 Tables, has almost


all the functions a TTable has,
and is developed to be completely
compatible with TTable. CB4
Tables also has some extra functionality like smart recognition of
expression tags and better scrollbar
support for DBGrids.
The major advances of CB4
Tables include no BDE install,
but a DLL (Windows) or a shared
object (Linux) sold by Sequiter
Software (www.sequiter.com);
a CodeBase database engine;

compatibility with the BDE, so its


easy to switch between the BDE
and CB4 Tables; compatibility
with all standard and third-party
database components (TDataSet
descendant); support for Delphi
3 through 6, C++Builder 3
through 6, and Kylix 1 and 2; and
functionality not in the BDE, such
as tag expression recognition.
Tiriss
Price: US$60, including source code.
Contact: info@tiriss.com
Web Site: www.tiriss.com

Altova Upgrades XML Spy Suite


Altova announced the release
of XML Spy 4.4 Suite. The suite
includes XML Spy Integrated
Development Environment, XML
Spy XSLT Designer, and XML
Spy Document Editor.
In addition to semantic tables,
the developers have added support
for CALS and HTML tables to
XML Spy XSLT Designer and
XML Spy Document Editor.
CALS tables provide a syntax for
SGML-based representation of

complex tables, allowing a publisher to visually express complex


data representations. The addition
of CALS and HTML tables
provides support for the DocBook
content model for describing
books, articles, and other prose
documents.
The XML Spy IDE and XML
Spy now offer a multi-language
spelling checker with support for
English, German, Italian, Portuguese, Spanish, French, Dutch,

Swedish, and other languages.


Separate English language medical
and legal dictionaries are included.
The suite also includes enhanced
Web Services standards conformance in the SOAP client and
debugger.
Altova, Inc.
Price: Free upgrade for current users; singleuser license, US$399.
Contact: sales@altova.com
Web Site: www.altova.com

Amzi!s 6.2 Release Extends Its E-business Capabilities


Amzi! delivers the next generation of e-business capabilities in
its newest major release of Amzi!
Prolog + Logic Server. The Amzi!
Logic Server lets you embed logicbases of business, knowledge, and
grammar rules in any application,
and query it as easily as you would
a database. Business rules are used
for workflow handling, resource
scheduling, automatic forms completion, pricing, and transaction
management. Knowledge rules are
used for support, problem solving,
recommendations, event forecasting, intelligent tutorials, and
system configuration. Grammar
rules are used for XML translation, protocol conversion, document parsing, natural language,
and message understanding.
The 6.2 release adds support for
the latest Web-based technologies:
XML, JSP, Java Servlets, ASP, and
.NET. It also supports the latest
versions of C++, Java, Visual Basic,
and Delphi. Logic-bases can be
extended with any needed capabilities, including database access.
Amzi!s Unicode and multi-byte

support means applications can


be developed and deployed in any
national language.
The 6.2 release is ISO-standard
compliant, including support for
modules, which makes it easier to
organize and maintain large logicbase applications. Performance of
the dynamic database is improved
with the choice of two types of
indexing, and many system limits
have been eliminated.

This version, available for


Windows, Linux, and Solaris, is
free for academic, personal, and
evaluation use.
Amzi!, Inc.
Price: Standard licenses start at US$795;
Professional licenses start at US$2,500;
and Enterprise/Source Code licenses start at
US$30,000.
Contact: info@amzi.com
Web Site: www.amzi.com

Greater Delphi
XML Transformations / XML Mapper Utility / Delphi 6 Enterprise

By Cary Jensen, Ph.D.

XML Transformations
Part I: Using the XML Mapper Utility

orland has always done a good job of differentiating its Professional and Enterprise
editions of Delphi, including sufficient goodies in the high-end release to justify the
additional expense. One of these extras in Delphi 6 Enterprise is XML transformations.
Support for XML transformations is provided in
two ways. The first is a collection of components
that use a special XML file called a transformation file to convert from one form of XML file
to another. The second is a special utility, called
the XML Mapper, which you use to generate the
transformation files.
XML transformations are provided in Delphi 6
Enterprise primarily to convert between XML

<customers>
<customer custno="1001">
<name>John Doe</name>
<address>101 Broadway Avenue</address>
<city>New York</city>
<state>New York</state>
<zip>00123</zip>
<credit>GOOD</credit>
<comments></comments>
</customer>
<customer custno="1002">
<name>Jane Doe</name>
<address>1001 Main Street</address>
<city>Los Angeles</city>
<state>California</state>
<zip>90123</zip>
<credit>GOOD</credit>
<comments>This is a great customer.</comments>
</customer>
</customers>

Figure 1: Sample XML file that doesnt conform to Borlands XML


data packet format.
4 August 2002 Delphi Informant Magazine

files of almost any format and the special XML


format used by ClientDataSets. For example,
with the appropriate XML transformation file,
you can convert data stored in a ClientDataSet
to an XML format required by external applications. Similarly, an XML transformation file can
permit you to convert some arbitrary XML file
(perhaps one provided to you by your customer)
into an XML file whose format permits it to be
loaded directly into a ClientDataSet for editing
and viewing.
This article is the first in a two-part series. This
months article demonstrates how to use the XML
Mapper to create XML transformation files. Part II
will show you how to use these transformation files
with Delphis XML transform components.

XML and ClientDataSets


The data loaded into a ClientDataSet can be
stored in a physical file on disk in one of two
formats: a proprietary binary format, or an XML
format. Its also possible to read the data stored in
a ClientDataSet as XML by reading its XMLData
property. In both cases, the XML data format
is particular to the ClientDataSet. This format
includes metadata, change-log information, and
raw data. In other words, you cant load data from
just any XML file into a ClientDataSet; only those
XML files that conform to the format required by
a ClientDataSet can be used.

Greater Delphi
<?xml version="1.0" standalone="yes"?>
<DATAPACKET Version="2.0">
<METADATA><FIELDS>
<FIELD attrname="custno" fieldtype="string" WIDTH="4"/>
<FIELD attrname="name" fieldtype="string" WIDTH="21"/>
<FIELD attrname="address"
fieldtype="string" WIDTH=v32"/>
<FIELD attrname="city" fieldtype="string" WIDTH="32"/>
<FIELD attrname="state" fieldtype="string" WIDTH="32"/>
<FIELD attrname="zip" fieldtype="string" WIDTH="32"/>
<FIELD attrname="credit" fieldtype="string" WIDTH="4"/>
<FIELD attrname="comments"
fieldtype="string" WIDTH="32"/>
</FIELDS><PARAMS/></METADATA>
<ROWDATA>
<ROW custno="1001" name="John Doe"
address="101 Broadway Avenue" city="New York"
state="New York" zip="00123" credit="GOOD"
comments=""/>
<ROW custno="1002" name="Jane Doe"
address="1001 Main Street" city="Los Angeles"
state="California" zip="90123" credit="GOOD"
comments="This is a great customer."/>
</ROWDATA>
</DATAPACKET>

Figure 2: An XML file conforming to Borlands XML data packet format.

One consequence of the ClientDataSets special XML format is that


it is unlikely that a particular XML file required or generated by
non-Delphi applications will be compatible with a ClientDataSet.
For example, consider the XML file shown in Figure 1.
The XML file in Figure 1 makes use of XML elements to reference
customer data. If this same data were inserted into a ClientDataSet,
and the XMLData property of the ClientDataSet were then read, the
resulting XML file would look something like that shown in Figure 2.
Borland refers to this special XML format as the XML data packet.
The XML data packet contains substantially more information than
the XML file shown in Figure 1. In addition to the data, which is
referenced by XML attributes of empty XML elements, metadata is
also present (again represented by attributes of empty XML elements).
The issue is actually a bit more complicated than this. Specifically,
the XML data packet shown in Figure 2 is one where there are no
changes in the ClientDataSets change log. If a change log was being
maintained, and MergeChangeLog (or ApplyUpdates) hadnt yet been
successfully invoked after editing the data, this XML file would be
larger, and contain change-log and row-state information.
Obviously, the XML files shown in Figures 1 and 2 arent
interchangeable. Fortunately, using Delphis XML
transformation capability, you can transform the
XML example in Figure 1 into that shown in
Figure 2, and vice versa. In other words, XML
transformations permit you to either use data in
an arbitrary XML format in a ClientDataSet, or
generate an arbitrary XML file format from the
contents of a ClientDataSet.
The components in Delphi that perform this
transformation require a special XML file that
describes the mapping between the two formats.
This XML file is generated by a special, standalone application that ships with Delphi 6
Enterprise called the XML Mapper utility.

XML Mapper
Figure 3: The XML Mapper utility.

The XML Mapper utility is shown in Figure 3.


(When the utility first appears, the panes are all
blank.) You can launch it by selecting Tools | XML
Mapper. Alternatively, you can run xmlmapper.exe,
which is located in Delphis \Bin directory.
Most of the time when you are creating a
transformation you begin either from an existing
XML file that isnt in an XML data packet
format, or you have an XML data packet saved
by a ClientDataSet and want to be able to store
it in an alternative XML format. Creating to
and from transformations when you begin with
an existing file is easiest, and is demonstrated
with the following steps. Note, however, that its
possible, though more complicated, to define
XML mapping without any existing XML data.

Creating a Transformation from an XML File


Figure 4: XML Mappers XML-Schema pane.
5 August 2002 Delphi Informant Magazine

The following steps demonstrate how to convert


the data stored in the XML file shown in Figure 1
into an XML data packet. This data can be found

Greater Delphi
in the file named xmldata.xml (available for
download; see end of article for details).
First, select Tools | XML Mapper from
Delphis main menu to display the XML
Mapper. Then, from the XML Mappers
main menu, select File | Open. Browse to
the directory in which you have stored
xmldata.xml, select it, and click OK. The
XML Mapper will open this file and
display its structure in the Document pane.
Its worth noting that you can also load a
schema file or document type definition
(DTD), permitting you to create a
transformation from an XML description
without the actual XML file. In addition,
you can view XML schema information
by clicking on the Schema View tab of the
Document pane, as shown in Figure 4.

Figure 5: Data packet result set.

In the next step, you need to identify which


attributes and elements of the XML file you
want to access in the ClientDataSet. You do
this by adding each attribute and element that
you want to transform into the Transformation
pane. In this case, youll add all attributes and
elements. To do this, right-click the customers
node in the Document pane and select Select
All. To add only specific attributes and
elements, right-click each one, one at a time,
and select Select. Figure 3 shows the selected
attribute and elements in the Transformation
pane (the Selected Fields column in the
Transformation pane and the Datapack pane will
be blank at this point).
Now that youve identified which fields
to transform, youre ready to generate
a transformation. Ensure the XML to
Figure 6: Changing individual properties.
Datapacket radio button is selected in the
Transformation pane, then select Create |
Datapacket from XML. The Transformation
pane will now display one selected field for each selected node,
and the Datapacket pane will be populated with the fields that will
be created in the XML data packet by the transformation (again,
refer to Figure 3).
Youre now ready to create and test your transformation. To do this,
click the Create and Test Transformation button in the Transformation
pane. XML Mapper responds by creating an XML-file-to-datapacket transformation, performs the transformation, and displays
the resulting dataset in a DBGrid, as shown in Figure 5.
Use this DBGrid to inspect and test the transformation before you
save it. This is an important step; in this case, the metadata generated
by the XML Mapper utility is based on the physical contents of
the original XML file. Referring to Figure 1 youll note that the
names of the two customers are both short. As a result, the XML
transformation generates metadata that specifies that the name field in
the ClientDataSet will be short also, allocating only eight characters to
it. To verify this, try editing the name field and enter a longer name,
such as Frank Borland. Youll find you can only enter Frank Bo.
6 August 2002 Delphi Informant Magazine

Figure 7: Sample document view.

Greater Delphi
button. This time you will find ample room for
full names in the generated DBGrid. When youre satisfied with
the transformation, select File | Save | Transformation. The XML
Mapper will suggest the name ToDP.xtr (to data packet). You can
accept this name, or provide any other you find appropriate.

Transformation

Since the Document and Datapacket panes are already populated,


you can now easily create the data packet to XML transformation.
Begin by selecting the Datapacket to XML radio button in the
Transformation pane.
Test your data packet to XML transformation by clicking Create
and Test Transformation. The XML Mapper responds by displaying
a sample document view, as shown in Figure 7. Close the view
when youre done, and save the transformation by selecting File |
Save | Transformation.

Creating a Transformation from an XML Data Packet


You just created a data-packet-to-XML-file transformation. Doing so
was simple because the structure of the XML file was already defined
based on an existing XML file. Creating the same transformation
when the XML file doesnt exist is more involved, but not that
difficult, as the following steps demonstrate. These steps make use of
a file name data.xml. This file, whose contents are shown in Figure 2,
can be found in the download file associated with this article.
Figure 8: The Create Xml from ClientDataSet dialog box.

Since the transformation isnt acceptable, youll need to make


some adjustments, so close the form on which the DBGrid
appears, returning to the XML Mappers main form. You make
adjustments to the metadata that the XML Mapper generates by
changing individual node properties. For example, to change the
size of the name field, select the name element in the Document
pane and click the Node Properties tab in the Transformation pane.
Select the Max Length property, change it to 30, and press R.
Your XML Mapper screen should now look like that shown in
Figure 6. Repeat this process for each of the other nodes in the
Document pane, adjusting the properties to meet your needs (for
example, change Max Length for the remaining text fields).
Once youve adjusted the node properties, select the Mapping
tab of the Transformation pane and click the Create and Test

If the XML Mapper isnt running, select Tools | XML Mapper


from Delphi to launch it. If its already open, clear the
current definitions by selecting File | Clear All. Now open the
ClientDataSet XML data packet named data.xml by right-clicking
in the Datapacket pane and selecting Open Datapacket. Browse
to the directory where you stored data.xml and open it. The
structure of this file will appear in the Document pane.
Next, right-click the Clientdataset node in the Document pane and
select Select All Children. This populates the Select Fields column in
the Transformation pane. If you want to transform some, but not all,
of the fields of the data packet, right-click individual fields in the
Datapacket pane and select Select. Now youre ready to define the
structure of the XML file to which you want to transform the data
packet. From the XML Mappers main menu, select Create | XML
From Datapacket. This displays the Create Xml from ClientDataSet
dialog box, shown in Figure 8.
You use this dialog box to define attribute
and element names for the XML
file that you want to create from the
ClientDataSet. To create a transformation
that will generate the XML file (shown in
Figure 1) from the data packet (shown in
Figure 2), change Root Name to customers,
the field ROW to customer, and the field
@custno from custno to @custno. (Note
that XML is case-sensitive, so enter these
values using the case you want to appear
in the generated transformation.)

Figure 9: XML Mapper with complete definitions.


7 August 2002 Delphi Informant Magazine

After making adjustments to the names,


and identifying whether you want
attributes or elements (by placing @
in front of names that you want to be
generated as attributes), click the Create
button. The XML Mapper will then

Greater Delphi
complete the definitions necessary for the transformation, as
shown in Figure 9.
You can now create your data-packet-to-XML-file transformation.
Begin by clicking Create and Test Transformation. The XML
Mapper displays a sample Document view showing the structure
of your transformation, which will look similar to that shown
in Figure 7. Close this dialog box when youre done inspecting
the structure. If the structure was satisfactory, select File | Save |
Transformation to save it.
With the current definitions in the XML Mapper, you can
quickly create a corresponding XML-file-to-data-packet
transformation. To do this, select the XML to Datapacket radio
button in the Transformation pane, click the Create and Test
Transformation button, close the displayed dialog box, and select
File | Save | Transformation.

Summary
The XML Mapper utility permits you to create transformation files
that are used by Delphi 6 Enterprises transformation components
to convert an arbitrary XML file to a data packet, and a data packet
to an arbitrary XML file. Next month, in Part II of this series, you
will learn how to use the transformation components in Delphi 6 to
perform XML transformations in your applications. See you then.
The files referenced in this article are available on the Delphi Informant
Complete Works CD located in INFORM\2002\AUG\DI200208CJ.

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


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

8 August 2002 Delphi Informant Magazine

On the Net
Web Services / SOAP / WSDL / Kylix / Delphi 6

By Bill Todd

Building Web Services


Part II: Kylix, a BLOB Server, a Database App, and More

ast month, in Part I of this series, we looked at building a simple Web Service
server and client, creating a WSDL file, working with complex types, and using
custom exceptions. Well continue this month by converting the TempConvert server
from Part I to run under Linux. Then well create an example of a Web Service that
provides large amounts of binary or text data to the client. Well also add logging to a
Web Service so that it records the IP address, date, and time of every client that uses
the Service. Finally, we will build a database application using a SOAP server.

Using a Linux Server


So far, all the examples in this article have been
Windows Web Services. Converting a Web Service to run on Linux is surprisingly easy. In fact,
if youve been looking for an excuse to buy Kylix
this may be it. Since Linux requires less hardware
than Windows, and since Linux itself and the
Apache Web server are free, you may find it economical to deploy your Web Services on Linux.
As a simple example we will convert the TempConvert Web Service from part one of this article to run
under Apache on Linux as a CGI application:
1) Start Kylix and choose File | New from the menu.
2) Click the Web Services tab, and double-click
SOAP Server Application to display the New
unit BlobServerIntf;
interface
uses InvokeRegistry, Types, XSBuiltIns;
type
{ Invokable interfaces must derive from IInvokable. }
IBlobServer = interface(IInvokable)
['{3EAFF85A-68AE-4A78-B140-33E97DBD0813}']
{ Methods of Invokable interface must not use the
default calling convention; stdcall is recommended. }
function GetBinaryFile(FileName: string):
TByteDynArray; stdcall;
function GetTextFile(FileName: string): string;
stdcall;
end;
implementation
initialization
{ Invokable interfaces must be registered }
InvRegistry.RegisterInterface(TypeInfo(IBlobServer));
end.

Figure 1: The IBlobServer interface.


9 August 2002 Delphi Informant Magazine

SOAP Server Application dialog box.


3) Choose CGI Stand-alone executable and click OK.
4) Save your new project, naming Unit1
WebMod.pas, and the project TempConvert.
5) Choose Project | Add To Project from the menu,
and add the TempConvertIntf and TempConvertImpl units from the Windows project to
your Linux project. This assumes you have
network access to the directory that contains
the Windows version of the TempConvert
server. If not, youll have to copy both units
to your Linux system via sneaker net.
6) Compile the Linux project.
Now just copy the compiled TempConvert application to your Linux server. If this is the first time you
have deployed a CGI application on Linux, youll
need to make some changes to the Apache configuration file, httpd.conf. On Red Hat 7.2, httpd.conf is
in the /etc/httpd/conf directory. The first step is to
create a directory to hold your CGI applications. Ill
assume that directory is /etc/httpd/cgi-bin. The next
step is to edit httpd.conf. Go to the end of the file
and add the following command to create a logical
directory, named scripts, that points to the physical
directory /etc/httpd/cgi-bin:
ScriptAlias/scripts/"/etc/httpd/cgi-bin/"

Now add the following lines to ensure the directory has the ExecCGI option set:
<Directory "/etc/httpd/cgi-bin">
AllowOverride None
Options ExecCGI
Order allow,deny
Allow from all
</Directory>

On the Net
The last step is to copy TempConvert into the /etc/httpd/cgi-bin
directory. For more information, see the Kylix 2 README file.
Now that the CGI application is installed on your Linux server, you
can change the URL property of the THTTPRIO component in the
Windows client application, run it, and connect to the Linux server.
For example, on my network the URL is http://redhatserver/scripts/
TempConvert/soap/.

Creating a BLOB Server


You might think that creating a SOAP server to supply large binary or
text files would require complex data types and a lot of code, but its
actually very easy. In fact, the procedure for creating the BlobServer
sample application for this article (see end of this article for download
details) is almost identical to the procedure used to create the temperature converter in last months article:
1) Start by going to the Web Services tab of the Object Repository
and double-clicking SOAP Server Application.
2) Choose CGI stand-alone executable for the application type.
3) Answer Yes when asked if you wish to create an interface.
4) Name the interface BlobServer.
5) Add two methods named GetBinaryFile and GetTextFile to the
IBlobServer interface, as shown in Figure 1. Remember to add
the stdcall directive for each method.
6) Copy the method declarations to the BlobServerImpl unit so the
type declaration for TBlobServer looks like Figure 2.
7) Press CSC to create the two methods in the
implementation section.
Figure 3 shows the completed GetBinaryFile method. The file name is
passed to the method as a parameter. The method begins by creating
a TFileStream instance that opens the file for reading. This method
returns the binary file in a dynamic array of bytes using the TByteDynArray type declared in the Types unit. Therefore, the next step is
to set the size of the dynamic array to the size of the file. Finally, the
method reads the contents of the file stream into the dynamic array,
and frees the file stream instance.
Since the Internet is a 7-bit ASCII network, 8-bit binary data must be
encoded into an ASCII text stream before transmission, and it must
TBlobServer = class(TInvokableClass, IBlobServer)
public
function GetBinaryFile(FileName: string): TByteDynArray;
stdcall;
function GetTextFile(FileName: string): string; stdcall;
end;

Figure 2: The TBlobServer class with the methods added.


function TBlobServer.GetBinaryFile(FileName: string):
TByteDynArray;
var
F: TFileStream;
begin
F := TFileStream.Create(FileName, fmOpenRead);
try
// Set the size of the array to the size of the file.
SetLength(Result, F.Size);
// Read the file into the array.
F.Read(Result[0], Length(Result));
finally
F.Free;
end;
end;

Figure 3: The GetBinaryFile method.


10 August 2002 Delphi Informant Magazine

be decoded at the receiving end. Using a TByteDynArray object works,


because its data that is passed as a parameter or return value is automatically encoded and decoded.
Automatic encoding and decoding of the contents of a TByteDynArray
is a great feature, but can add a noticeable delay for a large file. Also,
encoding the data increases its size by one third. If the data youre
transferring is text, it doesnt need to be encoded; so youll get better
performance by using a string instead of a dynamic array of bytes.
Figure 4 shows the code for the GetTextFile method. This method is
identical to GetBinaryFile except that it returns a string. Since the index
of the first character in a string is one, and the index of the first element
of a dynamic array is zero, the call to F.Read was changed to use Result[1]
as the first parameter instead of Result[0].

Creating the BLOB Client


Figure 5 shows the client applications main form. Follow these steps to
create the client:
1) Choose File | New | Application from the menu.
2) Add a Label, Edit, and two Button components to the main form,
as shown in Figure 5.
3) Name the Edit component editFileName, and the Buttons
btnGetBinary and btnGetText, respectively.
4) Add a THTTPRIO component (from the Web Services tab) to
the form, and set its name to Rio.
5) Set the URL to the location of the server with /SOAP/ appended.
On my PC, using IIS, the URL is http://localhost/scripts/
BlobServer.exe/soap/.
6) Add the server applications BlobServerIntf unit to the project.
Then add it to the main forms uses clause.
Double-click the Get Binary button to create an OnClick event handler,
and add the code shown in Figure 6. This method begins by casting the
function TBlobServer.GetTextFile(FileName: string): string;
var
F: TFileStream;
begin
LogClientData;
F := TFileStream.Create(FileName, fmOpenRead);
try
// Set length of string to the size of the file.
SetLength(Result, F.Size);
// Read the file into the string.
F.Read(Result[1], Length(Result));
finally
F.Free;
end;
end;

Figure 4: The GetTextFile method.

Figure 5: The BLOB clients main form.

On the Net
THTTPRIO component to the IBlobServer interface and calling the
GetBinaryFile method, passing the file name entered in the Edit control
as a parameter. GetBinaryFile returns a TByteDynArray thats assigned to
the local variable named Blob.
This program saves the file received from the server in the directory
where the EXE is stored. The path to the directory is extracted from
the Application.ExeName property, and the file name is extracted from
the editFileName components Text property. Next, a TFileStream
instance is created with the mode set to fmCreate, and the contents of
the TByteDynArray are written to the file. After the file is written, a
message is displayed and the file stream is freed.
Figure 7 shows the code for the Get Text buttons OnClick event handler.
This code is identical to the code for the Get Binary button, except that
the GetTextFile method returns a string variable instead of a dynamic
array of bytes. Compile and run the client program, and you should be
able to copy text or binary files from the server to the client.

Logging Clients
Return to the BlobServer application and open the BlobServerImpl
unit in the Delphi IDE. Add a new method, LogClientData, to
the TBlobServer class so it looks like Figure 8. You do not have to
add LogClientData to the IBlobServer interface, because it wont be
called by the client.
Implement LogClientData as shown in Figure 9. Change the
LogName constant to the path for your log file. This method
begins by opening the log file using AssignFile and then calling
either Reset or Rewrite depending on whether the file exists. The
heart of this method is the call to Writeln which first writes the
date and time to the file.
GetSOAPWebModule returns a reference to the Web module thats processing the current request, so GetSOAPWebModule.Request.RemoteAddr
returns the RemoteAddr property of the Web modules request object.
This property contains the clients IP address. The Writeln call also
writes the RemoteHost property of the TWebRequest object. This property
contains the domain name of the client, if it could be found; otherwise,
it contains the IP address. Now all you have to do to enable logging is
call LogClientData from the GetBinaryFile and GetTextFile methods.

Building SOAP Service Database Applications


If youve worked with DataSnap (formerly MIDAS) youll find
building Web Service database applications has a familiar feel.
Here are the steps to build the DbServer sample application that
accompanies this article:
1) Select File | New | Other from the menu, click the Web Services tab,
and double-click SOAP Server Application.
2) Choose CGI Stand-alone Executable and click OK.
3) Choose Yes in the Create Interface for SOAP Module dialog box,
and name the interface DbServer.
4) Save the project, naming the Web module unit WebModule, and the
project DbServer.
5) Select File | New | Other and return to the Web Services tab of the
Object Repository.
6) Double-click the SOAP Data Module icon, and name the data module
wdmEmployee.
7) Save the project, naming the SOAP data module EmpD.
8) Add a SQLConnection component, two SQLDataSet comps, a
DataSource, and a DataSetProvider arranged as shown in Figure 10.
9) Name the SQLConnection component sconEmployee, and set its
DriverName property to Interbase.
11 August 2002 Delphi Informant Magazine

procedure TfrmMain.btnGetBinaryClick(Sender: TObject);


var
Blob:
TByteDynArray;
F:
TFileStream;
FileName: string;
begin
with Rio as IBlobServer do begin
// Get the byte dynamic arrary from the server.
Blob := GetBinaryFile(editFileName.Text);
// Get the file name to save the file to.
FileName := ExtractFilePath(Application.ExeName) +
ExtractFileName(editFileName.Text);
// Create a filestream.
F := TFileStream.Create(FileName, fmCreate);
try
// Write the file.
F.Write(Blob[0], Length(Blob));
ShowMessage('File written.');
finally
F.Free;
end;
end;
end;

Figure 6: The Get Binary buttons OnClick event handler.


procedure TfrmMain.btnGetTextClick(Sender: TObject);
var
S:
string;
F:
TFileStream;
FileName: string;
begin
with Rio as IBlobServer do begin
// Get the string from the server.
S := GetTextFile(editFileName.Text);
// Get the file name to save the file to.
FileName := ExtractFilePath(Application.ExeName) +
ExtractFileName(editFileName.Text);
// Create a filestream.
F := TFileStream.Create(FileName, fmCreate);
try
// Write the file.
F.Write(S[1], Length(S));
ShowMessage('File written.');
finally
F.Free;
end;
end;
end;

Figure 7: The Get Text buttons OnClick event handler.

10) Open the property editor for the SQLConnection components


Params property and set the Database parameter to the path to your
InterBase EMPLOYEE.GDB file. Normally this is localhost:c:\
program files\borland\interbase\examples\database\employee.gdb.
11) Name the first SQLDataSet sdsEmployee, and set its SQLConnection
property to sconEmployee.
12) Set its CommandText property to SELECT * FROM EMPLOYEE.
13) Name the DataSource component srcEmployee, and set its DataSet
property to sdsEmployee.
14) Name the DataSetProvider provEmployee, and set its DataSet
property to sdsEmployee.
15) Name the second SQLDataSet sdsSalHist. Set its DataSource property
to srcEmployee and its SQLConnection property to sconEmployee. Set
its CommandText property to SELECT * FROM SALARY_HISTORY
WHERE EMP_NO = :EMP_NO.
16) Compile the project and copy DBSERVER.EXE to the scripts
directory of your Web server.

On the Net
TBlobServer = class(TInvokableClass, IBlobServer)
public
function GetBinaryFile(FileName: string): TByteDynArray;
stdcall;
function GetTextFile(FileName: string): string; stdcall;
procedure LogClientData;
end;

Figure 8: The TBlobServer class with the LogClientData method.


procedure TBlobServer.LogClientData;
// Logs the date, time, IP address and host
// name for each connection.
const
LogName = 'c:\_junk\BlobLog.txt';
var
LogFile: TextFile;
begin
// Open the log file.
AssignFile(LogFile, LogName);
if FileExists(LogName) then
Reset(LogFile)
else
Rewrite(LogFile);
Writeln(LogFile, DateTimeToStr(Now) + ' ' +
GetSOAPWebModule.Request.RemoteAddr + ' ' +
GetSOAPWebModule.Request.RemoteHost);
CloseFile(LogFile);
end;

Figure 10: The SOAP data module.

Figure 9: The LogClientData method.

Building the Client


Except for the type of connection component you will use, building the
client application for the SOAP server is no different than building any
DataSnap server:
1) Choose File | New | Application from the menu.
2) Choose File | New | Data Module from the menu.
3) Save the project, naming the data module unit MainD, the form unit
MainF, and the project DbClient.
4) Set the name property of the form to frmMain.
5) Set the name property of the data module to dmMain.
6) Add a SOAPConnection component from the Web Services tab of
the Component palette to the data module as shown in Figure 11.
7) Add two ClientDatasets and two DataSources to the data module.
8) Select the SOAPConnection component. Set its name to
soapEmployee.
9) Set its URL property to the URL of the server application. On my
system the URL is http://localhost/scripts/dbserver.exe/soap/.
10) Set the SOAPConnection components UseSOAPAdapter property to
False.
11) Add components to the main form so that it looks like Figure 12.
12) Add the MainD unit to the forms uses clause.
13) Set the DataSource property of all of the DBEdit components
to dmMain.srcEmployee. Set their DataField properties to the
appropriate field.
14) Set the DBGrids DataSource property to dmMain.srcSalHist.
15) Create OnEnter and OnExit event handlers for the DBGrid as
shown in Figure 13. These methods connect the DBNavigator,
named navEmp, to dmMain.srcSalHist when the grid gets focus and
back to dmMain.srcEmployee when the grid loses focus. This lets a
single DBNavigator serve both datasets.
16) Name the Apply button btnApply and create the OnClick event
handler shown in Figure 14. This code posts any unposted record
by calling CheckBrowseMode and, if there are any changes, calls
ApplyUpdates to update the database.
12 August 2002 Delphi Informant Magazine

Figure 11: The clients data module.

17) The last step is to create the data modules OnCreate event handler,
as shown in Figure 15. This code opens both ClientDataSets when
the application starts. You should now be able to compile and run
the client application.

Overcoming the Limits


SOAP server database applications have two significant limits when
compared to DataSnap applications. First, youre limited to a single
data module. If your application is so large and complex that you need
more than one data module, the only solution is to modularize your
application and write multiple SOAP servers. Since a client can connect to more than one Web Service at once, this should allow you to
build complex applications.
The second major limitation is the lack of a flexible means of
communication between the server and the client. Because
DataSnap applications use COM, you can use the Type Library
Editor to add custom methods to the servers IAppServer interface.
You can call those methods from the client using the AppServer
property of the connection component.
There is no tool analogous to the Type Library Editor in a SOAP server
application, so theres no way to add custom methods to the IAppServer
interface. To customize the interface you have to change the code in the
SOAPConn.pas unit of the VCL. However, there is an easier and safer
way to call methods on the server. To illustrate this technique lets modify
the database application we just wrote.
This technique takes advantage of the OwnerData property that
was added to several event handlers to allow you to pass the information needed to manage connections to stateless DataSnap servers. OwnerData is of type OleVariant, which means it can hold

On the Net

Figure 12: The clients main form.


procedure TfrmMain.DBGrid1Enter(Sender: TObject);
begin
navEmp.DataSource := dmMain.srcSalHist;
end;
procedure TfrmMain.DBGrid1Exit(Sender: TObject);
begin
navEmp.DataSource := dmMain.srcEmployee;
end;

Figure 13: The DBGrids OnEnter and OnExit event handlers.

Figure 16: The client main form with the method call buttons added.
procedure TfrmMain.btnTimes2Click(Sender: TObject);
begin
// Set up the variant array. The first element contains
// the number of the method to call. The second element
// contains the parameter value to pass to the method.
dmMain.CallParams := VarArrayCreate([0, 1], varVariant);
dmMain.CallParams[0] := gTimesTwoMethod;
dmMain.CallParams[1] := 2;
// Call FetchParams to trigger the ClientDataSet's
// BeforeGetParams event handler.
dmMain.cdsCaller.FetchParams;
end;

Figure 17: The OnClick event handler for btnTimes2.


procedure TfrmMain.btnApplyClick(Sender: TObject);
begin
with dmMain.cdsEmployee do begin
// Make sure the ClientDataSet is in browse mode.
CheckBrowseMode;
// If there are changes apply them.
if ChangeCount > 0 then begin
ApplyUpdates(0);
Refresh;
end;
end;
end;

Figure 14: The Apply buttons OnClick event handler.


procedure TdmMain.DataModuleCreate(Sender: TObject);
begin
cdsEmployee.Open;
cdsSalHist.Open;
end;

Figure 15: The data modules OnCreate event handler.

virtually any type and amount of data. That means that if I can
get the client application to fire an event whose before and after
event handlers have an OwnerData parameter, I can pass information to the server in the OwnerData parameter that tells the
server what custom method to execute. The server can return any
resulting information to the client in the OwnerData parameter of
the after event handler.
The first thing we need to make this work is an innocuous event
that we can fire at will. The ClientDataSets FetchParams method
is perfect. All it does is fetch the parameters from the dataset
the provider is connected to on the server. Calling FetchParams
fires the BeforeFetchParams and AfterFetchParams events of the
ClientDataSet and DataSetProvider, and all of these events pass
an OwnerData parameter.
13 August 2002 Delphi Informant Magazine

unit Globals;
interface
const
gTimesTwoMethod = 1;
gTimesThreeMethod = 2;
implementation
end.

Figure 18: The constants that identify the custom methods.

Lets start by making the necessary changes to the server. The


finished version is in the EvntDbServer directory of the sample
code for this article:
1) Add another SQLDataSet component to the servers data
module. Set its SQLConnection property to sconnEmployee and
its CommandText property to SELECT * FROM EMPLOYEE WHERE
EMP_NO = :EMP_NO. You can use any SQL statement you like
as long as it has a parameter. The SQL will never be executed.
Name the SQLDataSet sdsCaller.
2) Add a DataSetProvider, set its DataSet property to sdsCaller, and
its name to provCaller.
3) Compile and save the server application.
Now return to the client:
1) Add a new ClientDataSet component to the data module
and name it cdsCaller. Set its RemoteServer property to
soapEmployee and its ProviderName property to provCaller.
2) Add a public member variable to the data module. Name it
CallParams and make its type OleVariant.
3) Add two buttons to the main form as shown in Figure 16. We will
use these buttons to call the custom methods that we will add to
the server in the final phase of this project. Name the first button
btnTimes2 and the second one btnTimes3.

On the Net
procedure TdmMain.cdsCallerBeforeGetParams(Sender: TObject;
var OwnerData: OleVariant);
begin
// Assign the calling params to the OwnerData parameter.
OwnerData := CallParams;
end;
procedure TdmMain.cdsCallerAfterGetParams(Sender: TObject;
var OwnerData: OleVariant);
begin
ShowMessage('In = ' + IntToStr(OwnerData[0]) + ' ' +
'Out = ' + IntToStr(OwnerData[1]));
end;

Figure 19: The BeforeGetParams and AfterGetParams event


handlers.
procedure TwdmEmployee.provCallerBeforeGetParams(
Sender: TObject; var OwnerData: OleVariant);
begin
case OwnerData[0] of
gTimesTwoMethod: TimesTwo(OwnerData[1]);
gTimesThreeMethod: TimesThree(OwnerData[1]);
end;
end;

Figure 20: The providers BeforeGetParams event handler.

4) Create the OnClick event handler shown in Figure 17 for btnTimes2.


To call a custom method on the server you need to create a variant
array that contains two things. The first is a value that identifies the
custom method you want to call. The second is any parameters the
custom method requires. I decided to identify the methods by an integer number. To make the code more readable, add a new unit named
Globals to the client project, and declare a constant for each custom
method (as shown in Figure 18). This unit should contain nothing
but the constant declarations, so you can use the same unit in both the
client and server applications.
Returning to the btnTimes2 OnClick event handler shown in Figure 17,
the code begins by creating a two-element variant array and assigning
it to the dmMain.CallParams variable. The constant that identifies the
method to call is assigned to element zero of the array. This method takes
a single integer parameter and the parameter value, 2 in this example, is
assigned to the second element of the array. The last line of code calls the
cdsCallers FetchParams method to fire the required events.
Return to the data module, select cdsCaller, and create the
BeforeGetParams and AfterGetParams event handlers shown
in Figure 19. The BeforeGetParams event handler assigns the
CallerParams variable to the OwnerData parameter, so it will be
sent to the DataSetProvider on the server. In this simple example,
the custom method is going to multiply its parameter by two
and return the input parameter and the result in the OwnerData
property of the AfterGetParams event. The AfterGetParams event
handler just displays the returned values using ShowMessage.
Now you can return to the server and implement the custom methods:
1) Go to the data modules unit and add a private member variable
named Return. Make its type OleVariant.
2) Add the GLOBALS.PAS unit from the client to the server project.
Then add it to the uses clause in the data modules unit.
3) Select the provCaller component on the data module, and create a
BeforeGetParams event handler, as shown in Figure 20. This event
handler acts as a dispatcher by examining the first element of the
OwnerData array and calling the appropriate method.
14 August 2002 Delphi Informant Magazine

procedure TwdmEmployee.TimesThree(I: Integer);


begin
Return := VarArrayCreate([0, 1], varVariant);
Return[0] := I;
Return[1] := I * 3;
end;
procedure TwdmEmployee.TimesTwo(I: Integer);
begin
Return := VarArrayCreate([0, 1], varVariant);
Return[0] := I;
Return[1] := I * 2;
end;

Figure 21: The custom methods.


procedure TwdmEmployee.provCallerAfterGetParams(
Sender: TObject; var OwnerData: OleVariant);
begin
OwnerData := Return;
end;

Figure 22: The providers AfterGetParams event handler.

4) Create the two custom methods, TimesTwo and TimesThree,


shown in Figure 21. Note that both of these methods put
the data they need to return to the client in a variant array
assigned to the Return variable.
5) Finally, create the AfterFetchParams event handler for the provider,
as shown in Figure 22. This event handler simply assigns the Return
variable to the OwnerData parameter which is passed back to the
clients cdsCaller AfterGetParams event handler.
This may not be the most elegant way to enable a SOAP database client
to call custom methods on the server, but its easy to implement and it
doesnt require you to hack the VCL source code.

Conclusion
Borland has done a great job of implementing a technology so new
thats its still in a state of flux. And as always the Borland
engineers have made using the technology easy. The Web Service
Application Wizard lets you quickly create a SOAP server that can
be used by any client. The WSDL Importer makes creating client
applications that use any SOAP server a snap.
Building database applications using the DataSnap components is
also easy, although the lack of support for multiple data modules
and calling custom methods on the server makes the process harder
than it needs to be. In spite of these limitations Web Services are
ready to go for Delphi developers.
The eight example projects and associated files referenced in this article
are available on the Delphi Informant Complete Works CD located in
INFORM\2002\AUG\DI200208BT.

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

OP Tech
StringReplace / Optimization / Delphi 4-6

By Peter Morris

Optimizing StringReplace
Part I: The Boyer-Moore String-searching Algorithm

n Delphi, Borland created an excellent IDE and compiler. So its understandable that
programmers trust Borland not to let them down, and often just use the routines the
company has provided. Borland developers are human, however, and make mistakes. The
problem is that once Borland makes a mistake, its difficult (or sometimes even impossible)
for them to correct it. Simply fixing some code in a later version of Delphi could break existing applications that depend on Delphi to act in a predictable way.
Its probably for this reason that Borland wont
change its StringReplace function. Introduced
in Delphi 4, its purpose is to take a string and
replace all occurrences of one word (or substring)
with another. Given the monumental importance
of HTML files, XML documents, etc., text files
have become not only more popular, but also a
lot bigger. Unfortunately, this is the Achilles heel
of StringReplace.
In a simple test, I repeated the sentence, It
was the night before Xmas, and all was quiet.
I repeated it 2,500 times, which made a string
approximately 117KB in size. Then, I used
StringReplace to replace all occurrences of Xmas
with Christmas. It took seven seconds (on a
Duron 700MHz). Thats fairly poor! So lets see
what we can do to improve the situation.
The first thing you could do to improve the
speed of this routine would be to improve the
speed at which it finds text, so lets examine that
operation first. (Note that the technique used
here wont work on multi-byte locales without
modification.)

Searching for Substrings


Lets first look at the standard way of searching for a substring within a source string. As an
example, lets say the substring were looking for
is EXAMPLE, and the source string were searching
is: THIS IS A SIMPLE EXAMPLE.
15 August 2002 Delphi Informant Magazine

The standard, intuitive method is to take the first


letter of EXAMPLE, E, and search for it in the source
string, moving character-by-character, from left to
right. It this example, the first occurrence of E is the
last letter of the word SIMPLE. Here are the substring
and source strings again. The matching letters are
bracketed and vertically aligned to make the logic
easier to follow:
[E]XAMPLE
THIS IS A SIMPL[E] EXAMPLE

Continuing from left to right, however, the next


character in the source string (a space) doesnt match
the second character of the substring, X. Therefore,
we must continue to search for the next occurrence
of E and repeat. Using this method, you can guarantee that you will read every character in the source
at least once. In fact, some characters in your source
string are read more than once once for locating
the first letter, and once while comparing the strings.
Once this example is completed, a total of 25 characters are read from the source string, although it only
has 24. There has to be a better way.

Boyer-Moore Algorithm
The Boyer-Moore string-searching algorithm was
invented in the mid 1970s by Robert Boyer and
J Strother Moore. The algorithm allows you to
find a substring within a source string without
reading every character in the source string. The
first difference between the two methods is that

OP Tech
Boyer-Moore uses the last character of the substring, again E in this
case, as the character to search for in the source string. The next
difference is that it doesnt start at the first character in the source
string; instead, it jumps immediately to the character at position
Length(SubString), the seventh position in this case:
EXAMPL[E]
THIS I[S] A SIMPLE EXAMPLE

We dont get a match, but this tells us much more than that. It also
tells us that there cant possibly be a match at any of the positions
starting before S. We know this because S doesnt occur in the
substring. So we can jump ahead another substring length, i.e. seven
more characters in this example:
EXAMPL[E]
THIS IS A SIM[P]LE EXAMPLE

Again, theres no match. This time, however, the letter P in the source
string does occur in the substring. It occurs two characters from the end
of the substring, so the algorithm jumps two characters, like so:
EXA[MPLE]
THIS IS A SI[MPLE] EXAMPLE

Moving backward through the strings, the algorithm determines


that three more characters (MPL) match before it finds a mismatch
between A in the substring and I in the source string. Again, I does
not occur within the substring, so the algorithm jumps the length
of the substring:
EXAMPL[E]
THIS IS A SIMPLE EXAMP[L]E

Comparing backward, again, the algorithm encounters a mismatch


at the first character. It then determines that the character L occurs in
the substring one position from the end, so it jumps forward a single
character, and makes a comparison:
[EXAMPLE]
THIS IS A SIMPLE [EXAMPLE]

Its a match! In this example, the algorithm read only 17 characters from the source string, plus a single lookup from a table to
retrieve the number of characters to look ahead when theres a
mismatch (there were four mismatches).
This is only a simple example, yet it requires 23 percent fewer character reads of the source string. When searching for a large substring,
the savings can be greater. This is good stuff, so lets see how we can
implement it in Delphi.

The Delphi Implementation


First, lets build a table to tell us how many characters to look
ahead, given a specific character (see Figure 1). The table is built
only once. Note: Given the overhead of building a lookup table
in advance, the routine is beneficial if the substring exists near
the end of a large source string, or if youll be searching for the
substring many times.
Next comes the search routine itself, shown in Figure 2. Note that
the routine uses PChars instead of strings. This is done because
a PChar is a pointer (that will be used later with routines such as
16 August 2002 Delphi Informant Magazine

type
TBMJumpTable = array[0..255] of Integer;
...
procedure MakeBMTable(const Buffer: PChar;
const BufferLen: Integer; var JumpTable: TBMJumpTable);
var
I, Distance: Integer;
SourceChar: PChar;
begin
if BufferLen <= 0 then
raise EBMError.Create('Buffer length is 0');
// For each character not in the search string
// (Buffer), jump the length of the search string.
for I := 0 to 255 do
JumpTable[I] := BufferLen;
// For each character in the buffer, note how far
// it is from the end of the buffer.
Distance := BufferLen - 1;
SourceChar := Buffer;
for I := 1 to BufferLen - 1 do begin
JumpTable[Ord(SourceChar^)] := Distance;
Inc(SourceChar);
Dec(Distance);
end;
end;

Figure 1: Building a character-look-ahead reference table.

function BMPCharPos(const Find, Source: PChar;


const FindLen, SourceLen: Integer;
var JumpTable: TBMJumpTable): PChar;
var
EndOfSource: PChar;
CurrentCheckStart, CurrentCheckEnd: PChar;
begin
Result := nil;
EndOfSource := Source + SourceLen - 1;
CurrentCheckStart := Source;
CurrentCheckEnd := Source + FindLen - 1;
repeat // Compare then exit.
if CompareMem(CurrentCheckStart, Find, FindLen) then
begin
Result := CurrentCheckStart;
Exit;
end;
Inc(CurrentCheckStart,
JumpTable[Ord(CurrentCheckEnd^)]);
Inc(CurrentCheckEnd,
JumpTable[Ord(CurrentCheckEnd^)]);
until CurrentCheckEnd > EndOfSource;
end;

Figure 2: The search routine.

Move), whereas variables of type string require us to calculate an


address to pass to Move (and operations such as @SourceString[X]
create a lot of overhead when performed in a loop).
The first thing to note is that your jump table is passed to you as a
parameter. Moreover, its passed by reference, which is quicker than
passing the value of a whole array. This is to avoid having to calculate
the jump table each time unnecessarily:
1) Note the memory address of the last character in the string:
EndOfSource := Source + SourceLen - 1. If youre trying to
compare beyond this point, youve exhausted the source string
without finding a match.
2) Start to compare the substring with the source string.
3) If you find a match, exit.
4) If not, look in your jump table to see how many places you can
jump forward, then repeat step 2.

OP Tech

Figure 3: The test application at run time.


function POSStringCount(const SubString, SourceString:
string; const CaseSensitive: Boolean): Integer;
var
I, SubStringLength: Integer;
Patt, SearchStr: string;
begin
Result := 0;
if not CaseSensitive then
begin
Patt := UpperCase(SubString);
SearchStr := UpperCase(SourceString);
end
else
begin
Patt := SubString;
SearchStr := SourceString;
end;
SubStringLength := Length(SubString);
I := Pos(Patt, SearchStr);
while I > 0 do begin
Inc(Result);
// Pos has no FindNext capability, so you're
// forced to delete text from the string.
Delete(SearchStr, 1, I + SubStringLength - 1);
// Check for a next occurrence.
I := Pos(Patt, SearchStr);
end;
end;

function BMStringCount(const SubString, SourceString:


string; const CaseSensitive: Boolean): Integer;
var
SourceLength, SubStringLength: Integer;
JumpTable: TBMJumpTable;
SourcePChar, FindPChar, FoundPositionPChar: PChar;
Find: TBMSearchRoutine;
begin
Result := 0;
SubStringLength := Length(SubString);
SourceLength := Length(SourceString);
if (SubStringLength > SourceLength) or
(SubStringLength = 0) then
Exit;
// Start at the beginning of both strings.
SourcePChar := @SourceString[1];
FindPChar := @SubString[1];
if CaseSensitive then
begin
Find := BMPCharPos;
MakeBMTable(FindPChar, SubStringLength, JumpTable);
end
else
begin
Find := BMPCharPosNoCase;
MakeBMTableNoCase(FindPChar, SubStringLength,
JumpTable);
end;
// Repeat until there is no room for SubString.
while SubStringLength <= SourceLength do begin
FoundPositionPChar := Find(FindPChar, SourcePChar,
SubStringLength, SourceLength, JumpTable);
// If not found, then Exit.
if FoundPositionPChar = nil then
Break;
// Note how many characters left to search.
Dec(SourceLength, (FoundPositionPChar - SourcePChar) +
SubStringLength);
// Search from after the last occurrence.
SourcePChar := FoundPositionPChar + SubStringLength;
Inc(Result);
end;
end;

Figure 4: Deleting characters is essential in this routine, adding


unwanted overhead.

Figure 5: Optimized Boyer-Moore method.

Now that weve implemented the algorithm in Object Pascal, we can


write a test application to time the differences between using it, and
using the standard Pos and Delete routines. The finished test program is
shown in Figure 3. The program is also available for download; see the
end of this article for details.

the beginning of the temporary variable used to hold your source


string, to avoid Pos from returning the same value in an infinite
loop (see Figure 4).

The following implementations were used to count the number of


occurrences of Xmas within It was the night before Xmas, and all
was quiet when repeated 5,000 times.

Using the Pos Function


First, I used a routine with a method similar to the one used in
Borlands StringReplace. This involves:
1) Making a copy of the substring and source string.
2) Converting both to uppercase if required.
3) Finding the position of the substring using Pos.
4) Exiting if you cant find it.
5) Deleting the contents of your source string, up to and including
the substring.
6) Incrementing Result.
7) Repeating step 2.
One problem with this technique is about more than just the
speed at which Pos can find a substring within a source string.
Each time a substring is found, you must delete characters from
17 August 2002 Delphi Informant Magazine

This is what happens internally when Delphi creates a new string:


Enough memory is allocated to fit the size of the new string.
Each character in the new string is moved physically to that
memory location from the source via the Move procedure.
Considering that youre using the Delete procedure 5,000 times,
this is a lot of work. Its also one of the problems with StringReplace,
which well cover in more detail next month.

Boyer-Moore Again
Second, I used a function featuring the Boyer-Moore search routine
and which uses a kind of FindNext implementation (see Figure 5).
The routine uses a pointer to the start of the string rather than altering the string itself. This involves:
1) Setting a pointer to the start of the substring, and another to the
start of the source string.
2) Creating your jump table. (Notice you do not need to convert
each character in the source string and substring to uppercase.)
3) Finding an occurrence of the substring in your source string.
4) Incrementing Result.

OP Tech
5) Setting the source strings size to the amount of characters
left from the end of this occurrence to the end of the actual
source string.
6) Incrementing the pointer at the start of the source string so that it
points to the character after the current occurrence. (Technically,
you have done a Delete without reassigning your source string.)
7) Repeating the third step.
The results of this exercise were that the Borland method took 2.3
seconds, and the Boyer-Moore method (without reassigning the
source string) took a mere 10 milliseconds (on my feeble Duron
700MHz).

Conclusion
Now that you are more familiar with the inner workings of string
assignment, next month well look at Borlands implementation of
StringReplace, in particular some obvious inefficiencies that slow it
down. See you then.
The osFastStrings.pas unit and demonstration project referenced in this
article are available on the Delphi Informant Complete Works CD located
in INFORM\2002\AUG\DI200208PM.

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

18 August 2002 Delphi Informant Magazine

Informant Spotlight
Microsoft .NET Framework / Delphi 7 / Delphi for .NET

By Jon Shemitz

Borlands .NET Plans


D7 and Delphi for .NET

he new, developer-oriented Borland is much less secretive than it used to be. This
started with Kylix and has accelerated with .NET. There were some great sessions
at BorCon about the case for .NET and Borlands .NET plans. This article provides a
brief summary of both.
This article is mostly about .NET and Borlands
.NET plans, though I also talk a bit about the
syntax changes well start to see in Delphi 7 and
in the Delphi for .NET preview that will ship
with D7. Most of this comes from Danny Thorpes
two language preview talks, Chuck Jazdzewskis
Moving Delphi talk, and Microsofts Jim Hoggs
Inside the CLR talk, but I also draw a bit from
the Anders Hejlsberg and James Gosling keynotes.

Why .NET?
You may still be wondering how much attention
you should be paying to .NET. (It wasnt all that
long ago that all I really knew about .NET was that
it was Microsofts response to Javas server-side dominance.) So, before I get to the Delphi syntax stuff,
Ill just run through a couple of common questions
and one common assertion about .NET.

What Makes This Any Better Than Java?


Microsoft believes that Javas Achilles heel is Java
itself. That is, while the environment is great, you
cant simply port legacy code to Java you have
to rewrite it. Now, maybe the dot coms didnt have
any code that was more than a year or two old, but
thats far from typical. Most companies depend on
legacy systems that may be years, or even decades,
old. They can maybe be talked into porting their
legacy code to a managed code system, but they
definitely cant afford to rewrite all their legacy
systems in a new language.
What .NET offers is all the advantages of Java,
plus language neutrality. All .NET languages use
the same object-oriented run-time library, and
youll have to learn that. But learning a new language is easy its learning the new library thats
19 August 2002 Delphi Informant Magazine

hard. Once you know the new library, you can


easily work in whatever language the legacy code
du jour was written in. This is Microsofts way of
embracing and extending one of Javas big selling
points: Learn once, work anywhere.
All languages that implement all of the Common
Language Specification (CLS) are first-class languages.
.NET programs include extensive RTTI (known as
metadata) that lets, say, a C# program instantiate a
Visual Basic object that inherits from a Delphi object.
Metadata also means that there are no headers to
translate, so there isnt the sort of implementation language bias that there is with Windows and Linux. All
first-class languages can use all of the .NET API (i.e.
the Framework classes), not just the parts that have
had their headers translated. All first-class languages
can use any future .NET APIs as theyre released,
without having to first either translate the header, or
find a third-party translation. Since you dont need
header translation, theres no danger that the translation is wrong in various obscure ways.

What about That JIT Overhead?


While interpreted scripting languages are more and
more popular, most of us still believe that a commercial program needs to be as zippy as possible.
It doesnt matter if todays 2.5 gigahertz processor
executes interpreted code faster than yesterdays 500
megahertz processor executes compiled code. What
matters is that on any given machine an interpreted
program is slower than a compiled program, and
all other things being equal users will prefer
snappy programs to sluggish programs. Thats why
many programmers still find it pretty hard to get
comfortable with Just In Time (JIT) compilation of
intermediate code, despite Javas success with the idea.

Informant Spotlight

Figure 1: The heap, before (above) and after (below) a generation 0 garbage collection.

Of course, the system only has to compile Common Intermediate


Language (CIL, formerly known as MSIL for Microsoft Intermediate Language) byte codes to native object code the first time it
runs a routine. After that, it can just use the compiled code. The
surprising thing that Danny told us is that the .NET jitter actually compiles about as fast as Delphi. After all, its compiling from
pre-parsed byte codes, and it doesnt have to deal with either linking or C-style macro expansion. At Delphi-level compile speeds,
JIT time seems a rather modest addition to the sort of demand
load time we accept today.
The jitter doesnt just compile as fast as Delphi; it actually compiles
a bit better. The jitter does all the optimizations that Delphi does,
plus it does function inlining. Also, since jitted code will only
execute on the machine that does the jitting, it can optimize for the
actual processor that will run the code. You never have to choose
between optimizing for new machines (and pessimizing for old
machines) or optimizing for old machines (and pessimizing for new
machines). Finally, a jitter is by definition a sort of smart linker; it
only compiles (and uses memory) for the code thats actually used.
These arent huge benefits, given that memory is cheap and algorithms are more important than compiler quality, but to me they
outweigh the relatively low cost of JIT compilation.

Garbage Collection Sucks


Garbage collection offers some nice features:
Allocation is fast, as the system is just advancing a pointer,
not manipulating a linked list.
Consecutive allocations such as a routines local variables,
or the sub-objects you create in a constructor are adjacent,
not scattered all over the heap, which helps cache performance.
Code is smaller, simpler, and more reliable, because you never
have to worry about who owns a block, and because you never
have to free the memory you allocate.
Your code never has memory leaks.
Data structures never refer to memory thats been freed.
But none of these matter if garbage collection means that your
program might lock up for several seconds anytime it gets asked
to do something. Right? Right. But thats not how .NETs garbage
collection acts; a full, three-generation .NET garbage collect takes
less time than a single page fault.
20 August 2002 Delphi Informant Magazine

Garbage collection can be so fast because memory life spans are distributed according to a power law. Most memory is freed quite soon after
its allocated. Most of whats left is freed within seconds or minutes. And
most of what lasts longer than that lasts until the program shuts down.
So, .NET has a three-generation garbage collector. When the system
has done enough recent allocations, it triggers a generation 0 garbage collect. This looks only at the most recently allocated blocks,
and finds the ones that are still in use. At this point, the system
only has to pay attention to the blocks that arent garbage. These get
moved down to the bottom of the partition, and promoted to generation 1, which means that the next generation 0 collection wont
look at them. Once all the current data has been promoted and
moved to the bottom of the partition, whats left is free memory (see
Figure 1). By default, the generation 0 garbage collection threshold
is the size of the CPUs L2 cache at least in principle, short-lived
allocations never even make it to main memory.
When the set of generation 1 blocks exceeds a different, larger threshold or a generation 0 collection cant make enough room the
system does a generation 1 collection, which finds all the blocks that
have become garbage since being promoted to generation 1. All survivors are moved and marked as generation 2, and wont be touched
again until the system does a generation 2 garbage collection (see
Figure 2). A generation 2 garbage collection just moves the surviving
blocks down; it does not promote them to generation 3.
As you can see, this three-generation garbage collection minimizes
the time the system spends repeatedly noticing that a long-lived
object is still alive. This in turn, greatly reduces the number of times
a long-lived block gets moved. The idea of generations also saves time
in a more subtle way. The way the system detects that an object is still
live is to walk every reference from a set of roots on down. (It can
do this because it has type data for every structure in the system, i.e.
it knows every field of every structure.) This walk can stop as soon as
it reaches an object that is a higher generation than the garbage collection, e.g. every reference in a generation 1 object is to a generation
1 or 2 object, which a generation 0 sweep doesnt care about.

That Delphi Feeling


Once youve bought into .NET, you start to wonder how Borland
expects to prosper in a .NET world. (If you write books or components, you certainly hope that Delphi for .NET wont be like Kylix, a

Informant Spotlight

Figure 2: The same heap from Figure 1, before (above) and after (below) a generation 1 garbage collection. Some blocks have turned
to garbage, and some new blocks have been allocated.

niche of a niche, a product that only appeals to a small portion of the


small Delphi market.) The short answer is that Borland thinks that
in a world where all languages are essentially equal where they all
use the same run-time library, and they can all use new APIs without
translation theyll be able to convince people that Delphi is the
best development environment. I dont know if theyll be able to pull
this off, but Im very glad theyre going to try, and wont just settle for
maintaining their existing market share.

Pascal as it is to know Ruby or Python. I suspect Borlands going to


have a hard time increasing Delphis market share until Borland can
find a way to tell the world what Delphi users all know that Object
Pascal is a wonderfully expressive language, full of safeguards against
various common careless errors. Perhaps people benefiting from managed code will be more receptive to the Pascal idea that being very
explicit about your intentions helps prevent mistakes.

In this vein, Chuck talked about what goes into the Delphi feel.
I think there were three main points, here. Chuck emphasized
that the component model is probably the single biggest part of
the feel. It contributes to the tactile nature of Delphi programming theres something you can look at and touch, even for
something abstract like a timer or a database connection and
most Delphi programmers are more component oriented than
object oriented. In fact, Chuck said that you can be a successful Delphi programmer without ever using inheritance. Hes
probably right: While some of the most interesting parts of various projects have required inheritance, Ive certainly done plenty
of bread-and-butter apps where inheritance didnt come in at all.

Since the February 2002 announcement that Borland would port


Delphi to .NET, many of us have referred to the new product
as Delphi.NET. Apparently, Microsoft objects to this name
(probably on trademark dilution grounds) and the product will
be named Delphi for .NET. I guess well all abbreviate it as DfN
(certainly DfN2 isnt as obviously funny as Delphi for .NET 2).

Somewhat more broadly, Chuck says that one of the most important questions to ask when comparing programming environments
is: How many things do you have to remember? The more things
you have to remember, the easier it is to forget one and make a serious mistake. Delphi promotes a contract-less programming style
that encourages you to freely mix components without having to
worry much about how they ought to be used.
Finally, with Delphi you Debug the code you wrote ... and only
the code you wrote. Your code isnt cluttered with lots of toolgenerated code, as in C#. Despite some recent online discussions
about how building a form with run-time code can be faster (less
string lookup), and can make for smaller executables (no need to
link in unused property setters) than Delphis resource streams,
Delphi will continue to use resource streams.
One thing that struck me about the Delphi feel part of Chucks
Moving Delphi talk was that Chuck might almost as well have been
talking about Visual Basic. Chuck did talk a bit about the difference
in the C and Pascal notion of types, but otherwise he didnt say all that
much about Pascal. Unfortunately, somehow its just as chic to disdain
21 August 2002 Delphi Informant Magazine

Delphi for .NET

DfN will produce 100 percent managed code. That is, it will generate
only CIL that you can run through the Microsoft PEVerify utility to
prove that it is memory type safe. Type-safe code plays by the rules,
and doesnt do tricks like read other objects private fields. .NET code
doesnt have to be verifiably type safe, but unsafe code requires special
permissions to run. For example, the Microsoft WinForms classes,
which are how you build GUIs in .NET, are unmanaged code that
ultimately call the existing Win32 libraries like User32, ComCtrls,
and RichEd. They come signed by Microsoft in a way that asserts
that theyre just as safe as the libraries they call. These libraries may
not be perfect, but youve been using them for years.
With Delphi for .NET, youll be able to write WinForms applications, just as you can with the Microsoft Development Environment
(aka Visual Studio .NET). Borland will also write a version of the
VCL for .NET the familiar Delphi control set implemented
as managed code that ultimately calls the existing Win32 libraries
like User32, ComCtrls, and RichEd. This library will be signed by
Borland in the same way that WinForms is signed by Microsoft. My
guess would be that VCL for .NET wont be 100 percent compatible with the existing native VCL, but since its ultimately calling the
same libraries that the VCL does now, it should be more compatible
than CLX, which has to implement VCL-like behavior using Qt.
Whether your desktop application uses WinForms controls or the
new VCL for .NET controls, it will be able to use all the functionality of the Microsoft framework classes the new, object-oriented

Informant Spotlight
API that covers everything from Web Services and object serialization, to a nice set of collection classes and Perl-compatible regexes.
Your decision to use WinForms or VCL for .NET for any given project will be based mostly on your plans for that project: WinForms
projects should run on any desktop platform that supports .NET
(like IA64 platforms in a couple of years, and possibly OS/X or
Linux) while VCL for .NET projects will require a Windows desktop
host, but can be more easily ported from (and to) Delphi or Kylix.
Delphi for .NET will ship sometime in 2003. D7, which will ship in
2002, will include a Preview Edition of Delphi for .NET, which will
include (at least a preliminary version of) the VCL for .NET library and
the DCCIL command-line compiler, which compiles Delphi source
to .NET assemblies. (A .NET assembly is a standard Microsoft PE
[Portable Executable] file, which contains CIL byte codes and metadata
instead of Intel object code. Every entry point in the PE file points to a
stub which JIT compiles the code and then modifies the entry point so
that future calls just go straight to the compiled code. Metadata is basically just a generalized and extended version of Delphis RTTI.)
While obviously the Preview Edition will be a lot harder to use
than the eventual full blown Delphi for .NET, youll be able to
use it to build .NET applications in Delphi. In particular, youll
be able to embed Delphi code in ASP documents. (The audience
was very impressed when Anders did this during his keynote.)

New Syntax
There will be language changes. Many of the changes are necessary
to support .NET, but others seem to be coming because the compiler
guys want to do some cleanup while they make big changes. These
language changes will also appear in future versions of Delphi for
Windows and Kylix to maximize portability between Delphi for
.NET and the classic versions of Delphi (the ones that generate
native Intel code). However, while classic Delphi programs will be
able to use Delphi for .NET syntax, they wont have access to the
.NET run-time library (the Framework classes) or to standard .NET
object methods like ToString and HashCode.
In .NET, Delphis TObject will be a System.Object. (If Borland
didnt map TObject this way, then TComponent wouldnt be a
System.ComponentModel.Component, and Delphi components
wouldnt be playing in the language-neutral space.) Since TObject
does have methods like ClassName and Free that System.Object
does not, Borland is adding the concept of helper objects:
type
THelper = class helper for TObject;

THelper lets you add methods to TObject that subsequently look as


if they are declared in TObject. In particular, every method added to
a class, whether TObject or one of its descendants, is available to any
descendant of that class. There will be some scope restrictions, but this
sounds like a great new feature with lots of interesting possibilities.
Well also get several C# language features which are used extensively
in .NET. Attributes are basically a way to inject custom metadata
into your assemblies. This metadata is available at run time, and can
control things like how an object is represented in XML. You can also
define new attributes to add any custom metadata you want to your
code. Syntactically, attributes are text in square brackets before the
method or type they apply to. For example:
[Description('This string can be read at run time.')]

22 August 2002 Delphi Informant Magazine

creates a Description attribute with a string parameter. At run time,


you can use the reflection API to retrieve both the Description attribute and its text from the assemblys metadata.
A delegate is the .NET version of a Delphi event handler, an object/
method pair which is called whenever the right sort of thing happens.
The big difference is that where Delphi events always have zero or one
handler, .NET events are always multicast. That is, each event can have
multiple handlers, much as in Qt with its signals and slots. Delphi for
.NET will support both the traditional VCL-style single-recipient syntax,
and the .NET multiple-recipient syntax. The traditional OnEvent :=
Handler syntax will still work, and the Include and Exclude set operators
will be extended to allow you to add and subtract multicast handlers.
The two systems will be completely orthogonal: Setting or clearing an
event handler wont affect the multi-cast list, and editing the multi-cast
list wont affect the traditional single-cast handler.
There are no variants in .NET, and boxing and unboxing are a big
reason why. Where a language like Perl allows you to store any scalar
(a typed value much like a variant) in a hash or an array, .NETs
containers all hold objects. When you supply a value type, such as
a number, where an object is expected, the system automatically
boxes the value into an object. The boxed value then has functional
versions of all System.Objects methods, like Equals, ToString, and
HashCode. (Yes, this means you can use dates and numbers to index a
hash.) When you need the original value back, you unbox it by casting the boxed object to the type it holds. (There is no such thing as
an unsafe (blind) cast in .NET. All casts act like Delphis as operator;
theyre checked against RTTI to make sure the cast is valid.) Delphi
for .NET will support boxing and unboxing. It will also include
something like Kylixs portable variants library, so that legacy variant
code can be ported easily.
The Variant datatype will be only one of the Delphi constructs that
arent part of the CLS (Common Language Specification) requirements.
Others include sets and unsigned integers. Some .NET languages (like
C#) can understand unsigned types, but others (like Visual Basic) cant.
Dont expect many if any non-Delphi programs to be able to
understand Delphis variants and sets. You can expose non-CLS data
types as part of a cross-language component, but you should always
provide CLS-compliant ways to access the non-compliant features. For
example, a cross-language component that exposes a set property might
include methods to add and subtract elements, and to test whether an
element is in the set. Delphi for .NET will be able to warn you when
youre exposing non-CLS-compliant data types.
.NET features hierarchical namespaces. Flat namespaces means
that every namespaces name potentially conflicts with every other
namespaces name, e.g. two vendors cant both ship a Collections
namespace. With hierarchical namespaces, we have the System
namespace, as well as any number of additional namespaces rooted
in vendor trademarks. Thus, the System.Collections namespace doesnt
conflict with a (hypothetical) Microsoft.Collections namespace, or a
(hypothetical) Borland.Collections namespace.
Delphi for .NET will support hierarchical namespaces. Well be able
to use dotted names in uses clauses:
uses System.Collections;

and then use those dotted names as part of a fully qualified name in code:
HashTable := System.Collections.Hashtable.Create;

Informant Spotlight
Since those dotted names can get pretty long, well be able to define
aliases in the uses clause:
uses System.Drawing.Text as GuiText;

and then use the alias in a fully qualified name:


FontCollection := GuiText.FontCollection.Create;

The CTS specifies case-insensitive Unicode identifiers, and Delphi


for .NET will go along with this. Programmers in non-Englishspeaking countries will be able to use Unicode identifiers in their programs. However, Unicode ideograms such as Chinese and Klingon
wont be allowed (see http://www.midnightbeach.com/dotnet for a
link to an explanation).
Since Delphi reserved words, such as label and case, arent necessarily reserved words in other .NET languages, youll be able to
use reserved words as part of qualified names. (The first word in
the qualified name cant be a Pascal reserved word. Tough luck for
Procedure(tm) Corp. customers.) Thus, if the FooCo.Whizzamatic
class has a Label identifier, youll be able to write code that refers
to FooCo.Whizzamatic.Label. Therell be some sort of escape
mechanism, so that you can do things like override the Label
property when you inherit from the FooCo.Whizzamatic class.

New Class Syntax


There will be a number of enhancements to class syntax. Well get
nested types, where a class like Foo can define a helper class like
Bar, whose identifiers are only visible within Foos methods. For
example:
type
Foo = class
type
Bar = class
procedure Method;
end;
end;
procedure Foo.Bar.Method;
// Can only be called within a Foo method.
begin
end;

Along the general theme of more dots, there will be a new


syntax option for method resolution clauses. Where we now
have to write declarations that separate method declaration from
method resolution:
type
TThis = class (TInterfacedObject, IThis)
procedure IThis.Foo = ThisFoo;
procedure ThisFoo(N: Integer);
end;

well be able to write code that merges method declaration and


method resolution:
type
TThis = class (TInterfacedObject, IThis)
procedure IThis.Foo(N: Integer);
end;
procedure TThis.IThis.Foo(N: Integer);
begin
end;

23 August 2002 Delphi Informant Magazine

Well be able to have class properties, not just class methods. Well be
able to declare static class methods, which wont have a Self parameter
that reveals the type of class that the method was actually invoked on.
Much like in Java and C#, well be able to declare sealed classes that
cant be inherited from, and final methods that cant be overridden.

Changes to Record Syntax


There will be two really wonderful changes to the record syntax,
which will preserve the useful parts of the old-style object model
and allow the object keyword to go away. First, records will be able
to inherit from records. This means that new fields are added into
a flat namespace, just as with object inheritance, and you end up
with simple identifiers like This.Name instead of very dotty, nested
identifiers like This.That.TheOther.Name. This can be a very legible
alternative to variant records for structures with a common header
and a variable tail.
Second, records will be able to have static methods. Thus, when you
find you have a lot of routines that take a record type as a parameter,
you can just make them methods of the record instead. This gives
you many of the syntactic advantages of objects without any of the
dynamic memory overhead: local records are allocated on the stack,
and theres no cost to Create or garbage collect them; an array of
records is a single data structure, not an array of pointers to objects.
The interaction between inheritance and methods is a bit complex.
As I understand it, records will be able to inherit only from records
that dont contain methods. As soon as a record contains methods, its
sealed and cant be inherited from.

New Semantics
.NET is a garbage-collected environment. That means we can all forget
the old Free-What-You-Create rule. I imagine that Borland will build
some sort of mechanism so if ported code does Free an object, the garbage collector wont try to run the destructor again during finalization,
but thats just me Borland didnt say anything about it at BorCon.
Garbage collection also means that Delphi doesnt need to use reference counting to Free strings, dynamic arrays, and interfaces. Those
types will still be available, and with one exception will act the
same, but they will no longer be reference counted. This means that
assignment of every one of these types will be faster; it also means it
will take less time to call and return from every routine that now has
reference-counted local variables.
If you dont explicitly Free an object, its destructor only gets called
when the garbage collector needs to reclaim the space. Thus, one
big difference between interfaces in Delphi and Kylix and interfaces in Delphi for .NET is that on .NET an interfaces destructor
doesnt get called the instant the last reference goes away. That
is, if youve been using interfaces for resource protection, current
plans mean that your code will break. If you open a file in append
mode and wrap the handle in an IStream interface, your file wont
close as soon as the stream is all written. If you store the current
Cursor in an interfaced object before changing Screen.Cursor,
the system Cursor wont be reset when the procedure that set it
returns; itll be reset at some random time later, perhaps after
some other code had made its own changes.
If losing reference counting would break your code in this way, you
probably ought to let Borland know. Its at least theoretically possible to have some not all interfaces use reference counting on
.NET, and Borland may do so if it keeps enough code from breaking.

Informant Spotlight
Dead Syntax
In addition to the changes Ive just sketched, some existing syntax
simply wont work on .NET. .NET is all about managed, type safe
code, where the system knows the layout of every structure it may
garbage collect. Thus, the untyped memory functions GetMem,
FreeMem, and ReallocMem wont be supported under .NET. (New
and Dispose will still be supported.) Similarly, untyped var parameters
and the @ and Addr operators will be illegal in Delphi for .NET.
As I understand it, on .NET the record alignment strategy, and hence
the actual size of a record, will depend on the current hardware platform.
This is why you wont be able to use the file of type syntax on .NET.
Using .NETs SOAP serialization code to stream records and objects
in a portable manner is the closest youll be able to get to this type of
random-access record-oriented file. SizeOf may also be affected; it may
not be a compile-time constant on Delphi for .NET. The impression I
got is that this is one of those things thats still being researched.
Obviously enough, inline x86 assembler (BASM) wont be legal
on .NET. Current plans are that Delphi for .NET wont include a
BASM that understands CIL, either. Im not sure if this is a costof-writing-a-CIL-assembler issue, or a more fundamental matter
of BASM being less useful when even your hand-written CIL gets
compiled by the jitter. .NET does include the System.Reflection.Emit
namespace, which allows you to generate CIL at run time.
It may not be possible to implement virtual constructors on .NET.
Obviously, this would break all sorts of sophisticated code, and so
Borland would like to implement this, but apparently there are some
technical barriers they may not be able to overcome. If necessary,
code that currently uses virtual constructors can be rewritten to use
RTTI to find the appropriate constructor.
Finally, some old syntax thats been deprecated for a while the
absolute keyword, the real48 data type, the whole ExitProc chain, and
the old-style object keyword will no longer be supported at all.
Special thanks to Stefan Hoffmeister and Rick Ross for valuable feedback
on the draft of this article.

Jon Shemitz is a consultant and author who lives in Santa Cruz, CA. He is the
author of Kylix: The Professional Developers Guide and Reference (Apress,
2001) and is working on a Delphi for .NET book. You can contact Jon at
http://www.midnightbeach.com.

24 August 2002 Delphi Informant Magazine

Delphi at Work
Web Browser / Delphi 6

By Paul Joseph King

Building a Browser
Implementing a Custom Web Browser with Delphi 6

elphi 6 allows for syntax overloading in subroutines. As a result, the Web browser
ActiveX control based on IE6 provides multiple ways to call several TWebBrowser
methods, including Navigate.
If you have Internet Explorer 6 (IE6) installed
on your machine, youre better off importing its
native ActiveX control, especially if you want to
take advantage of security features. In the code
provided (see end of article for download details),
Ive named the ActiveX version of the Explorer
object TMSIE6WebBrowser, rather than the default
TWebBrowser, because that is the name of the
existing browser Delphi 6 offers on the Internet

page of the Component palette. TWebBrowser


is more reminiscent of the Internet Explorer 5
interface. The API for the TWebBrowser and my
TMSIE6Browser are similar except for the security
features my program needs.

The Basic Web Browser


Figure 1 shows PJKBrowse in design mode. It
has two toolbars, two pop-up menus, a status
bar, an image list, and the Web browser ActiveX
object itself. Placing all controls directly on the
form makes it easy to initialize the Web pages to
visit on start-up. It also allows for initialization
of other settings, such as a list of bookmarks I
will describe later.
Heres the code for initializing the Web browser:
procedure TfrmBrowser.FormCreate(Sender: TObject);
// Things to do as the program loads.
begin
WebBrowser1.GoHome;
Caption := progTitle;
readBookmarkFile(curDir);
end;

Saving and Managing Bookmarks

Figure 1: PJKBrowse in design mode.


25 August 2002 Delphi Informant Magazine

This program was designed to save bookmarks


when the program exits, either from the toolbar
button, or from the X button on the title bar.
An example bookmark file is shown in Figure 2.
(The ampersand insertion appears to be a default

Delphi at Work
&Halton School Board - Upload Tool Login Page
https://upload.haltondsb.on.ca/
&ibiblio - March
http://www.ibiblio.org/
&Welcome to Paul King's Web Page
http://www3.sympatico.ca/pking123/
&Borland Home Page
http://www.borland.com/
&Google
http://www.google.ca/
&OLGA - The On-Line Guitar Archive
http://www.olga.net/

Figure 2: This example bookmark file shows the menu captions


and their paired URLs.
procedure TfrmBrowser.tbBookMarkClick(Sender: TObject);
begin
WebBrowser1.createMenuItem(LocationName, LocationURL);
end;
procedure TfrmBrowser.createMenuItem(title, URL: string);
// Creates a dynamically-generated bookmark Pop-up menu.
// Also creates the menu item which deletes it.
var
item, item2: TMenuItem;
begin
if isUniqueBookmark(URL) then begin
item := TMenuItem.Create(Self);
item.Caption := title;
item.Hint := URL;
item.OnClick := miBookmarkClick;
item.ImageIndex := 0;
pmBookmarks.Items.Add(Item);
item2 := TMenuItem.Create(self);
item2.Caption := title;
item2.OnClick := miDeleteClick;
item2.ImageIndex := icoDel;
miDeleteBookmarks.Add(Item2);
bmIndex := bmIndex + 1;
end;
end;

Figure 3: Dynamically generated bookmarks.

behavior of TComboBoxEx.) Bookmarks are implemented as a


dynamically generated pop-up, which is deployed as an empty menu
at design time. Its contents are added dynamically as the user decides
to bookmark sites. Routines must be written to weed out duplicate
entries, and the user must be able to delete unwanted bookmarks.
The flat, ASCII, carriage-return-delimited bookmark file (that
is, one URL or one page title per line of data) gives the end user
added flexibility. You can edit the file in a text editor such as
Notepad safely, as long as you respect this format: one line for
the title, one line for the URL. From there, you can modify the
page titles and even reorder URL/title pairs. The file is read using
Readln statements inside a loop.
The first time you run PJKBrowse, theres no bookmark file and
clicking on the Go To button for bookmarks will do nothing. You can
still save and use bookmarks, but the bookmark file wont be written
until the browser exits.
To create a bookmark, click the Bookmark button. That invokes
the tbBookMarkClick event handler of the toolbar button tbBookMark, which, in turn, calls the private procedure createMenuItem.
As its name suggests, createMenuItem actually creates the menu
item (see Figure 3).
26 August 2002 Delphi Informant Magazine

procedure TfrmBrowser.miDeleteClick(Sender: TObject);


var
idx : Integer;
begin
idx := miDeleteBookmarks.IndexOf(Sender as TMenuItem);
(Sender as TMenuItem).Destroy;
pmBookmarks.Items.Delete(idx);
bmIndex := bmIndex - 1;
end;

Figure 4: This code handles bookmark deletion.

Two menu items are created: one for the bookmark menu and one for
the bookmark-deletion menu. These are anonymous objects, because
they will be added to the bookmarks pop-up menu pmBookmarks using
the pmBookmarks.Items.Add method.
TMenuItem.Create(Self ) creates an instance of a menu item and
stores it in a pre-declared TMenuItem named item. Its Caption property gets the page title, and its Hint property gets the URL. I use the
Hint property strictly for storage, but nothing should stop you from
using this URL as a hint in the pop-up menu.
Because you never know in advance which bookmark is requested
for deletion, a canned response to an OnClick is necessary. For
that reason, I introduce miBookmarkClick, shown in Figure 4.
This simple routine obtains the menu item through the TObject
parameter Sender. It gets the index of the clicked menu item first.
Then it deletes the bookmark by its index. The bookmark.dat file
will be updated when the program exits.
The menu items for deletion were created when the bookmark was
created, under createMenuItem. The caption was set the same way.
When the bookmark is selected for deletion, the deletion should
occur in the bookmark and deletion lists. All you need to do to
navigate to the URL that the Hint property indicates is to cast Sender
as a TMenuItem:
procedure TfrmBrowser.miBookmarkClick(Sender: TObject);
// Universal response to a click on a bookmark menu item.
begin
WebBrowser1.Navigate((Sender as TMenuItem).Hint);
end;

Note also that because of method overloading, I have gotten rid of


several parameters to Navigate that were mandatory at one time,
which forced me to pass dummy variables to it. Also, note that the
Hint property is never assigned a URL in the deletion list. Theres
no point; all thats important is that the captions and indexes
match. Unlike bookmark addition, bookmark deletion requires a
different canned response to a mouse click. Figures 5 and 6 show
the dynamic menus for selecting and deleting bookmarks.
In createMenuItem, the first thing the program does is pass the
URL to isUniqueBookmark, a Boolean function that returns True
if the bookmark is unique. It checks the URL against the menuitem hints, one item at a time, using strcomp (see Figure 7).

An Extended ComboBox
The purpose of using a TComboBox in place of the text box for
the Location bar is to provide another way of saving previously
visited sites during a session, and to save the user the trouble of
clicking on the Back and Forward buttons countless times. This is
different from bookmarking, in that the behavior of the custom

Delphi at Work
Whether a string is loaded into cbxLocation depends on the
URL of the site visited. Its actually an extended ComboBox
(TComboBoxEx), which accepts icons. If the name is substantially
different from any other URL on the list, that name is added.
This makes the ComboBoxs behavior different from that of
Internet Explorer (see the end of the article for suggestions about
obtaining Internet Explorer behavior in cbxLocation).
When I say the name is substantially different, what I mean
depends on a seemingly arbitrary but effective method consisting
of finding a match within the first two dozen characters of both
URLs. If no such match is found, then the URL is added via:
cbxLocation.ItemsEx.AddItem(URL, 0, 0, 0, 0, nil);

Figure 5: Dynamic menu for bookmark selection.

Users should bookmark any URLs they want saved in a more permanent and less arbitrary fashion. The only purpose of this weedingout method is to prevent cluttering the combo-box list with highly
similar or useless URLs. The syntax for this kind of AddItem is:
function AddItem(const Caption: string; const ImageIndex,
SelectedImageIndex, OverlayImageIndex, Indent: Integer;
Data: Pointer): TComboExItem;

The Images property of cbxLocation was set at design time to an


existing image list named ImageList1. Item 0 in that list is a 16-by-16
pixel icon signifying a URL. You may want to modify this behavior
by indicating different icons for selection and overlaying. AddItem
also allows the programmer to control the indent level of the menu
item, but, in this program, all were set to 0.

Status Bar Updates


Figure 6: Menu for bookmark deletion.

ComboBox, named cbxLocation, is less voluntary. This is useful if


a recently visited bookmark is deleted in error. Then, if you know
the sites URL, you can go to the site from the drop-down menu,
and bookmark the URL again.
The cbxLocation object provides the user another way of surfing to
new sites. There are three ways of loading the page from cbxLocation:
Enter the URL and click on the Go There! button on the far left of
the location bar.
Enter the URL and press R.
Select a URL loaded into the string list of cbxLocation with your
mouse.
The first way to load the page from cbxLocation calls
tbGoThereClick, which calls WebBrowser1.Navigate(cbxLocation
.Text). The second way invokes cbxLocations OnKeyPress event,
which checks to see if youve pressed R (see Figure 8) then
calls Navigate as before. The third way invokes the OnSelect event,
and thus calls cbxLocationSelect to surf to cbxLocation.Text in a
similar way.
PJKBrowses cbxLocation object can accept partially-typed, degenerate URLs in the same way Explorer does. For this reason, userinputted URLs shouldnt appear in the ComboBox drop-down
list. Instead, the drop-down list should contain complete URLs
that the browser successfully returned when the OnTitleChange
event is invoked.
27 August 2002 Delphi Informant Magazine

You may have noticed that in Internet Explorer, moving the


mouse over a hypertext link causes the links URL to appear in
the status bar. This behavior, along with the ability to relay status
messages, is available by invoking the OnStatusTextChange event.
OnStatusTextChange is one of many unseen events I will cover
later. This code provides the handler:
procedure TfrmBrowser.WebBrowser1StatusTextChange(
Sender: TObject; const Text: WideString);
begin
StatusBar1.Panels[1].Text := Text;
end;

This routine makes the status bar rather chatty and therefore not
much different from Internet Explorers. OnStatusTextChange calls this
event, which may be used to update the status bar with new status
messages, including mouse-overs. WebBrowser1StatusTextChange
receives the status text in its parameter list, data type WideString. This
can be transferred directly to the appropriate status-bar panel, as you
just saw in the code snippet.

Detecting Encryption
IE6 is capable of detecting encryption and can even determine the
kind of encryption. Delphi 6s stock TWebBrowser doesnt provide
that function. In this browser, I decide to find ways of telling the user
about the encryption in as much detail as possible if the browser does
or does not detect encryption. There are two ways this browser
does this: through messages on the toolbar, and through the use of
special icons. The security-level value is based on a C++ enum that
is rewritten here as the Delphi-style enumerated type securityLevel.
Figure 9 shows the associated code.

Delphi at Work
function TfrmBrowser.isUniqueBookmark(URL: string):
Boolean;
// Returns True if the URL is unique.
var
unique : Boolean;
i : Integer;
begin
unique := True;
for i := 0 to bmIndex - 1 do
if strcomp(PChar(pmBookmarks.Items[i].Hint),
PChar(URL)) = 0 then
unique := False;
isUniqueBookmark := unique;
end;

Figure 7: Checking for a unique bookmark.

procedure TfrmBrowser.cbxLocationKeyPress(Sender: TObject;


var Key: Char);
// Reacts to pressing the Enter key in the ComboBox,
// by surfing to the indicated Web site.
const
enter = 13; // ASCII 13 = CTRL+M (Enter)
begin
if Ord(Key) = enter then
WebBrowser1.Navigate (cbxLocation.Text);
end;

Figure 8: Responding to a key press in cbxLocation.

The output to the user is performed by outputSecurityMesg, which places


the mesg parameter in both the hint for the Go There! button, and the
display in the first panel of the status bar. The Go There! button regains
its default globe icon when the user returns to a page with no security
features. I chose the Go There! button because of its prominence on the
form. Figure 10 shows descriptions of the security-level constants.
(Note: Proper decryption of Fortezza security encryption requires
the use of a special Fortezza Crypto Card, which is being developed by Microsoft, AOL, and Netscape as part of the Multi-level
Information Systems Security for the Internet program led by the
National Security Agency, according to documentation on the
Microsoft Web site.)
The OnSetSecureLock event is robust, even when moving between
two secure Web sites. In such cases, the event is triggered once when
moving out of the old site, and once more when moving into the
new site to give the new security information to the user.

Dealing with Privacy


According to MSDN documentation Privacy can be affected in three ways:
If a document has encrypted information, and the user decides to
move away from it to an insecure document.
If the user is on a secure page and decides to refresh the browser.
If an event causes the browser to access cookies in an insecure
manner.
When privacy is affected, the OnPrivacyImpactedStateChange
event is called. Its handler is shown in Figure 11. The code considers the first two categories to be Privacy impacted by the end
user and the last category as Insecure cookie access, or privacy
impacted by the browser, if you will. If this event is called, the
Go There! toolbar button has either a broken-cookie icon or a
broken-lock icon, along with an accompanying text message the
first panel of the status bar will receive.
28 August 2002 Delphi Informant Magazine

type
// Security level icon indices.
secLevelIcons = (globeico = 12, lckMixed, lckUnk,
lck40bit, lck56bit, lckFort, lck128bit, lckCookie,
lckBroken = 25);
procedure TfrmBrowser.WebBrowser1SetSecureLockIcon(
Sender: TObject; SecureLockIcon: Integer);
// Tell user security level of site.
type
// Security Level Enumerations. Start counting from 0:
securityLevel = (secureLockIconUnsecure = 0,
secureLockIconMixed, secureLockIconUnknownBits,
secureLockIcon40Bit, secureLockIcon56Bit,
secureLockIconFortezza, secureLockIcon128Bit);
begin
case securityLevel(SecureLockIcon) of
secureLockIconUnsecure:
OutputSecurityMesg('NO encryption present',
globeico);
secureLockIconMixed:
OutputSecurityMesg(
'Multiple encryption methods used', lckMixed);
secureLockIconUnknownBits:
OutputSecurityMesg('encryption level UNKNOWN',
lckUnk);
secureLockIcon40Bit:
OutputSecurityMesg('40-bit encryption used',
lck40bit);
secureLockIcon56Bit:
OutputSecurityMesg('56-bit encryption used',
lck56bit);
secureLockIconFortezza:
OutputSecurityMesg('Fortezza encryption used',
lckFort);
secureLockIcon128Bit:
OutputSecurityMesg('128-bit encryption used',
lck128bit);
end;
end;
procedure TfrmBrowser.outputSecurityMesg(mesg: string;
iconIdx: secLevelIcons);
begin
StatusBar1.Panels[0].Text := mesg;
tbGoThere.Hint := mesg;
tbGoThere.ImageIndex := Integer(iconIdx);
end;

Figure 9: Reporting encryption to the user.

Events Unseen
Some events related to the browser are what I call unseen events. We
dont see these events happen, but they do happen, with consequences for which you may or may not have coded. Otherwise, the
events are ignored. Coding for unseen events can give the browser the
familiar feel of the commercial browsers (or any behavior you want).
Heres a list of unseen events:
OnProgressChange
OnStatusTextChange
OnTitleChange
OnSetSecureLockIcon
OnPrivacyImpactedStateChange
OnCommandStateChange
An OnCommandStateChange handler that takes into account the
enabling and disabling of Back and Forward buttons is compulsory
for this browser. Not handling this event with respect to the Back or
Forward buttons simply will cause the browser to crash.

Delphi at Work
Constant

Value

Meaning

secureLockIconUnsecure

encryption present

secureLockIconMixed

multiple encryption
methods used

secureLockIconUnknownBits

encryption level
UNKNOWN

secureLockIcon40Bit

40-bit encryption used

secureLockIcon56Bit

56-bit encryption used

secureLockIconFortezza

Fortezza encryption used

secureLockIcon128Bit

128-bit encryption used

Figure 10: The security-level constants and their values and


meanings.

The default data type of WordBool for Enable in the parameter


list can be treated as plain Boolean for this event handler. The
Enabled button properties still are updated appropriately. The
OnTitleChange event is used to update the page title in the title
bar. The bulleted list of unseen events gives the listing for a handler. Figure 12 shows the TitleChange and AppendLocList routines.
Note the use of strLIComp instead of strComp in AppendLocList.
StrLIComp checks for partial matches of strings (type PChar)
within the first two dozen characters.
In this event, the forms Caption property, the title-bar string,
is changed to the new page title given by the parameter Text.
In addition, if the URL is substantially different from others
visited during this session, it is added to the list portion of the
TComboBoxEx object, named cbxLocation. Also, the Text property
of cbxLocation is updated with the new URL.

Zooming in with ExecWB


ExecWB is a catch-all routine for interfacing with other methods
and functions to do more with your browser. As with the original
browser, its use is more or less limited to saving, printing, and
stepping the relative font sizes. The parameters passed to it are
enumerated types (integers) that originate from the interface to
Microsoft Office menu operations, as discussed in the Web browser
documentation at the MSDN site. Heres the syntax for ExecWB:
ExecWB(command: OLECMDID, modifier: OLECMDID,
inputVal: OleVariant, var errorlevel:OleVariant);

Because of overloading, there are now three ways to call ExecWB.


That is, the number of parameters now depends exclusively on
what you want to do with the function, rather than on a fixed
number of parameters for which you have to add unused zeroes
and nulls.
Because the keystrokes for cutting, copying, and pasting (CX,
CC, and CV, respectively) are not available in the ActiveX
control, their respective OLECMDID constants (see Figure 13)
are also ineffective. You dont need to set all of these for the
purposes of this browser. What works are OLECMDID_SAVEAS =
4, OLECMDID_PRINT = 6, OLECMDID_PRINTPREVIEW = 7, and
OLECMDID_ZOOM = 19.

Closing the Program


Closing the program through an explicit subroutine provides PJKBrowse
with a means of saving bookmarks. The method used to close the
program is FormClose, which comes with an Action parameter.
29 August 2002 Delphi Informant Magazine

procedure TfrmBrowser.
WebBrowser1PrivacyImpactedStateChange(Sender: TObject;
bImpacted: WordBool);
// Accounts for two major categories of privacy violations:
// either via the top-level URL (by way of such things as
// insecure cookie access) or via the end user (who might
// initiate navigation from a secure site).
begin
if bImpacted then
begin
tbGoThere.ImageIndex := Integer(lckCookie);
tbGoThere.Hint := 'Un-secure cookie access';
StatusBar1.Panels[0].Text := 'Insecure cookie access'
end
else
begin
StatusBar1.Panels[0].Text :=
'Privacy Impacted by User';
tbGoThere.Hint := 'Privacy Impacted by User';
tbGoThere.ImageIndex := Integer(lckBroken);
end;
end;

Figure 11: Handler for the OnPrivacyImpactedStateChange event.


procedure TfrmBrowser.WebBrowser1TitleChange(
Sender: TObject; const Text: WideString);
begin
Caption := Text;
AppendLocList(WebBrowser1.LocationURL);
cbxLocation.Text := WebBrowser1.LocationURL;
end;
procedure TfrmBrowser.AppendLocList(URL: string);
var
i : Integer;
match : Boolean;
begin
match := False;
for i := 0 to cbxLocation.Items.Count - 1 do
if strLIComp(PChar(URL),
PChar(cbxLocation.Items[i]), 24) = 0 then
match := True;
if not match then
cbxLocation.ItemsEx.AddItem(URL, 0, 0, 0, 0, nil);
end;

Figure 12: The TitleChange and AppendLocList routines.

The code in this browser was set up so it would save the bookmarks
and exit regardless of the value entered (on Windows 2000, pressing
the title bars X causes caHide to be passed as a parameter). The code
for FormClose is:
procedure TfrmBrowser.FormClose(Sender:Tobject;
var Action: TcloseAction);
begin
if (Action = caFree) or (Action = caHide) then
TbExitClick(Sender);
end;

tbExitClick, the toolbars Exit button, is implemented this way:


procedure TfrmBrowser.tbExitClick(Sender: TObject);
begin
storeBookMarks;
Application.Terminate;
end;

Variations on the Theme


Reprogramming ComboBoxEx for Internet Explorer behavior

Delphi at Work
using OnTitleChange. The ComboBox list is volatile; it exists only
while the program is running. Because the list grows as you surf, it
wouldnt make much sense to save the URL list unless its behavior
was reprogrammed to be like that of Internet Explorer. The
OnTitleChange event provides a good opportunity to see if the URL
at which you arrived was the result of the URL being entered (and
not the result of a link or a bookmark). Then, add that URL to the
cbxLocation ComboBox if it is unique and reaches its destination
successfully. The coding details are left to you.
Minor variations on TProgressBar. You can set TProgressBars
Orientation property to vertical (pbVertical ) for a thermometer effect (the
other value is pbHorizontal ). A vertical progress bar with its BorderWidth
property set to 1 or 2 also can serve as a button separator for a more
unusual design effect, saving space in the meantime.
Bookmark recovery. Using the framework shown previously for
bookmark addition and deletion, a similar routine could be written for
bookmark recovery after a deletion before the current session ends. This
would take advantage of the fact that the bookmark file isnt overwritten
until the browser exits. Thus, if the user thinks bookmarks were deleted in
error, the bookmarks can be restored by rereading the bookmark.txt file,
checking for differences between the current bookmarks and the saved
ones, and adding only the bookmarks that arent in the current menu.
Using frames. Creating a separate, modular frame to contain the browser
and the other controls may provide added flexibility to the application
should there be a desire to add more features to the application later. This
assumes, of course, that almost all the methods are coded as part of the
frame unit. Imagine having things like a series of browser instances each
occupying a page within a TTabControl. Designing a browser this way is
a good starting point toward that kind of expandability. Once you create
the frame, what you need to do is to add that frame to the main form or
PageControl page as a control, and set its Alignment property to alClient.
Done this way, however, the browser loses the ability to load a default
page on FormCreate, as mentioned earlier.

30 August 2002 Delphi Informant Magazine

type
OLECMDID = (
OLECMDID_OPEN = 1,
OLECMDID_SAVE,
OLECMDID_SAVECOPYAS,
OLECMDID_PRINTPREVIEW,
OLECMDID_SPELL,
OLECMDID_CUT,
OLECMDID_PASTE,
OLECMDID_UNDO,
OLECMDID_SELECTALL,
OLECMDID_ZOOM,
OLECMDID_UPDATECOMMANDS,
OLECMDID_STOP,
OLECMDID_SETPROGRESSMAX,
OLECMDID_SETPROGRESSTEXT,
OLECMDID_SETDOWNLOADSTATE,
);

OLECMDID_NEW,
OLECMDID_SAVEAS,
OLECMDID_PRINT,
OLECMDID_PAGESETUP,
OLECMDID_PROPERTIES,
OLECMDID_COPY,
OLECMDID_PASTESPECIAL,
OLECMDID_REDO,
OLECMDID_CLEARSELECTION,
OLECMDID_GETZOOMRANGE,
OLECMDID_REFRESH,
OLECMDID_HIDETOOLBARS,
OLECMDID_SETPROGRESSPOS,
OLECMDID_SETTITLE,
OLECMDID_STOPDOWNLOAD

Figure 13: The OLECMDID enumerated type.

Conclusion
By building your own ActiveX browser, you can build in the features
you want, any way you want. I do not mean to suggest that my browser
is an end-all, be-all program. You could write things quite differently
and emphasize different aspects of browsing. In this article, I focused on
conveying security information to the end user. By programming your
own browser, you have significantly more control over the browsers
features, as well as over the look and feel.
The project and associated files referenced in this article are available
on the Delphi Informant Complete Works CD located in INFORM\2002\
AUG\DI200208PK.

Paul Joseph King is a writer, teacher, and consultant living in Oakville, Ontario,
Canada. Readers may reach him at pking123@sympatico.ca.

New & Used


By Ron Loewy

ExpressLayout Control
A Genuine Alternative to Delphis Form Designer

xpressLayout Control is Developer Express new product for managing form layout
in Delphi applications. The product offers run-time form-customization capabilities,
hierarchical control grouping, support for look-and-feel styles, and more.
ExpressLayout is a hard product to explain. You really
have to see it in action to get a feel for it, and even
after several months of extensive use, you still learn
new things about it as you use it. When I bought
the product, I envisioned it as a method to promote
end-user layout customization. However, Ive found
I dont like to use that feature. Even so, the product
provides other benefits that make it a tool I use
frequently.
When you install ExpressLayout Control, it adds two
new components to Delphis Component palette.
The main one, TdxLayoutControl, is a control container you can drop on your forms. Its used to hold
a set of groups and controls that are arranged semiautomatically for you as you drop them on it. Groups
are containers within the layout control. Think of a
group as a container control that has attributes such
as vertical and horizontal alignment, location, and

layout direction. Layout direction describes the way


new items added to the group are arranged horizontally or vertically. If the layout direction is vertical,
new items added to the group will be arranged under
each other automatically. If the layout is horizontal,
the items will be arranged next to each other.
When you drop a control in a group, an item container is created for you automatically. The container
item has layout properties you can manage, such as
alignment within the group, whether to display a caption, and whether to use a border around the item.
The interesting thing about groups within the
layout control is that they can be nested. In other
words, you can create a hierarchical layout of areas
on your forms. Its easy to create a group within a
group and set their alignment properties in such a
way so they resize themselves automatically, based
on the size of the owner control. Instead of responding to OnResize events and calculating the size of
the different controls in a container manually, you
can define the size and alignment attributes of child
controls within a layout control, using the Object
Inspector. You no longer need to worry about the
components disappearing, or looking funny on different resolutions or form sizes.
I use the layout control for every dialog box I create
now. Creating professional dialog boxes that are
sizeable is a breeze. Figures 1 and 2 illustrate some of
the capabilities of the layout control as a way to create
your form layout.

Figure 1: This dialog box was created with no code. Notice


the three vertical groups (a top group, a bottom group, and a
buttons group) and that the top group has two groups nested
horizontally within it. The right group has several items and
another group nested within it.
31 August 2002 Delphi Informant Magazine

To arrange the items and groups within the layout


control, you use a combination of drag-anddrop operations, a designer dialog for the layout
control, and setting properties. The designer dialog
provides a way to see the hierarchy of groups and
items in the control (see Figure 3). This capability can be exposed to end users to allow them to
change the layout of the form to their liking. The

New & Used

Figure 2: The same dialog box shown in Figure 1, after being


resized. Note that the top, right group has a fixed size. The left group
(and all its items) is resized automatically. In the bottom group, the
left item is fixed in size, and the right item is resized automatically.
Also notice that the bottom group was resized vertically. This shows
the fine control afforded over vertical and horizontal alignment.

Figure 5: The dialog box with the OfficeLookAndFeel option


shown in Figure 4 applied. Compare this with the standard look
in the previous images to see the subtle changes.

layout of the child


controls can be
persisted to a file or
database. Thus, you
can save the layout
the user created and
allow him or her to
reuse it over time.
As much as I like
the control for
dialog creation, it
took me a while
to learn the visual
cues that appear
during drag-anddrop operations.
These operations
will be confusing to
the vast majority of
end users. Its my
belief that when
your dialog boxes
are well designed to
begin with, the need
for user customization is minimal.
Most users will find
a well-designed
dialog box easy to
understand and use.
Adding the usercustomization feature creates another
level of complication, requires
documentation of
the drag-and-drop

Figure 3: The layout-designer control used for


the dialog box displayed in Figures 1 and 2.

Figure 6: The same dialog box using the WebLookAndFeel


option. All it took to change from one format to the other was to
set a property value I didnt have to write any code.

operations, and opens you to the possibility of users complaining


about missing or hard-to-find controls if they move them.
Once youve created your layout, you can fine-tune the display.
For example, in the same sample dialog box, you can turn off
the caption and border around the buttons group. You can also
take advantage of the second component the product installs, the
TdxLayoutLookAndFeelList component. With this component, you can
define different attributes for your groups and control items. You can
also set the layout controls LookAndFeel to change the overall look of
the form. Each look and feel includes properties that function as group
options, such as captions, location, color, fonts, frames, borders, and
offsets. The same customization is available for control-item options.

Figure 4: The three layout look-and-feel


options created for the sample dialog.

32 August 2002 Delphi Informant Magazine

For example, I added a layout look-and-feel list to our sample and created
three look-and-feel options: one that resembles Office dialog boxes, a standard one, and one that looks like a Web form (see Figures 4 through 6). As
you may expect, you easily can change the look and feel during run time
and even allow your users the ability to customize it to their hearts desire.
The product comes with complete source code, a help file, and a couple
of demos, one of which shows how to create an interface like that of

New & Used

With ExpressLayout Control, you can create complex dialog boxes


and user-entry forms with automatic resizing capabilities, run-time
user customization, and popular look-and-feel schemes. If you need
to design forms with many different controls, and dont want to
spend hours doing it, ExpressLayout Control is worth considering.
Developer Express
6340 McLeod Drive, Suite 1
Las Vegas, NV 89120
Phone: (702) 262-0609
E-Mail: info@devexpress.com
Web Site: http://www.devexpress.com
Price: US$149.99

Microsoft Money. The Developer Express Web site provides a white


paper to get you started and the usual exceptional support from Developer Express employees and the peer-support newsgroups.
It seems weve been programming with Delphi for ages, and the
basics of positioning controls on a form basically has remained
the same since Delphi 1. ExpressLayout Control provides an
alternative with real advantages for creating professional-looking
user interfaces quickly. However, the product has some earlyrelease problems I hope will be addressed in future versions.
Frame support isnt perfect, but the support team promised me
that a point-release update solves the problem. (I didnt have time
to test it, though.) There is no way to create regions (groups) that
the user can resize in the control by including a splitter, for
example and it would be nice if the groups and controls in the
designer window were named automatically, based on the captions
(groups) or controls (items) assigned to them. Otherwise, you get
a long list of items with names like dxLayoutControl1Item1 and
dxLayoutControl1Item2.

Conclusion
This control provides a great alternative to the standard Delphi
form layout. Theres a learning curve involved in understanding
the logic behind the control. But, once you master it, the time
you save when creating complex dialog boxes and forms with
professional layouts is substantial. Creating complex dialogs that
optimize the users capabilities is no longer a chore. ExpressLayout
Control is indispensable for this reason alone.

Ron is a software developer specializing in business-intelligence applications.

33 August 2002 Delphi Informant Magazine

New & Used


By Robert Leahey

ModelMaker 6.11
Delphi-specific Two-way UML Tool Gets Even Better

odelMaker: Just your average, feature-rich, low-cost, active-modeling, codegenerating, two-way-editing, ever-improving, Delphi-specific UML tool. I
reviewed ModelMaker 5 in the October 1999 issue of Delphi Informant, and gave it
the highest praise I could manage. Well, I need to come up with some new superlatives, because the product continues to improve.
In this review, well look at diagramming, the
part of ModelMaker that has progressed the most
since version 5. For a look at the products other
features, please see my earlier review, at
http://www.delphizine.com/productreviews/1999/
10/di199910rl_p/di199910rl_p.asp.
I have a friend who eschews UML diagramming
as nothing but pretty pictures. He claims that
the moment the first line of code is written, the
diagrams are at best irrelevant (since theyll most
likely fall out of sync with the class implementations), and at worst, a waste of time and effort
(since they have to be updated). Putting aside
the fact that my friend misses the point of UMLs
value as a pre-implementation analysis tool,
the vast majority of available UML tools lend
credence to his argument. For example, you can
use Visio, and wind up with some pretty pictures,
but it has no code-generating capabilities so youll
have to update your diagrams regularly by hand.
You could also get the industry standard, Rational
Rose, and pay US$4,000 for the privilege of getting no Delphi support.
The pretty picture argument fails, however,
if youre using a two-way, active-modeling tool
34 August 2002 Delphi Informant Magazine

where the diagrams are tied directly to your code.


If youre a little fuzzy on the two-way concept,
note that one of the greatest strengths of tools like
Delphi is that if you make changes in the visual
environment (e.g. Delphis Object Inspector), the
source code is changed. Likewise, if you change the
code, the visual environment reflects that change.
ModelMaker, winner of the 2001 Delphi
Informant Readers Choice Award for Best
CASE Tool, brings two-way development to
diagramming. At the heart of ModelMaker is
an active model engine where all of your work
is stored as objects rather than source code.
Objects that represent your classes, events, class
members, method implementations, in-source
comments, etc. are manipulated within the
ModelMaker environment. Then, when youre
ready, source code files are generated.
ModelMakers diagram editor is just another
view into the active model. Edits made within
the various diagrams have a direct impact on
your code. Similarly, changes made to the code
in your model are reflected in your diagrams;
no energy is wasted keeping your pretty pictures
in sync. Figure 1 shows a simple class diagram

New & Used

Figure 1: ModelMakers diagram editor.

Figure 3: ModelMakers Diagram view.

Figure 2: Dragging the generalization arrow to change ancestors.

Figure 4: ModelMakers toolbar of common


diagram elements.

in ModelMaker; on the left side of the window theres a class


hierarchy, where the same classes are in the diagram and tree views.

nevertheless detect and display the change. Two-way UML diagramming for Delphi!

If you alter the diagram by, say, changing the ancestor of


TepProjectsManager by dragging the generalization arrow from one class
to another (see Figure 2), the active model automatically recognizes this
change and updates the various views throughout the user interface. The
source code will also be updated the next time the units are generated.

ModelMakers Diagram Editor

Likewise, if you renamed TepProjectsManagers Projects property


using ModelMakers non-diagramming tools, the diagram would

Youll find buttons to add diagrams to the model on the toolbar across
the top of the window. ModelMaker currently supports the following
diagrams: Class, Sequence, Use Case, State, Activity, Implementation
(component and deployment), Unit Dependency, and Mind Map. These
last two are not UML diagrams, but are useful tools nonetheless.

ModelMaker is a robust Delphi productivity/CASE tool that includes


support for UML diagramming, design patterns, reverse engineering,
and Delphi integration, and a bevy of other features. This product is
indispensable for Delphi developers doing serious design work.
ModelMaker Tools
Stenenkruis 27 B
6862 XG Oosterbeek
Netherlands
E-Mail: support@modelmaker.demon.nl
Web Site: http://www.modelmakertools.com
Price: US$269 for a single-user license. Discounts are available for
multi-user licenses.

35 August 2002 Delphi Informant Magazine

The tree view displays a list of all diagrams in the model and allows
you to add or clone diagrams in a hierarchical manner (see Figure 3).
Thus, related diagrams can be grouped under a single parent (all the
Use Case diagrams in Figure 3 are gathered under a single parent).

Mind Maps. Mind Map diagrams are based on the mind mapping technique by Tony Buzan and are powerful brainstorming tools. This type
of diagram consists of branching nodes representing related thoughts.
ModelMaker allows you to create the requisite small and large nodes and
connect them easily. Creating Mind Maps in ModelMaker is simple,
allowing you to keep up with a rapid brainstorming session.
Mind Maps offer support for many elements common to all ModelMaker diagram types (see Figure 4). Ill discuss them here, but they
are available to all diagrams.
From left to right, there are buttons to add packages, units, various types of annotation elements, constraint relations, hyperlinks,
polygons, lines, and images. ModelMakers diagram hyperlinks are
particularly powerful, featuring the ability to link to most elements of

New & Used


the model, other diagrams, and even external
documents. Clicking a hot spot will result in
the appropriate model element being selected,
the selected diagram activated, or the external
document opened.
This is a very nice feature; whats better is that
virtually any symbol in any diagram (classes,
use cases, states, activities, or even Mind Map
nodes) share the same functionality. Create a
hyperlink within a given symbols editor, and a
hot-spot icon will appear in that symbol. This
is very powerful; it means that, when viewed
within ModelMaker, these diagrams are far
more than static images, theyre dynamic and
interactive. You can navigate through related
diagrams via hyperlinks, select model elements
for editing, and even open requirements
documents. Ive used the external document
hyperlinks to link to Delphi package, AQTest,
FinalBuilder, and Doc-O-Matic projects. The
possibilities are intimidating.
Figure 5: ModelMakers diagram editor with an active Class diagram.

Class diagrams. Class diagrams show a set


of classes, interfaces, and collaborations, and
their relationships. Theyre also one of the most powerful diagrams
in ModelMaker. Its possible to add a class, interface, property,
or field to the diagram and have that action create the actual
corresponding element within the model. Dragging a generalization
relation within a class diagram can have direct results within
the code model; the same is true of properties or fields, element
documentation, and interface realizations. Continuing on the
two-way theme, the editors of most code model elements contain
settings for how an element will be visualized. Thus, when a class,
for example, is dragged to the diagram editor, its predefined settings
will determine how the class symbol will be displayed.

Take a look at the small linked chain icon in the class symbols on the
DataBroker property association arrow in Figure 5. Also, notice the minus
signs in the upper-left corner of the class symbols. The linked chains are
hot spots. Clicking them will result in opening the appropriate editor for
the selected element. Likewise, clicking the minus signs will collapse their
symbols. This allows class diagrams that are manageable, maintainable,
dynamic, and just plain cool. ModelMaker wields a diagramming editor
thats meant for serious productivity.
Sequence diagrams. Sequence diagrams emphasize the time ordering
of messages between objects. In other words, in a Sequence diagram,
objects are arrayed along the x-axis and time progresses downward
through the y-axis; messages between objects are displayed as
labeled arrows. Much like Class diagrams, adding elements within a
Sequence diagram can add them to the code model itself. Therefore,
if youre creating a Sequence diagram and you see the need for class
X to have a new method that class Y will call, you can simply create
the call, and in the process, create the new method. Theres no need
to leave the diagram to create the new method.
Sequence diagrams have a tendency to get rather large once youve
added call returns, annotation, etc., so this is a good place to talk
about ModelMakers new print capabilities. Its now possible to
create diagrams of any page size, and ModelMaker will tile the
output onto multiple pages. This eliminates the need to create
multiple letter-page-sized diagrams.
36 August 2002 Delphi Informant Magazine

Use Case diagrams. ModelMaker provides robust Use Case


diagramming, with full support for Extend and Include relations,
use case containers and generalization, and realization relations.
In addition, Use Case diagrams have the features common to all
diagrams, such as hyperlinks. Hyperlinks are particularly handy,
because you can link external documents or link to many other
diagrams designed to provide more detail for your cases.
Activity diagrams. As with each of its available diagrams,
ModelMaker offers a rich set of features for Activity diagrams, as
shown in Figure 6. All of the requisite shapes and connectors are
available, such as decision, synchronization bar, dynamic choice,
control icon, object flow, etc. As with all ModelMaker diagrams,
shaping connectors is simple: C+click to add a node, and then
you can drag it to create the desired shape for the connector.
The same is true for polygons and polylines. This can result in
some highly customized, attractive diagrams (which still actively
maintain a two-way link to your code model, for those keeping
score at home).
State diagrams. ModelMakers State diagram support is robust and wellimplemented. State diagrams are designed to offer a glimpse at a single
object and how it transitions from state to state during its lifetime. One of
the problems Ive seen in State charts is how to neatly display the internal
transitions that inevitably crop up in state symbols or composite state
symbols. Most of the time those transitions are crammed in at the bottom
of the symbol, almost as an afterthought, but ModelMaker offers a wellthought-out dialog for entering any internal transitions. Those transitions
are displayed near the top of the symbol, just under the state name in their
own collapsible section.
Unit Dependency diagrams. One nifty feature thats been available
in ModelMaker for some time is the Unit Dependency Analyzer.
The Analyzer, when provided with a given search path and a unit in
which to begin, will trace through the units in the uses clause of the
root unit, recursing through each successive units uses clause until
a dependency map is created, showing what units are required by
the root unit. In ModelMaker 6, the result of the Unit Dependency

New & Used


Analyzer can be displayed in a diagram. After
running the analyzer, you can choose to have
the resulting dependency map sent to a new
diagram that you can edit. In addition, like
the other diagrams, its possible to drag-anddrop elements from the various ModelMaker
views into the diagram. Thus, you can create
a dependency diagram from scratch by simply
dragging units from the Unit view into the
diagram editor.
Component and Deployment Implementation
diagrams. Deployment diagrams model
the static deployment view of a system.
ModelMaker provides the necessary means
to model the nodes, packages, components,
and classes that make up these diagrams. And,
as you might expect, adding existing model
elements, such as classes and units, can be done
by simple drag-and-drop.
Some of the other features in ModelMakers
diagram editor are:
auto-visualization (drop a class or other
element into a diagram and its relations can
be automatically realized);
Figure 6: An Activity diagram.
in-place editing of symbols;
multi-level undo/redo;
Conclusion
numerous wizards designed to help with layout and visualization;
It should come as no surprise that I highly recommend ModelMaker
diagram-style properties definable at Environment, Project, and
Diagram levels (these properties can be inherited or overridden);
and its UML diagramming capabilities. ModelMaker Tools has created
nothing but the highest quality developer tools, and this trend continues
XML export capabilities;
in version 6.11 of ModelMaker. If youre a Delphi developer, you should
full diagram support in the ModelMaker Open Tools API;
and, lest it go unstressed, two-way interaction with your code model. use ModelMaker. If you also need a UML tool, you shouldnt look any
farther than ModelMakers diagram editor.
What about Collaboration diagrams? At the time of this writing,
ModelMaker 6.11 doesnt support Collaboration diagrams. In fact, it
only supports six of the major UML diagrams, but the developers of
Robert is a Delphi developer who has been writing code since he discovered
ModelMaker continue to improve UML support, and they promise
AppleBasic on his Apple II+. He now provides custom creative solutions via his
that Collaboration diagrams are at or near the top of their to-do
company, Thoughtsmithy (http://www.thoughtsmithy.com). He graduated with a
list. Keep in mind that the great advantage of ModelMakers UML
degree in Music Theory from the University of North Texas and currently resides in
support lies in seeing your diagrams automatically realized in code,
Texas with his wife and daughters.
and that not all UML diagrams map directly to code, so their absence
is not as dramatic.

37 August 2002 Delphi Informant Magazine

New & Used


By Mike Riley

ActivePatch 1.1
Cost-effective Patching for Distributed Applications

our team has just finished and deployed its latest application to several thousand
users. One bug slipped through quality assurance, but its a showstopper. How do
you slipstream the patch to your users without asking them to download a 23MB patch?
Catalyst Development Corp. answers this problem with ActivePatch, a product that generates and applies byte-level change patches to your distributed applications.
Once the software is purchased and activated over
the Internet (yes, this form of software registration is becoming the norm, particularly in the
software component market), the product is ready
to be programmatically manipulated. When an
ActivePatch-enabled solution is constructed and
ready for distribution, the products license permits
unlimited client use of its redistributable libraries,
making it a cost-effective version control system
for deployed applications.
Unlike traditional setup utilities, ActivePatch
assumes that the bulk of your application payload
was delivered in its initial installation. It reviews
the changes from the original files and only applies
alterations. This makes the weight of distributing changes considerably smaller. The benefits of
applying patches at the byte level include: reduced
update size, faster distribution of patch (especially

important over a dial-up), and lower distribution/


media costs. Additionally, because ActivePatchs
functionality is supplied in a DLL, or a self-registering ActiveX control that can be programmatically manipulated, incorporating this slick
capability into your own applications is a breeze.

Three Solutions
The ActivePatch CD comes bundled with three
programmatic solutions: 1) a DLL that can be
accessed via standard C library calls; 2) a slightly
larger Microsoft Foundation Class (MFC) Library
object-oriented version; and 3) a 314KB ActiveX
control (its the largest, but the easiest to use).
These files are completely self-contained; they dont
use the MFC or Visual C Runtime library unless
the ActivePatch C++ classes are used. This makes
their distribution in the initial setup program easy.
When using the ActiveX version, the initial setup
program must support a self-registering ActiveX, or
run the REGSVR32 program to properly register
the component.
Although determined Delphi developers can generate a wrapper for the stand-alone DLL version,
most will elect the easy-to-use ActiveX control.
The only caveat with this approach is the potential
security risk its installation might pose on a clients
system. Nefarious hackers, aware of the controls
presence, might activate it via older Internet
Explorers ActiveX scripting capability, target a
trusted installation, such as Microsoft Office, and
as a result, slipstream their own unauthorized files
that damage or completely overwrite trusted files.

Figure 1: ActivePatch includes several programs with source code


that demonstrates how to use the technology.
38 August 2002 Delphi Informant Magazine

Besides slipstreaming byte changes between


original and modified file versions, ActivePatched
files can be created, replaced, or removed entirely.

New & Used


Depending on the target audience, and the liability involved, ActivePatch may be more hurtful than helpful in this respect.

Catalyst, and Catalyst would, in turn, freely distribute them (sans the
control or DLL, of course) via the companys Web site.

ActivePatch supports Microsofts Authenticode technology, thereby only


permitting authorized, signed patches to be applied to the intended files.
For those developers unwilling to spend the annual VeriSign charge for
an Authenticode key, but who still want some level of secure validation
over their distributed patches, ActivePatch can also programmatically
require a correct password before applying any changes.

Speaking of which, I had to consult their Knowledge Base section


a few times (at http://www.catalyst.com/cgi-bin/knowbase.cgi),
because the components behavior and patch application process isnt
as clearly defined as it should be in the products 320+ page Microsoft
Word-formatted electronic manual. In fact, the manual could probably have been reduced by 200 pages as the information is presented
redundantly for each of the DLL, MFC, and ActiveX approaches. It
would be better if Catalyst dedicated more manual space to defining
scenarios and implementing solutions.

ActivePatch also has built-in, automatic file backup and recovery


if something goes wrong during the patching process. In addition,
ActivePatch packages can include multiple patch types and can correctly target Win9x versus NT/2000/XP systems (the product supports all 32-bit versions of Windows). This can be a great time saver,
and can help categorize patch builds by delivering operating system
variations in a single file, instead of maintaining a library of separate
patch packages for each Microsoft 32-bit OS flavor.
Although ActivePatch packages can be distributed via physical media
(floppies, CD-ROMs, etc.) and local area networks via Universal
Naming Convention (UNC) paths to shared drives and Internet
downloads, the application developer is still responsible for the heavy
lifting when it comes to programmatically applying the patch. In
other words, ActivePatch provides the foundation to generate and
apply byte-level changes between the original and modified version of
files, but its still up to the developer to retrieve the patch, remove file
locks, apply the patch, and restart the application (if necessary).
I was disappointed that Catalyst omitted an Internet-enabled
example of this procedure. In fact, the only application examples
included on the CD are Visual Basic 5/6 and Visual C++ 6 versions
of the Build, Apply, and Patch features of the ActiveX control, and
several Visual C++ demos using the more lightweight APATCH.DLL
approach. These demos can be used to create patch packages for
your own applications, but it seems that it was Catalysts intent to
let creative programmers develop their own patch creation shells and
utilities based on the ActivePatch library.
I would prefer that Catalyst include a polished, simple patch-creation generator, instead of relying on developers to roll their own,
since most scenarios using the control would be with applying the
generated patch packages. Nevertheless, the option is available for
programmers with special needs, or who simply believe they can do
a better job than whatever Catalyst might have developed. My only
hope is that such developers would freely submit their shells back to

ActivePatch provides developers with an easy, yet powerful and


incredibly efficient mechanism to programmatically patch any type
of file on the Microsoft Windows 32-bit platform.
Catalyst Development Corp.
56925 Yucca Trail PMB 254
Yucca Valley, CA 92284
Phone: (800) 776-3818
Fax: (760) 369-1185
Web Site: http://www.catalyst.com
Price: US$199.99

39 August 2002 Delphi Informant Magazine

ActivePatch on the Net


So how would a Delphi developer use the control in an Internet-enabled
client application? First, the application needs to have an Internet file
transport mechanism built into it: FTP, HTTP, or even raw TCP communication. A Delphi developer could resort to the free, open-source
Indy controls available at http://www.nevrona.com/Indy to handle
this process. Next, if the primary application is expected to be patched
(which is almost always the case), a second program needs to be created
that will contain the patch logic. The reason a second application is
required is because Windows locks running application files, thereby preventing active patching to take place. Thus, to apply a patch to a primary
program executable, that primary application must close and allow a
secondary application to run and apply the patch.
So what happens when a patch needs to be applied to the secondary
program? To avoid a Russian dolls problem, the primary program
would have to also contain the ability to first download packages for
both primary and secondary programs and apply the patch to the
secondary program. You would launch the patched secondary program,
shut down the primary program, allow the secondary program to patch
the primary program, launch the patched primary program, and shut
down the secondary program. Unfortunately as I stated earlier, Catalyst
relies on the developer to define the procedure and write the code for
this process, rather than embedding it into their product.

Conclusion
After the hard work is finished, ActivePatch is quite satisfying to
watch in action. A rewarding level of polish and professional touch
can be attained using the technology, and the peace of mind of
knowing that even the smallest changes can be effortlessly applied to
deployed applications is considerably reassuring.
The only remaining concern is the fact that this product may be
short-lived, especially in a .NET Framework world. One of the
problems that .NET solves is the distribution and version control
nightmare that ActivePatch solves for pre-.NET Win32 executables.
Several articles posted on the Web show how easy it is to leverage the
Common Language Runtime (CLR) to provide much of the same
functionality that ActivePatch provides through its support for distributing and applying new application assemblies. Still, it will take
years for Microsoft to embed the bulky .NET Framework into every
OS that it ships, so the viability of Catalysts solution should remain
effective for some time to come.

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.

File | New
Directions / Commentary

Delphi 6: Worth the Wait?

hile the first five Delphi versions arrived at yearly intervals, we had to wait two years for Delphi 6. The reason
for the delay can be expressed in a single word Kylix. Borlands new cross-platform strategy involves a
close relationship between the new Linux development environment and Delphi. Kylix and Delphi have a common
user interface, but more importantly, they share a new group of components called CLX (pronounced clicks). These
cross-platform components make it possible to use the same code to compile applications for Windows with Delphi
or Linux with Kylix. Therein lies Delphi 6s greatest strength: the possibility of developing Windows applications that
can be easily ported to Linux, simply by recompiling in Kylix. Thus, I regard the Delphi 6/Kylix 2 combination as
another great Borland masterpiece, comparable to Borland Pascal 7, its other multi-platform environment.
Enhanced IDE and expanded Component palette. Delphi 6
shares two important qualities with each of the previous versions: enhancements to the IDE to increase productivity and new
components to create cutting-edge applications. For Delphi 6, the
latter is headlined by the new CLX, cross-platform components,
but theres much more. Theres increased support for XML and
XML-related technologies, such as the Document Object Model.
When the new XML support was previewed at the recent Borland
conference, the audience reaction was particularly appreciative.
For a complete overview, read Cary Jensens Delphi 6 article in
Delphi Informants July 2001 issue (http://www.delphizine.com/
features/2001/07/di200107cj_f/di200107cj_f.asp).
Ill limit the enhanced components discussion here to ActionManager. Among other things, the enhanced ActionManager gives you a
centralized means of controlling the various user actions that drive
applications. It also makes it possible for users of applications to
have that same control. Weve all seen applications that allow users
to modify toolbars and menus. With the new ActionManager and
its related components, you can easily add this modern functionality
to your applications. Please see Bill Todds Take Action article in
the April 2002 issue of DI for details (http://www.delphizine.com/
features/2002/04/di200204bt_f/di200204bt_f.asp).
Delphi 6 is impressive and it opens up a whole new world of possibilities, from cross-platform development to increased control over
the user interface. Still, an important question remains for many
developers: Should I upgrade?
In November 2001, I gave a talk on building code-generating Delphi
Experts to the Indianapolis Delphi Users Group (IDUG). I asked
how many people present were using Delphi 6, but only a few raised
their hands. Clearly another question is being pondered by many
developers, Should I upgrade to Delphi 6 now? While I cannot
offer a definitive answer, I can suggest some possible criteria. Multiplatform development needs to be at the top of the list. If youre
planning to develop applications that will run on both Windows
and Linux platforms, I say, What are you waiting for? Similarly, if
you require cutting-edge Internet (i.e. Web Services) and database
development tools, then Delphi 6 (especially the Enterprise Edition)
deserves serious consideration. On the other hand, if your development needs are more modest and youre comfortable with your cur40 August 2002 Delphi Informant Magazine

rent version of Delphi, then it might make sense for you to remain
with that product.
Up to this point, Ive focused on what Delphi 6 means to individual
developers, but lets take a larger view and consider Delphi in the
context of the company that makes it and the developer base that
depends upon it.
Delphi, a Borland barometer? With Borlands success closely wed to
Delphis success, the flagship product can be considered a barometer
indicating the companys health. Several years ago, when Delphi 4 was
released, the company was not in the best shape financially, and many of
its developer customers had questions concerning its vision and priorities. When Borland released Delphi 4, there were some rather troubling
bugs, the most infamous of which was the ItemIndex-bug in
TCustomListBox, as I discussed in a column at the time. Fortunately,
clever developers came up with a work-around even before Borland
released its own fix. Considering the larger problem, I pointed out in the
column that the consensus among many I spoke with at [the annual]
Conference was simply that Delphi 4 was released too soon. There needed
to be more rigorous testing to ensure that this essential tool mission
critical for so many developers was as bug-free as possible. Even at the
cost of delaying the release for a month while beta testing continued.
An important question to ask is, Did Borland learn any lesson
from this? Ill share some evidence that suggests they did. To begin
with, they were willing to acknowledge the mistake, which is always
the first step before taking corrective action. I remember a Borland
speaker at the 2000 conference posing this question to the audience,
When should we release the next version of Delphi? Someone in
the audience shouted, Now!, but the speaker was prepared for that
kind of impatience. He said, Well release it when its ready, which
turned out to be a year later.
Theres further evidence of Borlands increased commitment to quality control. As with previous versions, Borland has released a couple
of patches with Delphi 6 that fixed small problems and provided new
or enhanced functionality. Unfortunately, the first patch also introduced some new bugs. Borland realized it immediately, informed
its user base, withdrew the patch, and had a new patch (minus the
bugs) available within a few days. Many of you are probably aware
of all of this, especially if you subscribe to the various Delphi lists or

File | New
newsgroups. Heres something you may not know: Before releasing
the second patch, Borland invited its cadre of beta testers to resume
active duty and test the new patch before it was released. To me, this
shows that Borland has learned its lesson and is more committed
than ever to providing its customers with a tool in which they can
place their full confidence.
So, should you upgrade? I agree completely with Jensens assessment
that Delphi 6 represents the most significant upgrade to Delphi
since its release. More than the IDE enhancements and new components, I see Delphi 6s quality and cross-platform capabilities as its
strongest features. Worth the wait? Yes, indeed!
To get the most out of Delphi 6, you need to fully understand its
new features. There have been many articles in this magazine that
have exposed the information, but there are also some excellent new
Delphi books. Next month, well take our annual visit to the Delphi
Bookshelf.
Alan C. Moore, Ph.D.

Alan Moore is a professor at Kentucky State University, where he teaches music theory
and humanities. He was named Distinguished Professor for 2001-2002. He has
been named the Project JEDI Director for 2002-2003. He has developed educationrelated 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.

41 August 2002 Delphi Informant Magazine

You might also like