You are on page 1of 41

March

2004

Volume 10 Number 3

INSIDE
ON THE COVER
Columns & Rows

Product Reviews: Bernard D&Gs TurboDemo, Gnostices eDocEngine and PDFtoolkit

ADO.NET Data Access Components

Bill Todd continues his discussion of database development with Delphi 8 and
ADO.NET. This month he describes the Command, Parameter, and DataReader
objects, which among other things provide all the features you need to
perform the big four SQL operations: SELECT, INSERT, UPDATE, and DELETE.

FEATURES

Delphi Informant

March 2004 vol.10, no. 3

www.DelphiZine.com

The Complete Monthly Guide to Delphi Development

ADO NET

Data Access Components for .NET

In Development

Undocumented Delphi 8

10

Give Your Apps the

Project Retrospectives (AKA Post Mortems)

Registry Hacks

Step one in improving software project performance is to determine what


works and what doesnt. One good way to figure which is which is to employ a
process called a project retrospective. Steven Beebe explains how to make
retrospectives a valuable and integral part of your development cycle.

New IE-based Look


Productive Project

Post Mortems
Working with .NET

Directories & Files


Gauging App Performance:

Profiling

.NET Developer

14

Exploring the System.IO Namespace

Every programmer has to deal with directory and file manipulation, and
if youre working with .NET, the System.IO namespace is the way to do it.
Xavier Pacheco demonstrates the basics, and shares some nifty code for
copying entire directory structures, monitoring directory activity, and more.

Cover Art by Arthur A. Dugoni Jr.

The API Calls

19

Perfect Timing

Fernando Vicaria presents some of the timing functions available to Win32 developers, and
discusses the kind of precision and accuracy they offer, as well their associated overhead. He also
shares a straightforward framework that you can use for profiling simple tasks in your projects.

Sound+Vision

25

Getting the IE Look

You know that new look featured by Visual Studio .NET, C#Builder, and now Delphi 8?
Not only do users like it, its implementation offers some benefi ts to developers as well.
Zoltan Kurczveil demonstrates how to get the look.

Undocumented

30

Delphi 8 Registry Hacks

Borland R&D team member Corbin Dunn shares some secrets about the latest version of Delphi,
from how to customize the IDE by including/excluding modules to suit your development style and
shorten start up time, to unlocking hidden features such as Palette Wizards and Error Insight.

REVIEWS
32 eDocEngine and PDFtoolkit
Product Review by Mike Riley

35
40

TurboDemo Professional 4

D E PA R T M E N T S

Advantage Database Server:


The Official Guide

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

Product Review by John C. Penman

Book Review by Mike Riley

DELPHI INFORMANT MAGAZINE | March 2004

2 Toolbox

T O O L B O X
List & Label 10 Supports Delphi 8
combit announced List & Label 10, a
database-independent development
tool for report, label, and form printing functions and Web reporting. In
addition to other upgrades, List &
Label 10 introduces support for Delphi
8 for the Microsoft .NET Framework.
List & Label consists of the print
engine and the Designer. The Designer
is displayed in Office-XP style and
features dockable and floating toolbars
and tool windows. Rarely used tool
windows are quickly available without
using valuable space because multiple
windows can be docked together and
then brought to the foreground.
The Designer offers many features.
With the OLE-Object Container,
OLE-Server documents (i.e. from
WinWord, Excel, Visio, or MapPoint)
can be integrated in List & Label;
the object can be brought to the
foreground for editing with a doubleclick. With the Property Window,
dynamic layouts can be created with
formulas as contents. Also with the
Property Window, various dialogs are
presented uniformly in outline. The
Property Window allows dynamic
font presentation, color assignment,
dynamic table columns, or special

property changes. And with the .NET


component, you can integrate your
own objects and functions into the
Designer.
The new features of List & Label
10 include an entirely revised PDFexport, a new object to generate
forms, improvements to the .NET
component, an even easier to use
real-data Preview, an extended VCL
component, new barcodes, file transmission via SMTP, new export formats, and much more.
List & Label is available in English
and German and can process nearly
all character sets (including Far Eastern). The Designer will be available
in a variety of languages (the previous version was available in 19). The
DTP-Designer offers comprehensive
designing tools and a multitude of
filters and layout options that can be
configured via drag and drop. The
creation of graphic analyses, diagrams, and statistics is possible with
the extensive charting feature.

Hydra Announced
RemObjects released Hydra, a plugin
framework for use in RemObjects servers
or desktop applications. Hydra allows
you to partition applications into an
executable host and an arbitrary number of DLL modules that can be loaded
and unloaded at run time. This allows
for systems that are easier to update
and makes it easier to distribute tasks
through several developers.
A Hydra project is usually made of a
host exe, which loads DLLs through a
component called the Module Manager.
After the DLL is loaded, the host can
access the plugins contained therein.
With a plugin framework you can build
your systems as separate components
and assemble them based on your needs.
Plugins and host communicate using an
interface-based architecture. Built-in security classes and a user-profile component
allow you to disable functionality of your
plugins by setting a few properties.
Hydra Standard includes the tools to
develop modular client applications.
Hydra Server extends Hydra Standard
and the RemObjects SDK.

combit GmbH
Price: Prices may vary depending on exchange rates;
check the combit Web site for complete details.
Contact: pr@combit.net
Web Site: www.combit.net

RemObjects Software, Inc.


Price: Contact RemObjects.
Contact: info@remobjects.com
Web Site: www.remobjects.com

dtSearch Updates Product Line


dtSearch announced dtSearch 6.3, an
upgrade to its dtSearch product line.
dtSearch products search gigabytes
(and more) of text across a desktop,
network, Internet, or intranet.
This release includes some major
enhancements to dtSearchs enterpriseand developer-oriented products, with
minor enhancements to dtSearchs
desktop and network products.
All dtSearch products share the same
core search and display functionality,
including: indexed, unindexed, fulltext, and fielded data search options;
WYSWYG display of HTML, XML, and
PDF files with highlighted hits, as well
as embedded images, links, and formatting intact; and built-in HTML converters for display of word processor,
database, spreadsheet, e-mail, ZIP, Unicode, etc. files, with highlighted hits.
dtSearch Web with Spider uses a
wizard-based setup to publish a large

DELPHI INFORMANT MAGAZINE | March 2004

volume of instantly searchable content to the Web. The Spider can add
remote Web site content to the locally
searchable database, with integrated
relevancy ranking of search results
and hit-highlighted WYSWYG page
displays. The Spider supports public
sites, secure content HTTPS sites, password-accessible sites, and dynamiccontent ASP/ASP.NET code. The new
version of dtSearch Web with Spider
uses more efficient multi-threaded
algorithms for even faster searching on
sites with a large volume of concurrent
searching across large data sets.
dtSearch Publish enables quick publishing of an instantly searchable document collection to CD or DVD, and
works with dtSearch Web to provide
mirroring of an existing Web site on CD
or DVD. For end-users, the CD or DVD
operates with zero footprint, requiring no installation on the users hard

drive. The user interface can operate


out of the box as a default installation
for CD/DVD publishing. Alternatively,
the dtSearch Publish wizard provides
multiple customization options (no
programming required).
The dtSearch Text Retrieval Engine,
with Win/.NET and Linux versions,
lets developers add dtSearchs searching and built-in file format support
to Web-based and other applications.
The dtSearch Engine supports Delphi,
SQL, Java, C++, C++ .NET, C#, VB
.NET, and ASP.NET. The new version
adds a Language Analyzer API for
integrating third-party morphological
analyzers and dictionary-based word
breakers into the dtSearch Engine
indexing process.
dtSearch Corp.
Price: Contact dtSearch.
Contact: sales@dtsearch.com
Web Site: www.dtsearch.com

T O O L B O X
ShellPlus 2.3 Available
ShellPlus Development Group
announced ShellPlus 2.3, a set of Delphi/
C++Builder programming libraries.
This upgraded version of its comprehensive shell extension components package
enables Delphi and C++Builder developers to write feature-rich applications
with the help of shell/namespace extensions. ShellPlus offers a powerful set of
VCL components to extend Windows
Explorer with third-party applications
without having to implement all operating system level required interfaces.
ShellPlus 2.3 helps developers create
namespace extensions, shortcut menus,
property sheets, thumbnail image handlers, and other kinds of shell extensions
without worrying about the complexity of how it all works. This approach
allows developers to add advanced features to their applications without getting bogged down in the details of low-

level Windows Shell


development.
ShellPlus 2.3 targets Delphi and
C++Builder developers who want to
implement shell/
namespace extensions without having to invest large
amounts of valuable
time investigating
the practices and
pitfalls of Windows
Shell development.
ShellPlus 2.3 handles
all required methods and interfaces,
allowing developers to focus on writing their application code. The results
are applications that provide a high
degree of integration with the Windows Shell while leveraging operating

Shunra Software Announces Shunra\Storm 3.1


Shunra Software announced Shunra\
lab, enabling testing, networking, and
Storm Solution Suite 3.1, which delivers
performance professionals to evaluate
centralized management of the entire
the functionality, robustness, perforenterprise lab, including end-user load
mance, and scalability of any distribemulation and third-party automated lab
uted n-tier application from the remote
tools, to create a complete best-of-breed
end-user perspective well before global
pre-deployment lab. Shunra\Storm 3.1
deployment. It is ideal for testing any
simplifies the process of preparing applidistributed application, such as ERP/
cations for global deployment and saving CRM, eBusiness, VoIP, Citrix, offshore
testing time and technical resources.
outsourcing, .NET, and Web Services.
Shunra\Storm emulates any enterprise
Through its central console, Shunra\
environment, including the global netStorm 3.1 provides detailed reports that
work and remote end-user load in the
combine and correlate results from all

system features that their customers


use every day.
ShellPlus Development Group
Price: Contact ShellPlus for complete details.
Contact: info@shellplus.com
Web Site: www.shellplus.com

test lab equipment to deliver a comprehensive picture of application functionality, performance, and health once
deployed over the emulated production
environment. Through the central console, stored test scenarios can be re-run
automatically whenever required, thus
saving users the time and effort of recreating the lab environment.
Shunra Software Ltd.
Price: Contact Shunra.
Contact: info@shunra.com
Web Site: www.shunra.com

/n software Releases IP*Works! 6.0


/n software inc. announced the release
of IP*Works! 6.0, the first major upgrade
of the IP*Works! Internet Toolkit in
nearly two and a half years. This release
offers new components and additional
functionality.
IP*Works! 6.0 .NET Edition has been
released to IP*Works! Subscription
customers and was included in the Q4
2003 IP*Works! Subscription Update.
Other editions of IP*Works! 6.0,
including Delphi, ActiveX, Pocket PC,
C++, Java, etc. are scheduled to be
released during Q1 2004.
New components in IP*Works! 6.0
include: RSS, for creating, modifying,
and retrieving RSS feeds; a WebDAV

DELPHI INFORMANT MAGAZINE | March 2004

client component for distributed Web


authoring, versioning, and collaboration; IPMonitor, a network monitor
packet capturing and protocol analysis
component; a full-featured DNS component supporting every major DNS record
format; an SMPP (Short Message Peerto-Peer) client component (used to send
SMS cellular text messages); new SNMP
Agent and SNMP manager components,
now with support for SNMP version
3 and secure network management;
XMPP, a lightweight Jabber client for
communicating with XML-based Jabber
servers; and more.
In addition to the new components, IP*Works! 6.0 contains new

productivity enhancing features.


Major new component features
include network adapter management capabilities, advanced support
for new protocol versions and protocol extensions, customizable performance options, and more.
Every component in IP*Works! 6.0 has
been upgraded with simplified interfaces, expanded help documentation, and
additional sample applications designed
to decrease learning time and increase
overall developer productivity.
/n software inc.
Price: See Web site for details.
Contact: questions@nsoftware.com
Web Site: www.nsoftware.com

C O L U M N S
ADO.NET

&

R O W S

DELPHI 8 FOR THE MICROSOFT .NET FRAMEWORK

By Bill Todd

ADO.NET Data Access


Components
Part II: Command, Parameter, and DataReader

art I of this series looked at the


architecture of ADO.NET and the major
objects that comprise ADO.NET. It started

at the top of the diagram of ADO.NET objects


and examined the Connection and Transaction
objects and how they can be used to access the
metadata of a database. This installment moves
down the object diagram to look at the Command,
Parameter, and DataReader objects. Unless
otherwise noted, the information in this article
applies to all the Command, Parameter, and
DataReader descendants.

last months application. BdpConnection was added by


dragging the Employee connection from the Data Explorer
to the form and changing its name to EmployeeConn.
Adding a Command Object
With the connection object in place you can add a
Command object and use it to execute SQL statements.
To add the Command object, go to the Borland Data
Provider section of the Toolbox and drag a BdpCommand
object to the form. Use the Object Inspector to change the
Command objects Name property to EmployeeCmd and set
the Connection property to EmployeeConn.
Click the CommandText property in the Object Inspector,
then click the ellipsis button to open the Command Text
Editor shown in Figure 2. Select the EMPLOYEE table and
the EMP_NO, PHONE_EXT, and FULL_NAME columns,
then click the Generate SQL button to generate the SQL
statement. Add the following WHERE and ORDER BY
clauses to the statement:

The first sample application that accompanies this article


uses the BDP components and the InterBase Employee database. The second sample application uses the SQL components and the Microsoft SQL Server Pubs database. Please
note that this article was prepared using a pre-release version of Delphi 8, so there may be some differences between
the behavior described here and the shipping product.
Figure 1 shows the main form for the CommandDemo
sample application that accompanies this article (available for download; see end of article for details). It contains three Buttons, a StatusBar, and a ListBox. The tray
contains a BdpConnection object identical to the one in
4

DELPHI INFORMANT MAGAZINE | March 2004

Figure 1: The sample applications form.

Columns

&

Rows

ADO.NET Data Access Components


ParameterName to Job_Country, the BdpType to String,
and the Size to 15. The Direction property determines
whether this is an input or output parameter. The default
is input, which is the correct value for this parameter.
IsNullable is False by default. Change IsNullable to True
if you want to be able to set the parameter to null. You
wont normally need to set the Precision and Scale properties. Their default value of zero means they will be set
automatically by the data provider.
Set the Value property if you want to assign a default
value to the parameter. Note that if you want to set the
Value property to null, you must assign the constant,
DbNull, to the parameter, and the IsNullable property
must be True.

Figure 2: The Command Text Editor.

Creating the Click Event Handler


Listing One (on page 9) contains the code for the Select
Employees buttons Click event handler. The first if statement opens the connection to the database. Then an
assignment to Trans starts a transaction. This is because
InterBase SELECTS require an active transaction. Next, a
try..finally block ensures that the transaction is committed, and that the connection object is closed and disposed
of if an exception is raised.
Within the try..finally block, the transaction object
is assigned to the EmployeeCmd command objects
Transaction property, so the command will execute in the
context of the transaction. The value USA is assigned
to the command objects only parameter. Since the
BdpCommand object doesnt support named parameters,
you must add the parameter objects in the order they
appear in the SQL statement, and refer to them by their
zero-based ordinal number.

Figure 3: The BdpParameter Collection Editor.


WHERE JOB_COUNTRY = ? ORDER BY FULL_NAME

Click OK to save the SQL statement in the Command objects


CommandText property. Note that, with one exception, the
ADO.NET Command objects use the standard ? character as
a placeholder for a parameter, not the named parameters to
which Delphi developers are accustomed. The exception is the
SqlCommand object for Microsoft SQL Server, which requires
named parameters of the form @ParamName. If you were
using the SqlConnection and SqlCommand objects to connect to
a SQL Server database, the WHERE clause would look like this:
WHERE JOB_COUNTRY = @JOB_COUNTRY

Another thing that Delphi developers will miss is that the


Parameter objects are not created for you. Because the
EmployeeCmd object uses a parameterized query, the next
step is to select the Parameters property of the Command
object and click the ellipsis button to open the BdpParameter Collection Editor (shown in Figure 3).
Click the Add button to add a new BdpParameter object
to the Command objects Parameters collection. Set the
5

DELPHI INFORMANT MAGAZINE | March 2004

If you refer to the parameters in several places in your


code, consider defining constants for the parameter numbers and using the constants in the code. If you ever
change the number or order of the parameters in the
SQL statement, all you have to change in your code are
the numbers assigned to the constants. Changing the
constants in one place is easier than searching for every
parameter reference and changing its number.
You need a BdpDataReader object to read the results
returned by the SQL SELECT statement. Line 4 declares
a variable, EmployeeRdr, which is a reference to a
BdpDataReader. The call to the command objects
ExecuteReader method executes the SQL statement and
returns a BdpDataReader. Once the BdpDataReader
has been opened by the call to ExecuteReader, the
connection object is blocked, so youll want to close the
BdpDataReader as quickly as possible. The code in Listing
One uses a nested try..finally block to ensure that the
BdpDataReader is closed, even if an exception occurs.
There are three ways to access the values for the columns
returned by the SELECT statement. Two of the methods
use the ordinal column number. The third uses the column
name. The problem is that using the column name is slow
(using the ordinal number is fast). On the other hand,

Columns

&

Rows

ADO.NET Data Access Components

using the column name means that your code is much less
likely to break if the SELECT statement is changed.
The code in Listing One takes a little more work to write,
but it gives you the best of both worlds. First, three integer
variables are declared to hold the ordinal number of each
column. Then, in the inner try..finally block, calls to the
BdpDataReaders GetOrdinal method are made, passing the
name of the column as a parameter, and returning the ordinal number of the column. Using the name to look up the
column number is still slow, but this code moves that process outside of the while loop that reads the result set, so
the name lookup is only performed once for each column,
not once for each column for each row.

name every time you access the column for every row in
the result set. There is no right way. If the result set is
small, take the easy way and use the column name as the
subscript of the DataReaders Items property. If you have
to read many rows, use one of the first two techniques to
get the best performance. (The code from Figures 4 and 5
is included in the sample application as comments.)
Getting a Single Value
Its common to execute a SQL statement that returns
a single value, and ADO.NET provides an easy way to
do it. The Show Count button in the sample application
uses a BdpCommandObject named CountCmd with its
CommandText property set to SELECT COUNT(*) FROM
EMPLOYEE. Figure 6 shows the buttons Click event handler.

The sample program loads the result set into the ListBox on
the form, so a statement clears the ListBoxs Items property
First a string variable, EmpCount, is declared. When
before entering a while loop that reads the rows by calling
EmpCount is assigned a value, a call is made to the
the BdpDataReaders Read method. Read returns True if there
CountCmd objects ExecuteScalar method. ExecuteScalar
is a record to read. Note that
executes the SQL statement
when the DataReader is opened,
and returns the value of
Its common to execute a
its positioned before the first
the first column in the first
SQL statement that returns
record, so you must call Read
row of the result set as an
a single value, and ADO.NET
before you try to access the valobject. Because the value is
ues in the first row.
returned as an object, the
provides an easy way to do it.
Convert objects ToString
The call to ListBox.Items.Add adds the column values to the
method is used to unbox the value and convert it to a
ListBox. To make the columns line up properly, the ListBoxs string. The string is added to the ListBoxs Items property,
font is set to Courier New. I also used the Format function
and the connection is closed.
to convert the employee number from an integer to a string,
as well as to right justify it in a four-character-wide string. I
Changing Data
also used Format to convert the phone extension to a fixedChanging data is almost the same as selecting
length, four-character string.
data, except that you use the command objects
ExecuteNonQuery method. You use ExecuteNonQuery
I mentioned that there are three ways to read the value from
to execute any SQL statement that doesnt return a
a column in the result set. The code in Listing One calls the
result set. ExecuteNonQuery returns the number of rows
DataReaders type-specific Get methods. The EMP_NO column
affected. Figure 7 shows the code from the Update buttons
is defined as a SMALLINT in the InterBase database, so its
Click event handler. A call to the UpdateCmd command
retrieved by calling GetInt16. The other two columns, PHONE_
objects ExecuteNonQuery method is made to assign the
EXT and FULL_NAME, are VARCHARs, so they are read by
number of rows affected in the RowsAffected variable.
calling GetString.
The UpdateCmd command object in the sample applicaFigure 4 shows another way to get the column values.
tion uses an UPDATE statement to change the Job_Country
This code uses the Items property of the BdpDataReader,
to USA for all records where the Job_Country value is USA.
which is its default property. This means you can provide a
Yes, its a do nothing query, but the advantage is that it
numeric subscript for the EmployeeRdr object, for example,
doesnt change your sample Employee database. After perEmployeeRdr[EmpNoFldNum], to return the value. Note that
forming the update, the number of rows affected is displayed
using this technique returns the value as an object. Using
in the ListBox. You arent limited to INSERT, UPDATE, and
the Format function unboxes the EMP_NO and PHONE_EXT DELETE statements. You can also use Command objects and
column values. However, for the FULL_NAME column,
the ExecuteNonQuery method to execute DDL (data definition
a call to the ToString method is required to convert the
language) statements such as CREATE TABLE.
returned value from an object to a value type. Because
the columns are being referred to by ordinal number, this
Working with Blobs
method is as fast as using the type-specific Get methods.
Accessing blob fields is a bit different, because a single blob
field can contain gigabytes of data. Reading all the data for
Figure 5 shows the third way to get a column value from a all the columns in a row into memory isnt practical if a
row. This code also uses the DataReaders Items property,
table contains one or more large blob columns. To solve this
but uses the column name as the subscript. This is a lot
problem, the Command object has an overloaded version of
less work, because you dont have to declare variables to
the ExecuteReader method, which takes a CommandBehavior
hold each columns ordinal number and call GetOrdinal
parameter. CommandBehavior.SequentialAccess lets you read
for each column. The problem is that this method requires
the values from the columns of a row in sequence, and lets
the DataReader to look up the column number using the
you read blob data in chunks.

DELPHI INFORMANT MAGAZINE | March 2004

Columns

&

Rows

ADO.NET Data Access Components

// Get the ordinal number of each column in the result set.


EmpNoFldNum := EmployeeRdr.GetOrdinal('EMP_NO');
PhoneExtFldNum := EmployeeRdr.GetOrdinal('PHONE_EXT');
FullNameFldNum := EmployeeRdr.GetOrdinal('FULL_NAME');
// Load the result set into the ListBox.
ListBox.Items.Clear;
while (EmployeeRdr.Read) do
ListBox.Items.Add(Format('%4d',
[EmployeeRdr[EmpNoFldNum]]) + ' ' + Format('%4s',
[EmployeeRdr[PhoneExtFldNum]]) + ' ' + EmployeeRdr[
FullNameFldNum].ToString);

Figure 4: Another way to get the column values.

// Load the result set into the ListBox.


ListBox.Items.Clear;
while (EmployeeRdr.Read) do
ListBox.Items.Add(Format('%4d',
[EmployeeRdr['EMP_NO']]) + ' ' + Format('%4s',
[EmployeeRdr['PHONE_EXT']]) + ' ' +
EmployeeRdr['FULL_NAME'].ToString);

procedure CommandForm.UpdateBtn_Click(
Sender: System.Object; e: System.EventArgs);
var
Trans: IdbTransaction;
RowsAffected: Integer;
begin
EmployeeConn.Open;
Trans := EmployeeConn.BeginTransaction(
IsolationLevel.ReadCommitted);
try
EmployeeCmd.Transaction := Trans;
RowsAffected := UpdateCmd.ExecuteNonQuery;
ListBox.Items.Clear;
ListBox.Items.Add(Convert.ToString(RowsAffected) +
' records updated.');
finally
Trans.Commit;
EmployeeConn.Close;
EmployeeConn.Dispose;
end; // try
end;

Figure 7: Running an UPDATE statement.

Figure 5: Getting column values by name.

procedure CommandForm.CountBtn_Click(Sender: System.Object;


e: System.EventArgs);
var
Trans: IdbTransaction;
EmpCount: string;
begin
EmployeeConn.Open;
Trans := EmployeeConn.BeginTransaction(
IsolationLevel.ReadCommitted);
try
EmployeeCmd.Transaction := Trans;
EmpCount := Convert.ToString(CountCmd.ExecuteScalar);
ListBox.Items.Clear;
ListBox.Items.Add(
'There are ' + EmpCount + ' employees.');
finally
Trans.Commit;
EmployeeConn.Close;
EmployeeConn.Dispose;
end; // try
end;

Figure 6: Using ExecuteScaler.

Figure 8 shows the form for the BlobRdr sample


application. This program uses a SqlConnection object and
a SqlCommand object to read the Pub_Id field and the
Logo image field in the Pub_Info table of the Microsoft
SQL Server 2000 Pubs database. You cannot add a
SQLConnection to your project simply by dragging it from
the Data Explorer. Instead, you must drag it from the Tool
Palette and drop it on the form. Set its name to PubsConn
and set the ConnectionString property as follows:
Persist Security Info=True;Integrated Security=SSPI;
database=pubs;server=MyServer;Password=sa;User Id=sa

replacing MyServer with the name of the machine where


your SQL Server 2000 is running and changing the
User Id and Password as necessary. Next, drag a SQL
command object from the Tool Palette to the form, set its
Connection property to PubsConn, its Name to BlobCmd, and
its CommandText property as follows:
7

DELPHI INFORMANT MAGAZINE | March 2004

SELECT PUB_ID, LOGO


FROM PUB_INFO
WHERE LOGO IS NOT NULL

Listing Two (on page 9) is the Click event handler for the
Read Blob button. The PubsConn.Open statement opens the
connection, while the assignment to Trans starts a new
transaction. Immediately within the first try..finally block,
a statement assigns the transaction to the SQLCommand
objects Transaction property. The next statement calls
ExecuteReader and passes the CommandBehavior.Sequent
ialAccess parameter. When you use SequentialAccess you
must access the field values in the order they appear in
the result set. See the online help for information about
the other CommandBehavior options. Two assignments
within the nested try..finally block get the ordinal column
number for the Pub_Id and Logo columns. Next, a while
loop reads all the rows returned by the SQLDataReader.
This sample program saves the Logo blob from each record
to a file on disk. The files are named Logo plus the Pub_
Id plus .bmp. The first statement in the while loop reads
the value of the Pub_Id column and saves it in the Pub_Id
string variable. Next, the assignment to BlobFile creates the
FileStream used to write the contents of the Logo column
to the disk file. The assignment to BlobWriter creates the
BinaryWriter object used to write the blob data to the file.
Then the integer variable BlobIndex is set to zero. BlobIndex
is the location in the blob at which data reading will begin.
The blob data is read by calling the SQLDataReaders
GetBytes method. For a text blob, you would use the
GetChars method. GetBytes takes five parameters. The first
is the ordinal number of the column you want to read. The
second is the position in the blob where you want to start
reading. The third parameter is the buffer you want to read
the blob data into. The buffer is an array of bytes, named
Buff, thats declared in the var section at the beginning
of the event handler. The fourth parameter is the location
in the buffer where the first byte of blob data should be

Columns

&

Rows

ADO.NET Data Access Components

placed. The last


parameter is the
size of the buffer.
GetBytes returns
the number of
bytes read. If you
pass nil for the
buffer parameter,
GetBytes returns
the size of the
blob.
The assignment
to BlobSize calls
GetBytes with
Figure 8: The BlobRdr applications main form.
nil for the buffer
parameter, and
saves the size of the blob in BlobSize. A nested while loop
reads the blob BuffSize bytes at a time, and writes the data
to the FileStream. The first statement in the while loop calls
GetBytes to read data into the byte array named Buff. If the
number of bytes read is zero, a Break statement exits the
while loop. If not, the next statement calls the BinaryWriters
Write method to write the data to the file. The parameters
passed to BlobWriter.Write are the buffer, the starting location
in the buffer array, and the number of bytes to write. The call
to BlobWriter.Flush forces the data to be written to disk. Next,
an assignment to BlobIndex computes the position in the blob
to start the next read. Then BinaryWriter and FileStream are
closed, and the file name and size are added to the ListBox.
The remainder of the code closes the SQLDataReader and
Connection objects.
Conclusion
The Command, Parameter, and DataReader objects provide
all the features you need to SELECT, INSERT, UPDATE, and
DELETE rows in a table. Combined with the Connection
and Transaction objects, the Command, Parameter, and
DataReader objects give you everything you need to work
with database data in code. You can also use the Command
object to execute SQL DDL statements to change the
metadata of your database. The next article in this series
will delve into the DataAdapter and DataSet objects. These
objects let you build the user interface for your application
and work with data in a multi-tier environment.
The files referenced in this article are available for
download on the Delphi Informant Magazine Complete
Works CD located in INFORM\2004\MAR\DI200403BT.

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


and development firm based near Phoenix. He is co-author of four database
programming books, author of more than 100 articles, a contributing editor to
Delphi Informant Magazine, and a member of Team B, which provides technical
support on the Borland Internet newsgroups. Bill is also an internationally known
trainer and frequent speaker at Borland Developer Conferences in the United
States and Europe. Readers may reach him at btarticle@dbginc.com.

DELPHI INFORMANT MAGAZINE | March 2004

Columns

&

Rows

ADO.NET Data Access Components

Begin Listing One The buttons


Click event handler
procedure CommandForm.SelectEmpBtn_Click(
Sender: System.Object; e: System.EventArgs);
var
Trans: IdbTransaction;
EmployeeRdr: BdpDataReader;
EmpNoFldNum: Integer;
PhoneExtFldNum: Integer;
FullNameFldNum: Integer;
begin
// If the connection is not open, open it.
if (EmployeeConn.State = ConnectionState.Closed) then
EmployeeConn.Open;
// Start a transaction. With InterBase SELECTs require
// a transaction.
Trans := EmployeeConn.BeginTransaction(
IsolationLevel.ReadCommitted);
try
EmployeeCmd.Transaction := Trans;
EmployeeCmd.Parameters[0].Value := 'USA';
// Execute SQL statement and get a DataReader object.
EmployeeRdr := EmployeeCmd.ExecuteReader;
try
// Get ordinal number of each column in result set.
EmpNoFldNum := EmployeeRdr.GetOrdinal('EMP_NO');
PhoneExtFldNum :=EmployeeRdr.GetOrdinal('PHONE_EXT');
FullNameFldNum :=EmployeeRdr.GetOrdinal('FULL_NAME');
// Load the result set into the ListBox.
ListBox.Items.Clear;
while (EmployeeRdr.Read) do
ListBox.Items.Add(Format('%4d',
[EmployeeRdr.GetInt16(EmpNoFldNum)]) + ' ' +
Format('%4s', [EmployeeRdr.GetString(
PhoneExtFldNum)]) + ' ' + EmployeeRdr.GetString(
FullNameFldNum));
finally
// Close the DataReader immediately. The connection
// is blocked until the DataReader is closed.
EmployeeRdr.Close;
end; // try
finally
Trans.Commit;
EmployeeConn.Close;
// Dispose of connection so its resources will be
// released immediately.
EmployeeConn.Dispose;
end; // try
end;

End Listing One


Begin Listing Two The Read Blob
buttons Click event handler
procedure BlobForm.BlobBtn_Click(Sender: System.Object;
e: System.EventArgs);
const
BuffSize = 256;
var
Trans: SqlTransaction;
BlobRdr: SqlDataReader;
BlobSize: Integer;
BytesRead: Integer;
PubIdFldNum: Integer;
BlobFldNum: Integer;
Buff: array[0..BuffSize - 1] of Byte;
PubId: string;

DELPHI INFORMANT MAGAZINE | March 2004

BlobFile: FileStream;
BlobWriter: BinaryWriter;
BlobIndex: Integer;
begin
PubsConn.Open;
// Start a transaction.
Trans := PubsConn.BeginTransaction(
IsolationLevel.ReadCommitted);
try
// Execute SQL statement and get a DataReader object.
BlobCmd.Transaction := Trans;
BlobRdr := BlobCmd.ExecuteReader(
CommandBehavior.SequentialAccess);
try
ListBox.Items.Clear;
// Get ordinal number of each column in result set.
PubIdFldNum := BlobRdr.GetOrdinal('pub_Id');
BlobFldNum := BlobRdr.GetOrdinal('logo');
// Loop through DataReader and read all of the rows.
while (BlobRdr.Read) do begin
// Get the value of the Pub_Id column.
PubId := BlobRdr.GetString(PubIdFldNum);
// Create a FileStream and a BinaryWriter to write
// the blob to a disk file.
BlobFile := FileStream.Create('Logo' + PubId +
'.bmp', FileMode.OpenOrCreate, FileAccess.Write);
BlobWriter := BinaryWriter.Create(BlobFile);
// Set starting position within blob field to zero.
BlobIndex := 0;
// Get size of blob by passing nil for the buffer.
BlobSize := BlobRdr.GetBytes(
BlobFldNum, 0, nil, 0, BuffSize);
// Loop through the blob, reading and writing
// BuffSize bytes at a time.
while (True) do begin
// Read the next buffer of bytes from the blob.
BytesRead := BlobRdr.GetBytes(BlobFldNum,
BlobIndex, Buff, 0, BuffSize);
// If nothing was read, quit.
if (BytesRead = 0) then
Break;
// Write contents of buffer array to the file.
BlobWriter.Write(Buff, 0, BytesRead);
BlobWriter.Flush;
// Advance index into blob field by
// size of buffer.
BlobIndex := BlobIndex + BytesRead;
end; // while
// Close the BinaryWriter and FileStream.
BlobWriter.Close;
BlobFile.Close;
// Add the file name and size to the ListBox.
ListBox.Items.Add('Logo' + PubId + '.bmp ' +
BlobSize.ToString + ' bytes')
end; // while
finally
// Close the DataReader immediately. The connection
// is blocked until the DataReader is closed.
BlobRdr.Close;
end; // try
MessageBox.Show('Blob files successfully created.');
finally
Trans.Commit;
PubsConn.Close;
PubsConn.Dispose;
end; // try
end;

End Listing Two

I N

D E V E L O P M E N T

SOFTWARE PROCESS MANAGEMENT

ASSESSMENT

By Steven Beebe

Between Rigidity and Chaos


Part II: Project Retrospectives (AKA Post Mortems)

his series presents a practical approach


to software process management thats
designed to improve your success at fulfilling

project objectives, that is, achieving time-to-market


with the appropriate quality level, and managing
project lifecycle costs. Too often development teams
are operating at one of two extremes:

Rigidity: highly structured processes combined with a


zeal for adhering to the process that often threatens the
ability to get work done.
Chaos: seat-of-the-pants survival by responding to the
crisis du jour.
Where you are between rigidity and chaos is not a question
of right or wrong, but one of appropriateness. As we move
through this series, we wont be advocating a single correct
answer for all teams, but rather working with you to discover the best approach for your situation and environment.
This article focuses on assessing where you are and how
well its working.
If you havent already, read Part I in the January issue of
Delphi Informant to get an idea of the various Process Management Components to consider.
Where Are You?
Future articles will discuss the process management
components in detail. In preparation, you need to get a
handle on where you are with your current processes.
10

DELPHI INFORMANT MAGAZINE | March 2004

If you tend toward the chaos, you may be wondering


exactly what your processes are, or if you even have any.
But dont worry, you do have them. They are what you
do each day. As we work through an assessment of what
is working and what is not, youll get a handle on some
of these processes. If you tend toward rigidity, your task
will in some ways be easier. You undoubtedly have megabytes of process documentation that will be a boon to the
assessment process. However, the presence of a highly
structured process is often accompanied by a certain level
of organizational inertia that can hinder change.
Step one in improving your performance is to determine
what works and what doesnt. This process is called a
project retrospective. Project retrospectives are also known
as project post-mortems. If these terms seem too formal or
academic, feel free to substitute the term lessons learned.
Learn from your experiences and use that learning to
improve the future. Retrospectives are done for the same
reason you identify the business objectives of a project
before you identify the features to include. In the absence
of clear business objectives, all features appear to be
important. In the presence of explicit objectives, it becomes
much easier to separate the must-haves from the nice-tohaves. Or, if you prefer a non-software analogy, it is the
same reason you want a grocery list before you go grocery
shopping; without one, you end up buying things you
dont need and forgetting things that you do.
Identifying what is working and what is causing you problems will build your shopping list. Then, as we discuss each
software process management component, youll be able to
quickly extract the concepts, techniques, and tools that will
deliver the biggest improvement for your situation.

In

Development

Between Rigidity and Chaos

Theres Always a Big But


Objection: I dont have time for this. We dont have time
for non-productive activities, either. But stay with us on this
one. If youve never taken the time to involve an entire project team to assess your current practices, then we guarantee
an immediate payback from your efforts. Youll discover at
least one issue that you can address immediately and that
will return the time invested to identify it. If you routinely
review your current practices, we encourage you to read on
in order to benchmark your approach.

capable of doing within the constraints of the processes.


If a persons best is unacceptable, that is a personnel
issue, not a software process issue.
3) Were looking for practices to keep and practices to
change. We arent looking to give credit or assign blame.
Although giving credit is much less problematic than
assigning blame, both should be avoided as part of a
retrospective. Giving credit and assigning blame shifts
the focus from the effectiveness of your processes to the
performance of individuals.

Objection: We tried that before and it was a disaster.


Perhaps youve had some bad experiences with this type
of activity. Maybe some people felt they were bludgeoned
(unfairly or not) for the outcome of the project. Maybe
the discussions got too personal and damaged working
relationships. Maybe a lot of time was spent and nothing came of it. Or even worse, a lot of time was spent in
follow-up activity that was a waste of time. Just because
youve had a bad experience doesnt mean reviewing your
practices isnt valuable, but it certainly means youll need
a different approach.

Selecting the Forum


The next item to tackle is to select the forum for the retrospective. Generally, the preferred approach to conducting a retrospective is to hold a face-to-face meeting led
by a skilled facilitator. A skilled facilitator should be able
to: a) keep a discussion on track; b) manage and diffuse
tense situations; and c) encourage participation. A facilitator should not be contributing content to the discussion.
In other words, a retrospective is usually not conducted
by your lead developer.

Fundamentally, conducting a project retrospective is an investment activity. The approach well be taking is very focused and
will yield short-term results. Some practitioners would have
you start by documenting all your current processes. Although
there are sound reasons for taking this approach, its rarely
practical for an organization thats under any kind of market
pressure, whether it be cost or time to market.
Project Retrospective
Typically, a project retrospective is conducted after the
completion of a project, or after the completion of a major
milestone. If you arent at the end of a project or major
milestone, there is still value in assessing the effectiveness
of your current practices. Of course, the earlier you are in a
project, the less there is to assess. If youre in the very early
stages of a project, I recommend incorporating experiences
from prior projects into the review.
Getting Started
The first thing to be clear on is the objective: To identify
practices that are working, and practices that are not. There
are three things to note with our objective:
1) What is meant by working/not working? Working means
making a positive contribution to successfully achieving
the objectives of the project. Working does not mean: I
like doing it this way. Conversely, not working means
hindering the successful achievement of the objectives of
the project. Not working does not mean: I dont want
to do that. This assessment is not about personal preferences.
2) The subject of discussion should be practices, not people. One of the largest reasons for the failure of a project
retrospective is the confusion of people performance
with process performance. If you have incompetent
team members, do not try to fix that through a project
retrospective. Your organization already has personnel
processes for dealing with those situations, so use them.
A project retrospective should be done with the assumption that all team members did the best job they are
11

DELPHI INFORMANT MAGAZINE | March 2004

However, the preferred approach assumes a number


of things about the work environment that may not be
true about where you work. A face-to-face retrospective
assumes that:
Your project team is located in a single location.
People in your organization are comfortable (or can
reasonably be made to feel comfortable) critiquing how
work gets done.
People can focus on process issues, leaving aside individual performance.
People can critique processes in which they have a vested interest without being defensive.
Facilitation skills exist in the organization.
If these assumptions are generally true for your organization, an in-person meeting is the best approach. If these
assumptions arent true for your organization, or if you have
a history of failed attempts at similar activities, it will be
important to be creative in how you approach conducting a
retrospective. For example, using an anonymous approach
or electronic approach for gathering input. The subsequent
steps can be done by an individual or subset of the team.
Preparation
The real power of a project retrospective comes from the
combined perspectives of the team. To get the best results,
youll want participants to do some preparatory work. Participant preparation should center on:
1) How well the project met established objectives.
2) Feedback on software process components.
3) Feedback on team processes.
If you will be reviewing a completed project or milestone,
its critical to start with an assessment of how well the
project/milestone accomplished the established objectives. Participants should identify the underlying causes
for any objectives that werent fully satisfied, as well as
key practices that enabled achieving objectives. If you
dont have a completed project or milestone, then leave
this piece out.

In

Development

Between Rigidity and Chaos

You want to trigger feedback in each of the software process


components described in Part I of this series. Have each
participant read the first article in this series so theres a
common frame of reference for each area. In addition, team
processes, such as communication, status reporting, and
progress meetings, should be reviewed. If your processes
are well developed, you should consider developing specific
questions within each component, designed to assess your
existing processes. If you tend more toward the chaos end of
the spectrum, start small and keep it simple.
For software process components and team processes, each
participant should prepare a list of things that are working
and not working. Encourage them to keep the list balanced.
Encourage people to think beyond symptoms to underlying causes. For example, an issue in the Project Management area might be: Not enough time allowed in the
schedule. In some cases, this will be an underlying cause
(although it needs to be more specific: time for what?),
but it may also be a symptom. An example of an underlying cause would be: We dont have Microsoft Office COM
automation expertise and the schedule doesnt allow time
to learn it. Identifying underlying causes will provide
a broader possible set of solutions. In our example, the
issue as originally stated only allows for more time as the
solution. The second statement opens the possibility of
acquiring the needed expertise through other means than
having someone on the team learn it.
Finally, distribute the preparation material, including an
overview of the objective, leaving participants adequate time
to complete it. A week in advance should be sufficient.
Conducting the Retrospective
Conducting a retrospective involves the following steps:
1) Define the objective and ground rules.
2) Get the ideas out in the open.
3) Find commonality in the ideas.
4) Prioritize problem areas based on impact.
5) Establish an action plan.
6) Wrap up.
These steps assume youll be conducting a meeting, but all
of this can be accomplished without a meeting.
Define the objective and ground rules. First, be sure that
everyone is clear on the objective. You should have communicated the objective as part of the prep-work; however, its
worth reiterating at this point. Also, this is the right time to
establish some ground rules for the meeting, with the goal
being to encourage productive discussion. Good ideas for
ground rules include: 1) focus on process not performance,
2) criticism is directed at how we work, not ideas raised in

Need Help Getting Started?


Check out the resources at www.xapware.com/projretro.htm
if you feel you need additional information to get you started
on conducting a project retrospective, or to convince the
powers-that-be that one is needed.

12

DELPHI INFORMANT MAGAZINE | March 2004

the meeting (in other words, everyone is entitled to an opinion), and 3) represent your own perspective dont try to
speak for others.
Get the ideas out in the open. There are many techniques
to accomplish this step. A straightforward and effective
approach is to have each participant write their feedback
on Post-it notes. Only write one idea per note. At the top of
the note, write a short header indicating the objective, software process component, or team process to which the idea
refers. The notes are then placed on a wall or white board
so they can be viewed by everyone. Preliminary grouping
can be done based on the note headers. This approach effectively ensures that everyone contributes their ideas.
Find commonality in the ideas. Continue the process of
grouping, but now based on the note content. Notes put into
the same group should essentially be saying the same thing,
or be slightly different aspects of the same practice. This
may result in items with different headers being grouped
together. Have everyone participate in the process. An effective ground rule for this step is to do it without talking. This
encourages thought and avoids disruptive argument. The
goal here is not to be precise, but reasonable. Stop the exercise when the movement of the notes slows down.
It is often valuable at this point to talk through the groupings to ensure a common understanding of what each group
represents. Avoid significant regrouping at this point, but
feel free to fine tune.
Prioritize problems based on impact. The focus will
now shift to just the areas that represent problems or
opportunities to improve. There are quantitative techniques to prioritize these items, but most teams will find
qualitative approaches to be quicker and good enough,
i.e. more practical. A reasonable approach is to use a
multi-voting process. To do this, give each team member
a fixed number of votes. Keep the number small; five
votes are generally sufficient. Each team member is asked
to vote for what they see as the most important five items
to address. If your notes are on a white board, votes can
be cast placing a tick mark next to the grouping. When
voting is done, priority is established based on the number of votes a grouping receives.
Briefly discuss the results and make your final selection
of the problem areas to address. Again, fine tuning of the
results can be done at this stage. Look for low hanging
fruit, i.e. items that might not have made it to the top of
the list, but have a large payback for a relatively small
investment. The result of this step is to have two or three
items to address.
Establish an action plan. Depending on the complexity of
the top items, action plans can be developed as part of this
meeting or sub-teams can be tasked to develop the plans.
Sub-teams should present their work back to the complete
team for acceptance. Action plans require:
1) a specific action to be taken,
2) a person responsible for taking the action, and
3) a time frame for completing the action.

In

Development

Between Rigidity and Chaos

Go ahead and knock off the top one or two items from your
list. This will give you the return for your time spent in the
retrospective. The remaining items will be your shopping
list as we walk through the software process components in
future articles.
Wrap up. Establish a follow-up step to report back on
progress. This can often be done electronically. Also, spend
some time to gather comments on the preparation materials. This should take no more than five to 10 minutes and
should be used to improve the materials for the next retrospective.
Making a Difference
If youve gotten this far, the worst thing you could do is to
not follow through on the action plans. Navigating through
this stage will take discernment and some courage. It will be
somewhat natural for people to feel like they dont have the
time to implement the action plans. After all, it is unlikely
that anyone on the team feels like they have time to burn.
However, the benefits to completing the action plans should
be obvious. If people have the sense that the action plans
arent really going to make a difference, then its possible
the plans are off target and need to be adjusted. Or it may
simply be a matter of carving out the time.
If the plans are on target, its important to remember this is
an investment activity. If prioritizing was done well, changing these practices will make a difference. Its now your job
to make it happen. Dont start if you dont think you can
drive it through to completion when you get to this point.
You will have lost valuable time for nothing and become the
topic of hallway conversations in the process. If you stay
the course, youll not only gain the benefits from solving the
current issues, but set the stage for solving future issues.
Building for the Future
Having done one project retrospective is a valuable activity, and should produce immediate benefits. There is tremendous additional value to be gained by making this a
regular practice in your organization. Improvements from
subsequent retrospectives yield an accumulating return. The
results can be dramatic even over the course of a fairly
small number of projects. This is one activity you cant
afford not to make time for.
A practical approach to retrospectives will improve your
success at delivering on project objectives achieving time
to market with the appropriate quality level and managing
project lifecycle costs. And this can be done within both the
budget and the bandwidth of virtually every software development team.

Steven Beebe is the chief operating officer and senior consultant with
Xapware Technologies Inc., provider of Active! Focus a practical solution
for managing a number of aspects of software projects. Steve has been
developing software and managing the development of software for over
15 years. Steves experience ranges from managing teams from 10 to 120
professionals, in both start up and Fortune 20 companies. Steve is available for
consulting engagements. You may contact him at steve@xapware.com or visit
www.xapware.com.

13

DELPHI INFORMANT MAGAZINE | March 2004

. N E T

D E V E L O P E R

.NET FRAMEWORK
.NET COLLECTIONS
THE .NET FRAMEWORK

FILE I/O

DELPHI 8 STUDIO FOR

By Xavier Pacheco

Exploring the System.IO


Namespace
Part I: Working with .NET Directories and Files

t some point, every programmer will


have to deal with file input/output (I/O),
and directory and file manipulation. The

Microsoft .NET Frameworks System.IO namespace


defines the classes and types that you can use to

Working with Directory and


DirectoryInfo
As shown in Figure 1, there are two classes that you can
use to work with directories: Directory and DirectoryInfo.
Class

Purpose

Directory

Provides static methods for performing directory


operations such as copying, moving, renaming,
creating, and deleting. Methods in the Directory
class perform a security check, so if you intend
to perform more than one operation, use the
DirectoryInfo class instead.

DirectoryInfo

Provides instance methods for performing directory operations such as copying, moving, renaming, creating, and deleting. These methods do
not perform a security check, so this is the best
class for performing numerous operations.

File

Provides static methods for file operations such


as copying, moving, deleting, opening, and closing. These methods perform a security check, so
if you intend to perform numerous operations,
use the FileInfo class.

FileInfo

Provides instance methods for file operations


such as copying, moving, deleting, opening, and
closing. These methods do not perform a security
check, so this is the best class for performing
numerous operations.

FileSystemInfo

Base class for the DirectoryInfo and FileInfo classes.

Path

Enables cross-platform processing of directory


strings.

perform file, directory, and streaming operations.


In this article, I will discuss the classes in the
System.IO namespace that deal with directories
and files. I will also illustrate how you can
monitor the activity within a directory using a
special class from System.IO.
The Key Classes
The System.IO namespace provides several classes for performing asynchronous and synchronous I/O. For the purpose of this article, these classes can be discussed in two
groups: File/directory classes and streaming classes.
File and directory classes are for working with directories
and files as they exist on disk. Streaming classes deal with
reading and writing data to other mediums (including files).
For instance, a stream can be a file, memory, or network
share. The primary classes that deal with directories and
files are listed in Figure 1. The remainder of this article will
discuss using the directory and file related classes. I will
cover streaming classes next month.
14

DELPHI INFORMANT MAGAZINE | March 2004

Figure 1: File and directory classes (from Delphi for .NET Developers Guide).

.NET

Developer

Exploring the System.IO Namespace

The Directory class contains static methods, so its convenient for performing a single operation such as creating or
moving a directory. However, you should know that calling a method from this class results in a security check
each time the method is invoked. This isnt recommended
if youre concerned about performance, or if youll be
doing the same operation repeatedly.
The following code snippets illustrate the use of the
Directory class. Most of these methods will be the same or
similar when using the DirectoryInfo class, but Ill point
out key differences.
Creating and deleting directories. Creating a directory
requires the use of the CreateDirectory method:
if not Directory.Exists('c:\eeekaaak') then
Directory.CreateDirectory('c:\eeekaaak');

Note how this differs when using the DirectoryInfo class


which is based on instance methods. In this case, you
must distinguish between the Create constructor and
Create method by using the ampersand symbol (&).
The Create constructor doesnt actually create the
directory, but only associates the class instance with
that path. You must call the Create method to actually
create the directory:
var
dirInfo: DirectoryInfo;
begin
dirInfo := DirectoryInfo.Create('c:\eeekaaak');
if not dirInfo.Exists then
DirInfo.&Create;
end;

Deleting a directory involves using the Delete method.


Using the Directory class, the method takes the path and
a Boolean as parameters to indicate a recursive deletion.
When you dont specify a recursive deletion and subdirectories exist, an exception will be raised:
if Directory.Exists('c:\eeekaaak') then
Directory.Delete('c:\eeekaaak', True);

Using the DirectoryInfo class, the Delete method takes


only the Boolean recursive indicator:
var
dirInfo: DirectoryInfo;
begin
dirInfo := DirectoryInfo.Create('c:\eeekaaak');
if DirInfo.Exists then
DirInfo.Delete(True);
end;

Moving and copying directories. Moving a directory


requires a simple, one-line statement:
Directory.Move('c:\work\blahblah', 'c:\eeekack');

This can also work to rename a directory:


Directory.Move('c:\eeekack', 'c:\fumpgoof');

15

DELPHI INFORMANT MAGAZINE | March 2004

procedure CopyDirectory(SourceDir, TargetDir: string);


var
FilesToCopy: array of string;
i: Integer;
fa: FileAttributes;
cfile: string;
begin
// Fix up directory separator char.
if SourceDir[SourceDir.Length-1] <>
Path.DirectorySeparatorChar then
SourceDir := SourceDir + Path.DirectorySeparatorChar;
if TargetDir[TargetDir.Length-1] <>
Path.DirectorySeparatorChar then
TargetDir := TargetDir + Path.DirectorySeparatorChar;
if Directory.Exists(SourceDir) then begin
FilesToCopy :=
Directory.GetFileSystemEntries(SourceDir);
for i := Low(FilesToCopy) to High(FilesToCopy) do begin
cFile := FilesToCopy[i];
fa := System.IO.File.GetAttributes(cFile);
if (fa and FileAttributes.Directory) =
FileAttributes.Directory then
begin
if not Directory.Exists(
TargetDir+Path.GetFileName(cFile)) then
Directory.CreateDirectory(
TargetDir + Path.GetFileName(cFile));
// Since it's a directory, recurse it...
CopyDirectory(
cFile, TargetDir + Path.GetFileName(cFile));
end
else // ...otherwise, copy the file.
System.IO.File.Copy(
cFile, TargetDir + Path.GetFileName(cFile), True)
end;
end;
end;

Figure 2: Copying a directory.

Copying a directory requires more effort. There is no built-in


method in the Directory or DirectoryInfo class to copy a directory, so you must roll your own. As shown in Figure 2, Ive
already done this for my upcoming book, Delphi for .NET
Developers Guide.
This procedure basically iterates through subdirectories
recursively. All subdirectories are created in the target directory. Additionally, the procedure copies non-directory files.
There are some additional methods used from the System.IO
namespace that Ill mention.
Note the use of the Path class. Path is used to manipulate
strings that hold directory path information in a cross-platform
manner. Its used here to attach the proper directory separator
character to the source and target directories. Youll notice the
Directory.GetSystemFileEntries method is also called; it returns
an array of string of all files and directories for the specified
directory. This is the array that is iterated to process subdirectories. The rest of the code uses the File and FileInfo classes,
which I will discuss momentarily.
Other useful methods of the Directory class not shown in the
above example are: CreateSubdirectory which not surprisingly creates a subdirectory or subdirectories in the current
directory; GetDirectories, which returns an array of subdirectories for the current directory; and GetFiles, which returns an
array of files for the current directory.

.NET

Developer

Exploring the System.IO Namespace

var
sw: StreamWriter;
begin
if not System.IO.File.Exists('c:\deleteme.txt') then
begin
sw := System.IO.File.CreateText('c:\deleteme.txt');
try
sw.Write('hello world');
finally
sw.Close;
end;
end;
end;

Figure 3: Creating a text file.

The File and FileInfo Classes


The File and FileInfo classes parallel the Directory and
DirectoryInfo classes in how they provide static and instance
methods, respectively.
Creating and deleting files. There are several ways to create a file. For the purposes of this article, Ill demonstrate a
simple method for creating a text file. In the next article, Ill
discuss streams and go into more depth regarding file creation and manipulation. The code shown in Figure 3 illustrates how to create a simple text file.
The CreateText method returns an instance of a StreamWriter
class. StreamWriter, a descendant of TextWriter, is used to
write to a text file. (Again, working with streams will be next
months topic.) The example here should be sufficient to illustrate the process of writing text to a newly created text file.

Member

Description

Attributes

Refers to the FileAttributes enumeration (see


Figure 5).

CreationTime,
CreationTimeUtc

Returns time file was created. The property


ending Utc is in Coordinated Universal Time.

Exists

Specifies whether the file exists.

Extension

Returns the files extension.

FullName

Returns the full path of the file.

LastAccessTime,
LastAccessTimeUtc

Returns last time file was accessed (standard


and UTC).

LastWriteTime,
LastWriteTimeUtc

Returns last time file was written to (standard


and UTC).

Delete

Deletes the file or directory.

FullPath

Returns the fully qualified path to the


directory or file.

OriginalPath

Returns the path originally specified by the


user, which may be relative.

Figure 4: FileSystemInfo class members.

Member

Description

Archived

Files archived status for backup or removal.

Compressed

A compressed file.

Device

Unused/Reserved.

Directory

File is a directory.

Encrypted

Determines that file is encrypted (if referring


to a file), or that newly created files in this
directory will be encrypted (if referring to a
directory).

Hidden

A hidden file (wont be included in normal


directory listing).

Normal

A normal file. No other attributes may be


set.

NotContextIndexed

File may not be indexed by the context


indexing service of the operating system.

Offline

File is offline and data is not immediately


available.

ReadOnly

File is read-only.

ReparsePoint

File contains a user-defined block of data.

SparceFile

File is a sparse file containing mostly zeros.

System

File is an operating system file, or is used


exclusively by operating system.

Temporary

File is a temporary file.

Deleting a file is a simple operation:


if System.IO.File.Exists('c:\deleteme.txt') then
System.IO.File.Delete('c:\deleteme.txt');

Moving and copying files. To move a file use the File.Move


method:
if System.IO.File.Exists('c:\deleteme.txt') then
System.IO.File.Move('c:\deleteme.txt',
'c:\deleteme2.txt');

Likewise, to copy a file, use the File.Copy method:


if System.IO.File.Exists('c:\deleteme.txt') then
System.IO.File.Copy('c:\deleteme.txt',
'c:\copy_deleteme.txt');

Examining Directory and File


Information
The DirectoryInfo and FileInfo classes descend from the
FileSystemInfo class. The FileSystemInfo class contains the
properties and methods shown in Figure 4.

output shown in Figure 7. Note that there is a requirement


to typecast the Attributes enumeration as an Enum.

The Attributes property of the FileSystemInfo class is a


FileAttributes enumeration which contains the FlagsAttribute
allowing bitwise combinations of the Attributes property.
Figure 5 lists the various values which may or may not
apply to both files and directories. As an example, the code
shown in Figure 6 uses these properties to generate the

Monitoring Directory Activity


The FileSystemWatcher class is useful for monitoring events
that occur within a directory. FileSystemWatcher invokes
various events when a file is created, modified, deleted, or
renamed. You provide the event handlers and specify which
actions are to occur within the event handler.

16

DELPHI INFORMANT MAGAZINE | March 2004

Figure 5: FileAttributes enumeration members.

.NET

Developer

Exploring the System.IO Namespace

program Project1;
{$APPTYPE CONSOLE}

Value

Description

Attributes

Monitor changes to the file or folder attributes.

CreationTime

Monitor changes to the time the file or folder is


created.

DirectoryName

Monitor changes to the directory name.

FileName

Monitor changes to a file name.

LastAccess

Monitor changes to the date the file or folder


was last accessed/opened.

LastWrite

Monitor changes to the date the file or folder


was written to.

Security

Monitor changes to the file or folders security


settings.

Size

Monitor changes to the size of the file or folder.

uses System.IO;
var
fi: FileInfo;
begin
fi := FileInfo.Create('c:\msdos.sys');
Console.WriteLine(fi.FullName);
Console.WriteLine(fi.CreationTime);
Console.WriteLine(fi.Exists);
Console.WriteLine(fi.Extension);
Console.WriteLine(fi.LastAccessTime);
Console.WriteLine(fi.LastWriteTime);
Console.WriteLine(Enum(fi.Attributes).ToString);
Console.ReadLine;
end.

Figure 6: Using the FileInfo class. This code produces the output shown in Figure 7.

c:\msdos.sys
1/11/2003 7:11:00 PM
True
.sys
10/22/2003 9:30:32 AM
1/11/2003 7:11:00 PM
ReadOnly, Hidden, System, Archive, NotContentIndexed

Figure 9: NotifyFilters enumerations.

property is where you specify the types of changes that you


want monitored. Figure 9 lists the various values that can be
assigned to this property.
You can also specify the file types to monitor through the
FileSystemMonitor.Filter property. Finally, you must assign
the various event handlers to their respective events, and set
the EnableRaisingEvents property to True.

Figure 7: Output was produced by the code shown in Figure 6.

There are four events you can use:


Changed. Fires whenever a file or directory is modified
within the monitored directory.
Created. Fires whenever a file or directory is created
within the monitored directory.
Deleted. Fires whenever a file or directory is deleted
from the monitored directory.
Renamed. Fires whenever a file or directory is renamed
within the monitored directory.
The Changed, Created, and Deleted events take a
FileSystemEventArgs parameter, from which you can
determine the change type and file name. The Renamed
event takes a RenamedEventArgs parameter, from which you
can determine the change type and old and new file names.
Figure 8: Using the FileSystemWatcher class.

An application that accompanies this article (see Figure 8)


illustrates how to use the FileSystemWatcher class. This application monitors a specified directory and adds the information to the TextBox as shown. The code for this application is
shown in Listing One.
The FileSystemMonitor constructor takes the directory path
that you want to monitor as a parameter. The NotifyFilters

17

DELPHI INFORMANT MAGAZINE | March 2004

Thats it for files and directories. Next month, well explore


the streaming capabilities of the System.IO namespace.

Xavier Pacheco is the president and chief consultant of Xapware Technologies


Inc., provider of Active! Focus a practical solution for managing software projects
and requirements management. Xavier is the co-author of Delphi 6 Developers
Guide and has also written for publications such as Delphi Informant and Delphi
Magazine. Xavier is available for consulting and training engagements. You may
contact him at xavier@xapware.com or visit www.xapware.com.

.NET

Developer

Exploring the System.IO Namespace

Begin Listing One Monitoring


Directory Activity

Include(fsw.Deleted, OnDeleted);
Include(fsw.Renamed, OnRenamed);
fsw.EnableRaisingEvents := True;
end;

unit WinForm1;
interface
uses
System.Drawing, System.Collections,
System.ComponentModel,
System.Windows.Forms, System.Data, System.IO;
type
TWinForm1 = class(System.Windows.Forms.Form)
TextBox1: System.Windows.Forms.TextBox;
private
procedure OnChanged(source: System.Object;
e: FileSystemEventArgs);
procedure OnCreated(source: System.Object;
e: FileSystemEventArgs);
procedure OnDeleted(source: System.Object;
e: FileSystemEventArgs);
procedure OnRenamed(source: System.Object;
e: RenamedEventArgs);
public
fsw: FileSystemWatcher;
constructor Create;
end;
implementation
constructor TWinForm1.Create;
begin
inherited Create;
InitializeComponent;
fsw := FileSystemWatcher.Create('c:\work\');
fsw.NotifyFilter := NotifyFilters.Attributes or
NotifyFilters.CreationTime or
NotifyFilters.DirectoryName or
NotifyFilters.FileName or NotifyFilters.LastAccess or
NotifyFilters.LastWrite or NotifyFilters.Size;
fsw.Filter := '*.*';
Include(fsw.Changed, OnChanged);
Include(fsw.Created, OnCreated);

18

DELPHI INFORMANT MAGAZINE | March 2004

const
crlf = #13#10;
procedure TWinForm1.OnChanged(source: TObject;
e: FileSystemEventArgs);
begin
TextBox1.Text := TextBox1.Text + System.String.Format(
'Change Type: {0}, File: {1}' + crlf,
[e.ChangeType, e.FullPath]);
end;
procedure TWinForm1.OnCreated(source: TObject;
e: FileSystemEventArgs);
begin
TextBox1.Text := TextBox1.Text + System.String.Format(
'Change Type: {0}, File: {1}' + crlf,
[e.ChangeType, e.FullPath]);
end;
procedure TWinForm1.OnDeleted(source: TObject;
e: FileSystemEventArgs);
begin
TextBox1.Text := TextBox1.Text + System.String.Format(
'Change Type: {0}, File: {1}' + crlf,
[e.ChangeType, e.FullPath]);
end;
procedure TWinForm1.OnRenamed(source: TObject;
e: RenamedEventArgs);
begin
TextBox1.Text := TextBox1.Text + System.String.Format(
'Change Type: {0}, Renamed From: {1} To: {2}' + crlf,
[e.ChangeType, e.OldFullPath, e.FullPath]);
end;
end.

End Listing One

T H E
TIMING

A P I

PROFILING

C A L L S

DELPHI 4-7

By Fernando Vicaria

Perfect Timing
Gauging Win32 App Performance (AKA Profiling)

t one level or another, every program we


write will contain bottlenecks. This is
usually acceptable if the bottlenecks arent

detrimental to the programs output and not perceptible to users. Performance requirements will
ultimately define what will be considered good,
bad, or acceptable in a real-world application.
This article presents some of the timing functions
available to Win32 developers some from the Win32
APIs, some directly from the hardware, and some from
Delphis RTL. Well see what kind of precision and
accuracy we can obtain, as well the overhead associated
with each. Well also build a simple framework that you
can use for profiling simple tasks in your projects.
Along the way, its important to keep in mind that all
measurements we make will affect the task being measured.
Its up to us to take that effect into consideration when
analyzing the results of our measurements. This is why
knowing the accuracy, resolution, and overheads of our
testing methods are crucial.
Win32 Timing Functions
The operating system makes available to us a variety
of timing functions. Well look at seven of those
most commonly used. They are all implemented in
Kernel32.dll, with the exception of the last, TimeGetTime,
which is found inside Winmm.dll. Here are the functions
and their descriptions as per the SDK documentation:
19

DELPHI INFORMANT MAGAZINE | March 2004

1) GetTickCount retrieves the number of milliseconds that


have elapsed since Windows was started.
2) GetSystemTime retrieves the current system date and time
expressed in UTC (Coordinated Universal Time). You must
pass a pointer to a SYSTEMTIME structure.
3) GetSystemTimeAsFileTime obtains the current system
date and time in UTC format. You must pass a pointer
to a FILETIME structure.
4) QueryPerformanceCounter retrieves the current value of
the high-resolution performance counter, if one exists.
You must pass a pointer to a LARGE_INTEGER that the
function sets, in counts, to the current performance-counter
value. If the installed hardware doesnt support a highresolution performance counter, this parameter can be
zero. If the installed hardware supports a high-resolution
performance counter, the return value is nonzero.
5) GetThreadTimes obtains timing information about
a specified thread. You must pass the handle to the
thread, and four pointers to FILETIME structures to
receive: the creation time of the thread, the exit time of
Function

Win9x

WinNT/2000/XP

WinCE

GetTickCount

Yes

Yes

Yes

TimeGetTime

Yes

Yes

No

GetSystemTime

Yes

Yes

Yes

GetSystemTimeAsFileTime

Yes

Yes

No

QueryPerformanceCounter

Yes
(hardware)

Yes
(hardware)

No

GetThreadTimes

No

Yes

No

GetProcessTimes

No

Yes

No

Figure 1: Availability of Win32 timing functions.

The

API

Calls

Perfect Timing

procedure TForm1.btnGetTickCountClick(Sender: TObject);


var
i: Integer;
start, stop, delta: DWORD;
begin
Screen.Cursor := crHourGlass;
delta := 0;
start := GetTickCount;
Sleep(spInt.Value);
stop := GetTickCount;
Edit2.Text := IntToStr(stop - start);
if ckbxRes.Checked then
for i := 0 to 99 do begin
start := GetTickCount;
stop := GetTickCount;
while stop = start do
stop := GetTickCount;
if delta <> 0 then
delta := Min(stop - start, delta)
else
delta := stop - start;
end;
Edit1.Text := IntToStr(delta);
Screen.Cursor := crDefault;
end;

Figure 2: Using a Win32 API timing function, GetTickCount in this case.

the thread, the amount of time the thread has executed


in kernel mode, and time executed in user mode. If the
function succeeds, the return value is nonzero.
6) GetProcessTimes obtains timing information about a
specified process. You must pass the process handle whose
timing information is sought and four pointers to FILETIME
structures to receive: the creation time of the process,
the exit time of the process, and the amount of time the
process has executed in kernel mode and in user mode. If
the function succeeds, the return value is nonzero.
7) TimeGetTime retrieves the system time, in
milliseconds. The system time is the time elapsed
since Windows was started. The value returned by the
TimeGetTime function is a DWORD value.
Figure 1 shows the availability of these timing functions in
different flavors of Windows. Figure 2 shows part of a simple
example on how to use one.
A demonstration project that accompanies this article
(TimeFuncs, shown in Figure 3) shows how to call these
timing functions using Delphi, and how to handle and
interpret the results from each. Select the check box if you
want to calculate the resolution of each function, as well
as the time taken to execute a dummy task in our case
a simple call to Sleep. This demonstration application is
available for download; see end of article for details.
The demo app shown in Figure 3 does not include
GetSystemTimeAsFileTime for the simple reason that
it doesnt really implement a new timing function. Its
just a sequence of two other APIs wrapped into one.
Calling GetSystemTimeAsFileTime is equivalent to calling
GetSystemTime followed by SystemTimeToFileTime.
As you would expect, resolution and overhead will depend
on the hardware and operating system used. The values
presented here were obtained with my laptop (a Pentium IV
20

DELPHI INFORMANT MAGAZINE | March 2004

Figure 3: The demonstration application, TimeFuncs, at run time.

1700 MHz), running Windows XP Professional. Dont worry


too much if the values you get are slightly different than
the ones shown in this article.
As a rule of thumb the most important factors on the final
resolution of a timing function are:
Other activities. The most important variable factor is the
amount of other activity on the PC in question.
Priority setting. Each process/thread has a particular
priority which can be set by the programmer, with levels
from Below Normal to Time Critical (for Win95/98/NT).
Reentrancy of DLL calls. For example, setting a DLL
call to non-reentrant will stop multiple applications
from using the task/function youre trying to measure;
it makes sure that the DLL code cannot be called while
already called from your testing code.
Differences in platform. The OS scheduler for the various
flavors of Windows behaves differently.
Hardware. Different chip sets will have different
minimum resolution.
Before we start analyzing the results obtained with our test
application, lets look into the pros and cons of some of the
functions tested.
GetTickCount. The main advantages of this function is
that its available in all platforms and has a very low cost.
The disadvantages are, as you would expect, the very low
resolution (an average of 10 milliseconds), and the fact
that it returns a 32-bit result that will wrap every 49.71
days. Its theoretical resolution depends on the system
timer, which can be obtained calling the API function,
GetSystemTimeAdjustment.
QueryPerformanceCounter accesses the CPUs highperformance counter, which changes its tick value
much more frequently than the Windows system timer.
This allows us to resolve differences on the order of
microseconds (10^-6), rather than milliseconds (10^-3). As
I mentioned earlier, the actual resolution will depend on
other factors (see bullet list above). The main advantages

The

API

Calls

Function

Perfect Timing

Documented

Win98

Win2000

WinXP Pro

GetTickCount

1000

1000

15000 (or 15 ms)

15000

TimeGetTime

1000

1000

1000

15000

0.1 (or 100ns)

50000 (or 50 ms)

15625

15625

hardware

GetSystemTime
QueryPerformanceCounter
GetThreadTimes

0.1 (or 100ns)

15625

15625

GetProcessTimes

0.1 (or 100ns)

15625

15625

Figure 4: Resolution in microseconds (s) of Win32 timing functions.

Function

Win98

Win2000\XP

WinXP Pro

GetTickCount

100

100

100

TimeGetTime

6881

1264

397

GetSystemTime

79151

145

53

QueryPerformanceCounter

4939

12873

8839

GetThreadTimes

8877

2992

GetProcessTimes

8836

2964

Figure 5: Call cost for Win32 timing functions (as percentage of GetTickCount).

of QueryPerformanceCounter are its high-precision, and


that the result is 64-bits long (so its wrap period is over
100 years). On the down side, its particularly sensitive to
other the activity of other processes.
TimeGetTime presents resolutions varying from 1 ms
(in Windows 98) up to 15 ms (on XP). The documented
resolution is 1 millisecond. TimeGetTime is very similar
in use to GetTickCount and its resolution and call costs
are slightly better. The down side of TimeGetTime is its
location, which will force Delphi developers to include
MMSystem in the list of used files.
A comparative table of the results obtained for all the timing
functions tested can be seen in Figure 4.
Interpreting the Results
The result we got for the resolution in the TimeFuncs
demo project were obtained by running an inner loop that
would constantly pool the result for the particular timing
function being tested until its value changed. The delta
would be stored. We would then repeat this for a few times
(1,000,000 times for example) and the smaller delta would
be considered the smallest meaningful time increment we
can get for that particular function. Usually this minimum
is very solid and occurs over 90% of the time.
There are other ways to evaluate timing functions, but
because of the different results we get from different
OSes, hardware comparisons are usually not meaningful
unless you define the results as a percentage of one of
the functions, GetTickCount for example. The results
for the call cost presented in Figure 5 were extracted
from an article on the same subject written by Matthew
Wilson. The results clearly show that GetTickCount
has the lowest cost on all tested platforms, and that
QueryPerformanceCounter has one of the highest (due to
its many internal calls to I/O functions).
21

DELPHI INFORMANT MAGAZINE | March 2004

Some Unexpected
Results
The result returned by the
QueryPerformanceCounter
function may unexpectedly
leap forward from time to
time. This leap may represent
several seconds.

This problem occurs as a


result of a design defect in
the peripheral component
interconnect (PCI) to the Industry Standard Architecture (ISA)
bridge of some chipsets. This bridge is commonly referred
to as the South Bridge. The jump occurs under a heavy
PCI bus load, when the operating system receives a series
of unexpected results from the bridge. The operating system
detects the unexpected results and computes an amount to add
to the performance counter. This causes the returned result
from QueryPerformanceCounter to jump forward.
You should watch for an unexpected jump by comparing
the change in time as determined by successive calls to
QueryPerformanceCounter, with the change in time as
determined by successive calls to the GetTickCount function. If
theres a significant jump based on QueryPerformanceCounter,
but no similar increase based on GetTickCount, then it can be
assumed that the performance counter jumped forward.
The code in Listing One (on page 23) demonstrates how
to make this comparison. If the code is run on a computer
with the PIIX4 chipset, for example, this console application
will sporadically report leaps in the performance counter
value. (This application is also available for download.)
To get more details on this problem check Microsofts
Knowledge Base Article #274323. They will also have a list
of the chips that are known to exhibit this problem.
A Performance Test Framework
Is it quicker to divide by two, or bit shift left one place? Should
I use an array, or dereference a pointer? How much faster will
this code run with certain combinations of optimization flags?
Most of you know many of these little tips and techniques and
keep them tucked away in your bag of tricks. Knowing where
to optimize your application is just as important as knowing
what to do when you find the bottleneck.
In this section, I will quickly introduce a very simple testing
framework you can use with all your projects to try to spot a
possible bottleneck in your application. It will use some of the
timing functions we studied above. We will also try to account
for overhead the test itself might introduce.
Listing Two (beginning on page 23) shows the code for our
framework. As you can see we only exposed three routines
to users of the unit, one of them is the Profile procedure. This
procedure takes only one parameter of type TProcedure.
On this unit we used a bit of inline assembly to get to
the hardware precision counter, something similar to
what QueryPerformanceCounter does, but with a bit
less overheads. The meat of the code are the internal
procedures StartTime and StopTime, which use the

The

API

Calls

Perfect Timing

Figure 6: Framework demo application in action.

assembly
opcode RDTSC
(Read Time
Stamp Counter)
or $0310F in
hexadecimal.
Another point
worth mentioning
is that we modify
the applications
thread priority
just before we
start profiling it,
and then reset
it to its original
value after were
finished.

We can consider Profile.pas as a simple sanity check


profiling tool (see Figure 6). With this unit you can quickly
test your code for performance bottlenecks. If any issues
appear, you can then analyze the code in more detail.
As you can see, you can select the task you want to time
at run time. The Task variable will point to the procedure
you want to time. You can easily modify the code to
take a function or method. Other ideas are to send the
results to a log file, and place the calls to StartProfile and
StopProfile inside conditional defines. The code shown in
Figure 7 is an example on how to do this.
The profiling code will only be linked into your application
if the DEBUG conditional has been defined. LogTask should
output the results of the test to a text file. Once youve
finished profiling your application, remove the DEBUG
conditional. Some commercial profilers will use similar
techniques, with the difference that they will automatically
scan your code, adding and removing the necessary code.
Conclusion
In this article we looked into some of the available Windows
API timing functions, for simple time keeping or for
performance tracking measurements. We also exposed some
of the less obvious characteristics of these functions, such as
resolution, overhead, and range.
In a general way we saw that TimeGetTime may carry
with it an uncertainty in its result ranging from 10 to 20
milliseconds depending on the OS and hardware youre
using. Usually NT-based systems, such as Windows 2000
and XP, will show a higher uncertainty when compared to
Windows 98, for example.
We saw how to improve the granularity of Sleep on NTbased systems, using the pair of functions timeBeginPeriod
and timeEndPeriod. We also learned how to detect a
possible hardware problem that will affect the results
of QueryPerformanceCounter. Finally, we introduced a
simple framework that you may use to reliably time any
individual task in your application. If you want to learn
more on this subject, the links below lead to a wealth of
information on timing and performance issues.
22

DELPHI INFORMANT MAGAZINE | March 2004

procedure TForm1.btnDebugClick(Sender: TObject);


{$IFDEF DEBUG}
var
e: Extended;
{$ENDIF}
begin
{$IFDEF DEBUG}
StartProfiler;
{$ENDIF}
Task1;
{$IFDEF DEBUG}
e := StopProfiler;
LogTask('Task1', e);
{$ENDIF}
end;

Figure 7: Using conditional defines for testing while debugging.

Further Reading
MSDN Library: http://msdn.microsoft.com/library/
default.asp
Precision Timing Under Windows Operating Systems:
www.wideman-one.com/gw/tech/dataacq/wintiming.htm
The Problems Youre Having May Not Be the Problems You
Think Youre Having: www.research.microsoft.com/~mbj/
papers/tr-98-29.html
Choosing the Right Win32 Timing Functions: windows::
Developer (May 2002).
The Poormans Profiler: www.starstonesoftware.com/
OpenGL/poorman.htm
The demo apps referenced in this article are available for
download on the Delphi Informant Magazine Complete
Works CD located in INFORM\2003\MAR\DI200403FV.

Fernando Vicaria is a Senior Software Engineer currently based in


Santa Cruz, CA. He was an active member of the development team for
C++Builder, Delphi, and C#Builder at Borland. He is also a freelance
technical author for Delphi and C++Builder issues. Fernando specializes
in VCL and .NET frameworks. When hes not at work hes probably surfing
at some secret spot in Northern California. He can be reached via e-mail at
fernando@vicaria.com.

Quick Tip:
Reducing Sleeps Granularity
I have an important tip for those of you that use the Sleep
API function as a way of specifying a fixed time lapse. You can
lower its granularity to whatever value you want by calling
timeBeginPeriod and passing the value you want. For example:
procedure CallSleep;
begin
timeBeginPeriod(1);
Sleep(100);
timeEndPeriod(1);
end;

You must match each call to timeBeginPeriod with a call to


timeEndPeriod, specifying the same minimum resolution in both
calls. An application can make multiple timeBeginPeriod calls as
long as each call is matched with a call to timeEndPeriod.

Fernando Vicaria

The

API

Calls

Perfect Timing

Begin Listing One


QueryPerformanceCounter
Hardware Test

end;

// if PerfElapsed > 500

liLast := liCurrent;
end;
end.

program QueryPerfTest;

End Listing One

{$APPTYPE CONSOLE}
uses

Begin Listing Two


Profiling Generic Tasks

SysUtils, Windows;
var
liFrequency, liCurrent, liLast: LARGE_INTEGER;
liRecent: array[0..9] of LARGE_INTEGER;

unit Profiler;
interface

PerfElapsed: Int64;
i, j: Integer;

uses

begin
// Save performance counter frequency for later use.
if not QueryPerformanceFrequency(Int64(liFrequency)) then
WriteLn(Format('QPF() failed with error %d'#13,
[GetLastError]));
// Query the performance counter value and tick count.
if not QueryPerformanceCounter(Int64(liCurrent)) then
WriteLn(Format('QPC() failed with error %d'#13,
[GetLastError]));

Windows, SysUtils;
function Profile(FuncPointer: TProcedure): Extended;
procedure StartProfiler;
function StopProfiler: Extended;
implementation
type

liLast := liCurrent;

TAppPriority = record

i := 0;

Priority: Integer;

while True do begin


// Sleep statement will simulate output of a bad chip.
// Comment it out when performing the actual test.
// Sleep(Random(600));
// Query the performance counter value and tick count.
if not QueryPerformanceCounter(Int64(liCurrent)) then
WriteLn(Format('QPC() failed with error %d'#13,
[GetLastError]));
// Store performance counter value in list of
// recent values.
liRecent[i].QuadPart := liCurrent.QuadPart;
i := (i+1) div 10;
// Convert difference in performance counter values
// to milliseconds.
PerfElapsed := Round((liCurrent.QuadPart liLast.QuadPart) * 1000/liFrequency.QuadPart);
// Check for discrepancy greater than 500 milliseconds.
if PerfElapsed > 500 then
// Print the previous 9 performance-counter values.
for j:=9 downto 1 do begin
Previous %d:

%d'#13,

[j, liRecent[i].QuadPart]));
i := (i+1) div 10;

WriteLn(Format(
%d

delta = %d milliseconds'

#13, [liCurrent.QuadPart, PerfElapsed]));


// Retrieve and print next 9 performance-counter
// values.

procedure StartTime;
asm
RDTSC
mov shi, edx
mov slo, eax
end;
procedure FinishTime;
asm
RDTSC
mov flo, eax
end;
function GetTicks: Extended;
begin

// GetCPUSpeed: Return value is in MHz.


function GetCPUSpeed: Double;
const
DelayTime = 100;

// Measure time in ms.

var

for j:=1 to 9 do begin


QueryPerformanceCounter(Int64(liCurrent));
Next

%d:

%d'#13,

[j, liCurrent.QuadPart]));
liRecent[i].QuadPart := liCurrent.QuadPart;
i := (i+1) div 10;
end;
WriteLn;

23

DefaultAppPriority: TAppPriority;

end;

// Print the leap value.

WriteLn(Format('

shi, slo, fhi, flo: DWord;

Result := (fhi-shi)*$FFFFFFFF + flo - slo;

end;

'LEAP: Current:

var

mov fhi, edx

begin

WriteLn(Format('

PriorityClass: Integer;
end;

DELPHI INFORMANT MAGAZINE | March 2004

// TimerHi,
TimerLo: DWORD;
PriorityClass, Priority: Integer;
begin
PriorityClass := GetPriorityClass(GetCurrentProcess);
Priority := GetThreadPriority(GetCurrentThread);
SetPriorityClass(GetCurrentProcess,

The

API

Calls

Perfect Timing

REALTIME_PRIORITY_CLASS);
SetThreadPriority(GetCurrentThread,
THREAD_PRIORITY_TIME_CRITICAL);
// Give Windows time to set new threads priority.
Sleep(1);
asm
DW $0310F

// RDTSC

MOV TimerLo, EAX


PUSH DelayTime

// StdCall convention.

CALL Sleep
DW $0310F

// RDTSC

SUB EAX, TimerLo


MOV TimerLo, EAX
end;
SetThreadPriority(GetCurrentThread, Priority);
SetPriorityClass(GetCurrentProcess, PriorityClass);
Result := TimerLo / (1000.0 * DelayTime);
end;
procedure StartProfiler;
begin
DefaultAppPriority.Priority :=
GetThreadPriority(GetCurrentThread);
DefaultAppPriority.PriorityClass :=
GetPriorityClass(GetCurrentProcess);
SetPriorityClass(GetCurrentProcess,
REALTIME_PRIORITY_CLASS);
SetThreadPriority(GetCurrentThread,
THREAD_PRIORITY_TIME_CRITICAL);
Sleep(1);
StartTime;
end;
function StopProfiler: Extended;
var
Freq, Ticks: Extended;
begin
FinishTime;
SetThreadPriority(GetCurrentThread,
DefaultAppPriority.Priority);
SetPriorityClass(GetCurrentProcess,
DefaultAppPriority.PriorityClass);
Freq:= GetCPUSpeed;
Ticks := GetTicks;
Result := (Ticks/Freq)*1e-3;

// Milliseconds.

end;
function Profile(FuncPointer: TProcedure): Extended;
begin
StartProfiler;
FuncPointer;
Result := StopProfiler;

// Operation to be profiled...
// result is in milliseconds.

end;
end.

End Listing Two

24

DELPHI INFORMANT MAGAZINE | March 2004

SOUND+VISION
INTERNET EXPLORER INTERFACE

TWEBBROWSER

HTML

DELPHI 7

By Zoltan Kurczveil

Getting the IE Look

Putting the New Look to Work in Your Applications

his article illustrates a technique for


creating an applications opening screen
also called a welcome screen that is

user-friendly and customizable. Examples of these


can be found in the latest Borland and Microsoft
development environments (see Figure 1).

Upon startup the user is presented with an HTML document that lists hyperlinks to the most recent project files,
available actions for starting a new project, and links to
the Web. Actions initiated by clicking on these hyperlinks
or buttons are captured and handled by the application.
This article presents a small text editor application that
utilizes this approach.

handled by the HTML control (i.e. Internet Explorer)


instead of the application forms logic.
Problem Overview
A typical application opening screen lists the most-recently-used files, and displays an available set of user commands, such as new, open, and exit. The goal is
to present this view to the end user, and have the Delphi
application respond to actions that the user initiates. For
example, if the user clicks on a hyperlink that says new
the application displays a text editor that allows the user
to type the new document.
Intuitively, this consists of the following steps:
1) Defining an HTML template: This template is used for
the opening screen.
2) Processing the HTML template: Populate the HTML
template with the list of most recently used files.

A good first impression. An HTML page featuring hyperlinks and buttons to initiate tasks provides users with a
familiar look and feel as soon as they start the application. The presentation of available tasks, grouped by
category on a single screen, helps users by reducing the
amount of time spent figuring out what tasks are available, and how to get to them.
There are also several benefits for developers. Most importantly, the coupling between the presentation layer and
its implementation is greatly reduced. The creation of the
opening screens user interface can be accomplished by a
Web developer, while the implementation of the application
logic remains the application developers responsibility.
This means that the opening screen can be customized
for different users without having to touch application
code. As a bonus, the opening screens resize events are
25

DELPHI INFORMANT MAGAZINE | March 2004

Figure 1: Borland C#Builder sports a good example of the new IE-based interface.

Sound

Vision

Getting the IE Look

3) Displaying the HTML document: Load the resulting


HTML document from memory into an instance of
TWebBrowser (located on the Internet tab of the
Delphi IDEs Component palette).
4) Interacting: Respond to events initiated from the
TWebBrowser instance.
The Big Idea
One issue that comes to mind immediately: How can
the Delphi application respond to user commands in
TWebBrowser? In other words, how is clicking on a button or
hyperlink propagated as an event to the Delphi application?

procedure TMainForm.FormCreate(Sender: TObject);


begin
{ Set the template HTML file. }
PageProducer1.HTMLFile := ExtractFilePath(
Application.ExeName) + FILENAME_STARTPAGE;
{ Load the most-recently-used files. }
FRecentFiles :=
TRecentFilesList.Create(RecentFilesFilename);
{ Set the form's display mode to the opening screen. }
FormView := fvOpening;
end;

Figure 2: The FormCreate event handler.

TWebBrowser encapsulates Internet Explorer (IE). The answer


to this question is in the BeforeNavigate2 event of TWebBrowser.
This event is of type TWebBrowserBeforeNavigate2 which is
declared in ShDocVw.pas, as follows:

In this manner, the <#RECENTFILES> tag is inserted


where the most-recent files will be rendered. This tag is
substituted at run time with the commands to open each
of the most-recent files.

TWebBrowserBeforeNavigate2 = procedure(Sender: TObject;


const pDisp: IDispatch; var URL: OleVariant;
var Flags: OleVariant; var TargetFrameName: OleVariant;
var PostData: OleVariant; var Headers: OleVariant;
var Cancel: WordBool) of object;

The template also defines hyperlinks for several commands; the Create a new file command looks like this
in the HTML template:
<a href="new:">Create a new file</a>

The parameters of interest are URL and Cancel. The URL


parameter is a string representing the target URL, i.e.
where to navigate to. This parameter is used to pass data
from the HTML page to the application, allowing the
application to respond to events.
The application code can decode a
command and whatever parameters
are needed for executing that
command from the URL parameter.
The following standard is used for
encoding this information:

<img src="HelloWorld.jpg."/>

An HTML page
featuring hyperlinks and
buttons to initiate tasks
provides users with a
familiar look and feel.

Command:Parameter

This standard was arbitrarily selected; you can define


your own convention to make this technique suitable for
your needs.
The Cancel parameter tells IE whether to navigate to the
URL. If Cancel is True, IE stops and the application can
handle the navigation. Otherwise, IE attempts to navigate
to the URL. Now lets walk through the sample code.
Defining the HTML Template
The HTML template is located in the same directory as
the applications executable. An example of a template
can be found in the startpage.htm file in the source
code that accompanies this article (see end of article for
download details). A template is necessary because the
application needs to render custom items (such as the list
of most-recently-used files) into it at run time.
Delphi offers the TPageProducer component for turning
templates into HTML documents, so our template must
comply with the way TPageProducer works. Wherever the
application has to insert data into the HTML document, a
custom tag is defined in the format <#CustomTagName Parameter>,
and Parameter is optional.
26

Custom tags are also used for images. Images are displayed in IE using the standard HTML tag:

DELPHI INFORMANT MAGAZINE | March 2004

When an HTML file that uses this tag


is viewed, IE searches for the image
in the same directory where the
HTML file is located. If the image is
there, it gets displayed. Our situation
differs from this scenario in that the
HTML document isnt loaded directly
from disk. The HTML template is
processed in memory; hence, the HTML document is loaded
from a memory stream into IE. This supports only full image
paths, and it does not support referencing relative paths.

Therefore, all images are stored in the applications


directory along with the HTML template file, and are
referenced in the template in the following way:
< #IMAGE "ImageFilename">.

This tag is processed at run time and is substituted with


the correct <img...> HTML construct by including the
full path to the images:
<img src="ApplicationDirectory + ImageFilename">.

Processing the HTML Template


The PageProducer component is assigned an HTML template by setting its HTMLFile property to the file name of
the HTML template. This is done in the FormCreate event
handler, shown in Figure 2.
When PageProducers Content property is read, the
PageProducer component parses the HTML template. Each
time a tag is encountered, it conveniently calls the page

Sound

Vision

Getting the IE Look

procedure TMainForm.PageProducer1HTMLTag(Sender: TObject;


Tag: TTag; const TagString: string; TagParams: TStrings;
var ReplaceText: string);
{ Gets called when the PageProducer's Content property is
read and the HTML parser encounters a custom tag in the
HTML template. }
var

procedure TMainForm.DisplayHTML(const HTMLSource: string);


{ Load the HTML in HTMLSource into Internet Explorer so
it's displayed. This is an "in-memory" operation using
IE's interfaces. }
var
m : TMemoryStream;
ipStream : IPersistStreamInit;

i : Integer;
begin

AStream : IStream;
begin

{ Process the Recently used files by rendering hyperlinks


in the HTML page. }
if AnsiCompareText(TagString, TAG_RECENTFILE) = 0 then
begin

if WebBrowser1.Document = nil then


{ This is the standard way to create an IE document. }
WebBrowser1.Navigate('about:blank');
m := nil;

ReplaceText := '';

AStream := nil;

for i := (FRecentFiles.Count-1) downto 0 do

try

{ Recently used filenames are loaded into an


HTML table. }
ReplaceText := ReplaceText + Format(

{ Create a stream to contain the HTML to be shown. }


m := TMemoryStream.Create;
{ Create Stream Adapter for the memory stream. }

HTML_TABLEENTRY, [FRecentFiles.Items[i],

AStream := TStreamAdapter.Create(m);

ExtractFilename(FRecentFiles.Items[i])]);

{ Save HTMLSource to a stream. }

end

m.Write(Pointer(HTMLSource)^, length(HTMLSource));

else if AnsiCompareText(TagString, TAG_IMAGE) = 0 then


{ Process Images by rendering the image reference using
explict links to the images on disk. }

{ Go to the beginning of the stream so IE can load. }


m.seek(0, 0);
{ Convert the document to an IPersistInit, which

begin

implements the load method. }

{ Internal Check to ensure the Template is correct. }

ipStream := WebBrowser1.Document as IPersistStreamInit;

Assert(TagParams.Count > 0);

{ Load the document through the stream adapter's

{ Formulate the HTML reference. }


ReplaceText := Format(HTML_IMAGEREF,
[ExtractFilePath(Application.ExeName) +
TagParams.Strings[0]]);
end;
{ Any additional tages to be processed go here in an
if-else clause. }
end;

IStream implementation. }
ipStream.load(AStream);
finally
m.Free;
ipStream := nil;
AStream := nil;
end;
end;

Figure 3: The PageProducer1HTMLTag method.

Figure 4: The DisplayHTML method.

producers HTMLTagEvent event, allowing the application


to substitute the tags with something useful. This process
is the translation of the HTML template into the real HTML
document. The HTMLTagEvent is of type THTMLTagEvent,
defined in HTTPProd.pas:

<a href="openfile:C:\ARecentFile.txt">ARecentFile.txt</a>

THTMLTagEvent = procedure(Sender: TObject; Tag: TTag;


const TagString: string; TagParams: TStrings;
var ReplaceText: string) of object;

The implementation of the event handler, the


PageProducer1HTMLTag method, is shown in Figure 3. The
application handles two different tags: recent files and images.
TagString is the name of the tag. For example, if the
HTML file defines a <#RECENTFILES> tag, the tag
parameter would be set to the string RECENTFILES
when the event is called. ReplaceText is the string that
will be rendered into the HTML document in the place
of the actual tag. This parameter is the primary output
of the event handler.
When the RECENTFILES tag is encountered, the
application loops through the most-recently-used files
and renders an HTML table entry for each file. The table
entry is a hyperlink to a URL that follows the Command:
Parameter convention defined above. Heres an example:
27

DELPHI INFORMANT MAGAZINE | March 2004

The result is that the RECENTFILES tag is substituted with a


set of hyperlinks to each of the most recently used files.
The other tag that is processed in this routine is the
<#IMAGE>, for reasons explained in the previous section.
Displaying the Processed HTML
PageProducers Content property is of type string. This means
its in memory and not a file on the hard disk, so how can
the string be loaded into the Web browser component?
The mechanism is implemented in the DisplayHTML
method, listed in Figure 4. The method takes a string
containing an HTML document, and displays it in the
Web browser component. TWebBrowser encapsulates
the IE ActiveX control. It has a Document property of
type IDispatch that grants access to the underlying
COM structure.
The first trick is that this document property is initially
set to nil. The way to initialize it is to navigate to an
empty page with a URL of about:blank. This is accomplished with the following statement:
WebBrowser1.Navigate('about:blank');

Sound

Vision

Getting the IE Look

procedure TMainForm.WebBrowser1BeforeNavigate2(
Sender: TObject; const pDisp: IDispatch;
var URL, Flags, TargetFrameName, PostData,
Headers: OleVariant; var Cancel: WordBool);
var
Command, Parameter : string;
ColonPos : Integer;
begin
ColonPos := Pos(':', URL);
if ColonPos > 0 then
begin
{ Set Cancel to True in case the command is processed
by the application. }
Cancel := True;
{ Extract a command and a parameter -- only one
parameter for now. }
Command := Copy(URL, 1, ColonPos-1);
Parameter := Copy(URL, ColonPos + 1,
length(URL) - ColonPos);
{ Determine whether command is something the
application handles. }
if AnsiCompareText(COMMAND_NEW, Command) = 0 then
actnNew.Execute;
else if AnsiCompareText(COMMAND_OPEN, Command) = 0 then
FileOpen1.Execute;
else if AnsiCompareText(
COMMAND_OPENFILE, Command) = 0 then
PerformFileOpen(URLToString(Parameter));
else if AnsiCompareText(
COMMAND_SHOWMESSAGE, Command) = 0 then
MessageDlg(URLToString(Parameter), mtInformation,
[mbOK], 0);
else if AnsiCompareText(COMMAND_EXIT, Command) = 0 then
Close;
else
{ If the command isn't something we can handle;
let IE proceed. }
Cancel := False;
end;
end;

Figure 5: The WebBrowser1BeforeNavigate2 event handler.

The next trick is to recognize that the Document property


also implements the IPersistStreamInit interface, which
defines the following function:

Command

URL

Create a new text file

new:

Open a recent file called: C:\Text.txt

openfile:C:\Text.txt

Open a file from a file dialog

open:

Exit the application

exit:

Figure 6: Comparing the command parameter to available commands.

Figure 7: The sample application at run time.

respond to them. The sample code is listed in the


WebBrowser1BeforeNavigate2 event handler (see Figure 5).
The BeforeNavigate2 event is handled by the application
where the URL is decomposed into a command and a
parameter. The first step is to tell IE that the application
will handle the navigation. This is done by setting the
Cancel parameter to True.
Command is then compared to available commands that
the application supports (see Figure 6). For each command
we invoke the appropriate method or action. For example, if
the command is new, then invoke the new action. If the
command is openfile, then open the file whose file name is
stored in the Parameter variable.

function Load(const stm: IStream): HResult; stdcall;

If the HTML document is loaded into an object that


implements the IStream interface, IE can display it
by calling the Load method of the IPersistStreamInit
interface.
This is where TStreamAdapter comes to the rescue. Any
instance of TStream class or its descendants can be
converted to an IStream interface by instantiating the
TStreamAdapter class with the TStream instance as a
parameter in the constructor. The HTML source string is
then loaded into a stream. The streams position is set to
the beginning of the stream. Finally, the Load method is
called to load the HTML from the stream.
Responding to Events
At this point, an HTML template is read, processed, and
displayed. The only thing left to do is capture navigation
events from the browser and have the Delphi application
28

DELPHI INFORMANT MAGAZINE | March 2004

There is an issue to note about the URLToString function


call. And that is that URLs represent spaces in hexadecimal
notation. IE navigates to URLs. Whenever a string is passed
from IE, if the original string contains a space, it will now
contain the characters %20 instead of the space. For example:
The filename C:\My Documents\Hello World.txt is represented
by the URL: C:\My%20Documents\Hello%20World.txt.
However, the LoadFromFile method for the RichEdit control
doesnt accept hex characters. Therefore, URLs must be
converted to proper Windows file names, by converting the
hexadecimal numbers to the proper character. This is what the
URLToString function does; for example, %20 is converted to 32,
which is converted to the space character.
Other Than Hyperlinks...
How can we obtain inputs not just from hyperlinks, but
also from other controls, such as edit boxes? At this point,
this is a scripting exercise, which consists of writing a

Sound

Vision

Getting the IE Look

script to construct a URL in the appropriate format and


navigate to it. The demonstration program that accompanies this article (shown in Figure 7) should help. Its
available for download; see end of article for details.
The sample source HTML template (again, available for
download) defines a function in JavaScript that takes a
string from the edit control in the HTML page and passes
it to the application for processing. Currently, the sample
application simply shows a message that displays the string.
Here again, the URLToString function is used to convert the
hexadecimal space representation to a space character.
Conclusion
This article demonstrates how applications can be
integrated with Internet Explorer to provide a more
pleasant user experience. The opening page can also utilize
more advanced HTML display and formatting techniques
such as style sheets. It is also possible to present various
controls to make navigation easier and more interactive,
such as allowing users to visit a particular Web site.
Finally, the technique is not only applicable to opening
screens, but also to interactive dialog windows.
I would like to thank Lee Payne for preparing the artwork
used in the HTML sample template.
The demo application referenced in this article is available
for download on the Delphi Informant Magazine Complete
Works CD located in INFORM\2004\MAR\DI200403ZK.

Zoltan Kurczveil is the director of engineering at Armus Corporation,


a company specializing in clinical outcomes application development.
He obtained his bachelors degree from UC Berkeley in physics and
computer science. Currently, he is finishing his masters degree in software
engineering at Santa Clara University. He also holds a private pilots
license. You can write to Zoltan at Zoltan@armus.com.

29

DELPHI INFORMANT MAGAZINE | March 2004

UNDOCUMENTED
REGISTRY HACKS

DELPHI 8

By Corbin Dunn

Delphi 8 Registry Hacks


Shortening IDE Load Time and Revealing Secrets

ow in its 8th version, Delphi has become a


large product with a lot of features. In fact,
you may never use some of them. The trouble

is, the larger the program, the longer it takes to load.


This means that all those features you dont use still
cost you time whenever you start the Delphi IDE.

On the other hand, some features that you might find handy
are hidden in the shipping version of Delphi 8, accessible only
via a registry modification. Fortunately, with a few registry
hacks, you can expose some new features, and disable others
that they dont use, to customize the IDE and make it snappier.
Caveat registor! First, modifying the Windows registry is
dangerous, and can potentially cause problems on your
system. I recommend modifying the registry only if you
really understand what youre doing.
Having said that, you should understand how Delphi 8
uses the registry. To start the Windows registry editor, run
RegEdit.exe.
The Delphi 8 IDE normally reads all its registry settings
from HKEY_CURRENT_USER\Software\Borland\BDS\2.0.
Previous versions of Delphi used HKEY_CURRENT_USER\
Software\Borland\Delphi\
<Version>. C#Builder and
Delphi 8 share the same
IDE, so the executable
was renamed to BDS.exe,
standing for Borland Developer Studio. When the IDE
starts, if the given registry
key doesnt exist under
HKEY_CURRENT_USER
(or HKCU for short), it
will copy the contents of
HKEY_LOCAL_MACHINE
(HKLM) to the HKCU key.
This allows the IDE to be
Figure 1: Modifying a shortcut to
BDS.exe with the -r option.
used with multiple users.
30

DELPHI INFORMANT MAGAZINE | March 2004

Figure 2: Registry editor displaying the BareBones registry key.

The IDE has a little-known option that allows it to start with a


different registry key. By using the command line option
-rSomeString, where SomeString is any given registry key, the
IDE will start using SomeString as its registry. For example, I
have a shortcut to BDS.exe on my desktop with a Target of:
"C:\Program Files\Borland\BDS\2.0\Bin\bds.exe" -rBareBones.

When the IDE starts, it reads and writes to the registry key
HKCU\Sofware\Borland\BareBones\2.0. The first time you
start the IDE this way, it will copy its settings over from HKLM.
What is the advantage of this -r registry switch? It allows
you to use the same IDE in different ways. For example,
by loading different packages with different registry keys,
you can have one IDE tailored for text editing, and another tailored for all available features.
Now that you know where to find the registry keys, its time to
look at how the IDE uses them. The IDE itself is very modular.
Virtually all product features are simply plugged into the IDE
via packages or assemblies. Native packages are loaded from
the Known IDE Packages key. .NET assemblies are loaded
from the Known IDE Assemblies key. Installed VCL .NET
components are loaded from the Known Assemblies key.
You can speed up the start time of the IDE by disabling items
that you dont use. Start the IDE with the -rBareBones option
to leave the current settings untouched. The easiest way to do

Undocumented

Delphi 8 Registry Hacks

Registry Entry

Package Description

$(BDS)\Bin\delphidotnetide71.bpl

Core Delphi for .NET IDE


Personality

$(BDS)\Bin\dotnetcoreide71.bpl

Core .NET IDE Personality

$(BDS)\Bin\dotnetdebugide71.bpl

.NET Debugging Features

$(BDS)\Bin\idefilefilters71.bpl

IDE File Filters - (Required)

$(BDS)\Bin\vcldotnetdesignide71.bpl

VCL for .NET Personality


Features

Figure 3: For a bare bones version of Delphi, set all values but these to a
blank string.

this is to create a shortcut


to BDS.exe and modify the
target to contain -rBareBones
(see Figure 1). Start the IDE
once (so it copies the keys
over), and exit. In the registry
editor, you should see something similar to Figure 2.

Figure 4: Palette Wizards in the Tool


Palette.

Select the Known IDE


Packages key and delete it.
Repeat with Known IDE
Assemblies and Known
Assemblies. Start the IDE
again with the shortcut you
created earlier. It should start
very quickly. What you now
have is a familiar and very
fast text editor, with color
syntax highlighting.

You probably still want to use certain features of Delphi,


such as the ability to compile applications, and do basic form
design. To do this, you must disable individual packages.
Start the IDE again with a different -r option, such as
-rBareDelphi, and exit. Refresh the registry editor and select
the Known IDE Packages key under HKCU\Software\
Borland\BareDelphi\2.0. You should again see something similar to Figure 2. Each item in the list is a particular feature for
the IDE. For example, "$(BDS)\Bin\delphidotnetide71.bpl" is
the core Delphi for .NET IDE personality. Disabling this package would disable the ability to compile Delphi applications.
Some items in the list have a description telling you their
purpose. Others simply say (Untitled). To determine a
packages purpose, browse to it with Windows Explorer,
right-click on it, and select Properties. On the Version tab,
look at the Description to see what the package does. For
example, the package asmview71.bpl has a Description of
Assembly Viewer. This package allows the IDE to open/
browse assemblies inside it.
Now the trick is to learn how to disable packages. Its actually
quite simple: Make the string value empty and the package
wont load; make the string value not empty and it will load.
So to make a bare bones version of Delphi, set all values to
have a blank string except those shown in the table in Figure 3.
In addition, if you want professional Delphi features, dont disable the packages with the word pro in them.
31

DELPHI INFORMANT MAGAZINE | March 2004

Figure 5: Error Insight in action.

Now when you start the IDE it should load a lot faster, but
youll still have the ability to compile Delphi for .NET projects.
I also mentioned the Known IDE Assemblies registry
key. This key is the .NET counterpart to Known IDE
Packages. You can selectively disable assemblies in this
list the same way as Known IDE Packages. I dont recommend disabling $(BDS)\Bin\Borland.Studio.Delphi.dll,
as it adds core Delphi features to the product.
The last key to look at is Known Assemblies. Items in
this list register components and/or component designers.
You can disable them by setting their string values to a
blank string, if you dont use a certain set of components.
For example, you can remove all items in the list, if you
dont do VCL for .NET development.
Now for some cool hidden features in Delphi 8. You
should have a registry key named HKCU\Software\
Borland\BDS\2.0\Globals. If you add a new String Value
named PaletteNewItems containing a value of 1, the IDE
will have the Palette Wizards feature in the Tool Palette, as
shown in Figure 4. Palette Wizards are a quick and easy
way to create any item found in the New Items Gallery.
Another really cool hidden feature is Error Insight (see
Figure 5). In the Globals key, add another String Value named
ShowMeProblemsCorbin containing a value of 1. When you type
in the code editor, it will constantly update to show you small
red squiggles when there is a syntax error. Holding the mouse
over the squiggle will show why the error occurred.
If for some reason Error Insight doesnt show up, you
may have to register the ToolsAPI type library. Do this at
the command line, thus:
C:\Program Files\Borland\BDS\2.0\Bin>
tregsvr -t -s Borland.Studio.ToolsAPI.tlb

Now you know how to create a highly customized version of


the Delphi 8 IDE. I commonly find myself disabling features
that I dont often use. Its easiest to create multiple shortcuts to
BDS.exe, each containing a different -r option to start the IDE
with or without certain features. Its a handy way to get the
most flexibility, with one of the greatest products.
Corbin Dunn has been working for Borland Software Corporation for
the past five years. He currently works on the IDE as a Research and
Development Software Engineer. In his spare time, he can be found riding
a bicycle down a steep mountainside at very fast speeds. Corbin can be
reached via e-mail at cdunn@borland.com.

N E W

&

U S E D

By Mike Riley

eDocEngine and PDFtoolkit


COM-based Component Development Is Not Dead Yet

ven as Borlands Win32-based Delphi enters


its twilight era, with the release of Delphi 8 for
the Microsoft .NET Framework, its encourag-

ing to see that commercial component development


for the older COM-based platform is still viable in the
marketplace. Case in point: Gnostices eDocEngine
and PDFtoolkit VCL components. These two powerful
packages are lifesavers for developers seeking a painless path to integrating multiple file type output from
their Delphi-driven applications.
eDocEngine
eDocEngine is a relatively comprehensive set of 20 components capable of generating most of todays popular document file types (see Figure 1). These include CSV, HTML,
Lotus 1-2-3, PDF, Quattro Pro, RTF, TXT, XHTML, and XLS
document types and DIF, BMP, EMF, GIF, JPEG, PNG, SYLK,
SVG, TIFF, and WMF image formats. Output can be directed
to client browsers, e-mail attachments, or local disk stores,
with BMP, DIF, SYLK, and TXT formats also capable of
being copied to the Windows Clipboard.
Report writers will be pleased to know that eDocEngine
can also export a number of report formats, such as the
popular QuickReport, ReportBuilder, and FastReport builders, among others. So, rather than having to manually construct the entire document template in code, eDocEngine
components can be attached to a DataModule and simply
flow the output into the control for automatic formatting
and document file type creation. Its a tremendous time
saver for those developers seeking an alternative solution
to the typical printer port output option.
The latest version evaluated for this review (1.03) heaps on
even more support for PDF eForm manipulation, text, and
graphic overlays on top of generated page content, cell color,

32

DELPHI INFORMANT MAGAZINE | March 2004

and border style attributes for Excel documents and export


support for the Developer Express PrintingSystem, RichView,
and HTMLViewer components.
Installation was simple, with the usual compiled component
question of which build version of the product (Delphi 5,
6, or 7, and CBuilder 5 or 6) to install on the target system.
The installer worked and registered everything within my
Delphi 6 environment, but had some trouble on my Delphi
7 installation particularly with the Library path registration. I tried it on a separate Delphi 7 system and it installed
just fine. The products README file reminds users how
to manually configure components, so the odd registration
consequence was of minor importance.
Once installed, each component is accessible from
the eDocEngine toolbar palette tab, and is ready to be
dropped on a form (see Figure 2). The accessible properties and methods in each component type are intuitive
and easy to use, even without reviewing the online help
or Document Creation sample project.
However, developers wont fully begin to comprehend the
toolkits power and flexibility until they spend some time
with the demo project. Although it takes a few minutes to

Pros & Cons


Pros
eDocEngine provides document output options that
cover most major document file type generation.
A standard, easy to use coding model for all components
means a mild learning curve.
PDFtoolkit provides extremely powerful flexibility in the
manipulation of PDF files.
Cons
Cannot separately purchase specific file output components.
Online help not integrated into Delphi IDE.

New

&

Used

eDocEngine and PDFtoolkit

acclimate to the array of properties that


can be set, learning one components
parameters translates to most of the other
components properties since each has
the same organization and style. This not
only helps flatten the learning curve, it
also allows other components/document
types to be added to an application with
a minimum (if any) of recoding.

Figure 1: eDocEngine adds a number of new controls to the Delphi tools palette.

Online help is also installed and generally well organized and written,
although, at least on the installation
systems I tested, it doesnt appear to be
context-sensitive. This isnt a big deal
since the properties and methods are,
for the most part, self-evident.
After the thrill of a few Hello, << fill in
file output type here >> examples have
subsided, developers will need to spend
some time with the products components to explore the more sophisticated
functions that eDocEngine has to offer.
These include the ability to set document headers and footers, bookmarks,
and forms support for PDF files, tables
of content auto-generation for HTML
files, and the very helpful setup dialog
boxes that can be instantiated from the
program. These built-in run-time dialog
boxes are tailored for each type of file
output, and can be further customized by
the developer if necessary. Throughout
the exploration phase, I was surprised at
just how quickly these components rendered the output files.

Figure 2: Both the eDocEngine and PDFtoolkit installations provide numerous code and project samples.

One criticism that can be leveled


against eDocToolkit is the very nature
of its bevy of supported file output formats. For developers seeking a simple,
la carte SVG generator component,
eDocTookits full course library may be
an expensive answer. Also, the fact that
these components merely generate rather than consume and reinterpret documents into an alternatively supported
format is unfortunate for those seeking
a PDF to HTML converter.
However, if developers are seeking a
component that will allow them to
manipulate virtually all key facets of
a PDF document, Gnostice offers a
separate, powerful PDF programming
library called PDFtoolkit.

Figure 3: PDFtoolkit supports many advanced PDF features, such as watermarking.

PDFtoolkit
Although the gtPDFEngine component included in the
eDocEngine suite is acceptable for most PDF rendering needs,
Gnostices PDFtoolkit provides tremendous control on nearly
33

DELPHI INFORMANT MAGAZINE | March 2004

every aspect of PDF file manipulation. In addition to the creation abilities found in gtPDFEngine, PDFtoolkit extends this
functionality with the ability to read PDF forms, auto-generate
tables of content, password protection, appending/merging,
and watermarking PDF documents (see Figure 3).

New

&

Used

eDocEngine and PDFtoolkit

Just the Facts


Win32-based Delphi developers seeking diverse document manipulation library and/or PDF creation and editing components need to look no further than Gnostices
eDocEngine and PDFtoolkit products. Few other commercial Delphi components on the market come close to the
breadth of document support and range of features that
these products provide.
Gnostice Information Technologies
#44/4, Floor - II, 15th Cross
4th Main Malleswaram
Bangalore 560 055
INDIA
Contact: sales@gnostice.com
Web Site: www.gnostice.com
Price: eDocEngine and PDFtoolkit bundled, US$449;
US$299 sold separately.
Two examples that illustrate the granularity of the additional
properties found in PDFtoolkit are PDFVersion, which targets
specific versions of the Adobe Acrobat Reader, and PageMode,
which sets the viewing style when a PDF file is opened by
Reader (for example, pmFullScreen displays the document with
no menu or other windows controls, which is helpful for kiosk
and lengthy document reading).
Version 1.02 (analyzed in this review) includes additional
enhancements such as Unicode support for document info and
bookmarks, improved PDF form field processing, and the ability
to save PDF documents directly over HTTP, a necessity when
transmitting modified PDFs from a Web server connection.
Virtually every need for creating and manipulating PDF files is
possible using the toolkit. Multiple PDF documents can be easily merged by simply listing the separate files to be combined

34

DELPHI INFORMANT MAGAZINE | March 2004

and executing the MergesDocs procedure. Likewise, pages can


be inserted, appended, deleted, or extracted from PDFs just as
effortlessly. If you have ever worked with Adobes ActiveX control (installed with Adobes Acrobat program), youll quickly
appreciate how little work it takes to learn and execute these
procedures. And because its a native Delphi VCL, theres no
need for Acrobat to be installed or called upon.
Other useful PDFtoolkit methods include the ability to programmatically password protect documents with 128-bit
strength encryption, and micro manage permissions down to
the printing of the document and copying of file elements.
Thumbnails, bookmarks, and watermarks can be created easily
as well, providing access to a range of PDF editing functionality
normally found in more expensive PDF editing solutions.
Forms support is also included, allowing for the creation, reading, and writing of form data. Individual form fields can be
accessed by its name or index, and controls such as radio buttons and check boxes can be set or unset. This means that PDF
form data collection can be easily automated for batch or realtime processing. Very cool stuff!
Conclusion
The folks at Gnostice are to be commended on their strong
product entries into the Delphi component marketplace. Even
though theyre arriving just as the Win32-based Delphi market
is giving way to the world of .NET, well-written traditional
32-bit Windows applications will be in the computing environment for years to come. And as the Delphi developer community migrates to the .NET edition of Delphi, Gnostice intends to
evolve their product onto that platform as well. I look forward
to seeing these two products evolve and future component
developments from this skilled company.
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. Readers may reach him at mike_riley_@hotmail.com.

N E W

&

U S E D

By John C. Penman

TurboDemo Professional 4
Multimedia Presentations in a Snap

n these days of fierce global competition, its


essential to market your killer application as
widely as possible and ahead of the competi-

tion. The Internet, specifically the World Wide Web,


provides an excellent communication tool to market
your software to a global audience. By using the Web,
developers can demonstrate their software by giving a
multimedia presentation. But how can the developer,
solitary and corporate, create a professional presentation without spending large sums of time and money?
The answer lies with TurboDemo Professional 4.

In the past, creating a demo or tutorial was never an


easy task. I know this from experience, having spent
hours modifying production code to create demos and
tutorials. Believe me, you dont want to go that route. I
used an application in the early 90s for building DOSbased demos and tutorials, but it was still hard work,
so I stuck to modifying production code, which, I am
sure you would agree, was not a very timesaving or
cost-effective approach.
Although it was with some trepidation that I received
the review copy of TurboDemo 4 Professional, I neednt
have worried. TurboDemo lives up to its claims of quickly
building professional tutorials and demos. TurboDemo is
more than a simple screen-capture program. It provides an
environment that makes designing, testing, and building
tutorials and demos an easy and painless exercise.

Overview
TurboDemo enables you to create professional online and
offline multimedia demos and tutorials using the pointand-click approach, and you dont need any programming
knowledge to use it. TurboDemo provides such presentation
formats as Flash, Java, EXE, and AVI.
Here are a few examples of how you can use TurboDemo to
provide slick and professional-looking tutorials and demos:
Create a demonstration of a product on the Web
Create a virtual tour of a Web site
Create a standalone demonstration of a product on CD
or as an e-mail attachment
Create a standalone tutorial on a products features or
a subset of features this can either be interactive or
non-interactive
Integrate online help documentation in the form of
animation (AVI)
Send a tutorial to users of how to use a feature in a product
35

DELPHI INFORMANT MAGAZINE | March 2004

Figure 1: A collection of slides, displayed as thumbnail images, in the second


stage of building a new TurboDemo project.

New

&

Used

TurboDemo Professional 4

Effect

Description

Animated notes and


balloons

Special areas of text that you add to


explain a feature or step.

Rollover and rubber


effects

Rollovers are captions that appear


when the user moves the mouse over
the rollover area. A rubber functions
similarly to that of an attractor. It
encloses a text or area by using a frame
that is either circular or rectangular. This
enclosure of the text helps the viewer
notice even the smallest details.

Eye-catcher through
attractors

An attractor is an arrow that blinks in


any given color when activated to focus
the users attention to important facts.

Hot-Key area

This is an assigned area that responds


to a keyboard generated Hot-Key (such
as Ct) by displaying a success or
error message depending on which HotKey is pressed.

Text area

This is a special area to accept input


from the user.

Hyperlink to a Web
page or another
demo/tutorial

Clicking on a URL in the tutorial will


open the home at that URL.

Record by using
audio wizard

Use the wizard to add a commentary


or audio.

Import WAV files

Add to a slide pre-recorded audio, such


as commentary or music in WAV format.

Assign audio to one


or more slides

You can add audio to one or more


slides.

Insert and delete


sound

The audio wizard makes it easy to add


or delete audio.

Figure 2: Some of the effects used for the review tutorial.

Installation and registration was easy. For this review I


was given registration information by e-mail as well as by
instructions that came in the box. One nice feature was that
the installation process created and placed an icon on the
desktop for instant access.
Before we look at TurboDemo in detail, lets be clear on the
terminology. Demos are not the same as tutorials. This fact
might be obvious to some readers, but I will explain it nonetheless. A demo displays the marketable characteristics of a
product to potential customers. This is a standard marketing
tool. A tutorial, on the other hand, teaches a user how to operate a piece of software or hardware. This is a weapon in the
e-learning arsenal. However, well designed demos and tutorials
developed for use on PCs have something in common, which
is the professional presentation of facts. To keep this review
simple, I will use the word tutorial to refer to a demo as well.
Like any well designed software tool, TurboDemo 4 is so
intuitive that I was able to knock up a tutorial in a matter
of minutes. However, as I found out, it takes a bit of preparation to create a more professional tutorial. As with any
project, its a good idea to make a plan or a script before
starting your tutorial. Having a plan or script to guide your
36

DELPHI INFORMANT MAGAZINE | March 2004

tutorial creation is a good idea when you build a tutorial for


a complex application or features of an application. If you
have a clear plan of what you want to teach or demonstrate,
you can easily create your tutorial. Without a clear plan,
you will spend time backtracking to correct the tutorial to
get an easy-to-follow, professional tutorial.
My tutorial consisted of more than 127 screenshots (or
slides in TurboDemo terminology). Because I didnt have a
plan, I discovered that I had missed screenshots, some of
which were blindingly obvious through hindsight. Although
TurboDemo has the capability to add extra slides after creating the tutorial, I thought it was easier to recreate the slides
again after I created my plan. I must point out that this is
not TurboDemos fault, but simply my mistake. Your plan
doesnt have to be detailed, just a sort of roadmap. In the
script I used for the review tutorial I simply listed actions to
capture with proposed special effects.
Creating a tutorial with TurboDemo consists of three stages.
The first stage is to capture screenshots of the target application. The second stage is to add special effects to the screenshots. The final stage is to select a format and build the
tutorial. To demonstrate how it all hangs together, lets tour
the important features of TurboDemo by going through the
three stages. For this review I created a tutorial on how to
build a Hello World Delphi project aimed at new users.
A Tutorial on a Tutorial
Click on the desktop icon to activate TurboDemo. On activation the program features a tip-of-the-day dialog box.
I found this useful as I began to learn how to use TurboDemo; you can disable this feature when youre comfortable with TurboDemo.
The following steps demonstrate how to build a new project
(you can view a demo online at www.turbodemo.com/help/
faq/project.htm). This is the first stage that also has wizards
to guide the beginner:
1) Select File | New Project.
2) A screenshot wizard appears presenting a choice of resolution, including a user-defined resolution. Select the appropriate resolution (the recommended resolution is 640 x 480
pixels). For this review, I chose 1024 x 768 pixels.
3) Select which key to capture the screen. By default, a mouse
button is used for capturing or you can use the Pause button. This is easily changed to the button of your preference.
4) Click the Start button to begin.
5) Select the target application.
6) Select the size of the recording window. There are two
key combinations; the first is CSP to resize the
application inside the recording window, and the second is
CSR to fit the recording window to the size of the
target application.
7) Capture every action by clicking a mouse button or your
personally defined key.
8) To stop capturing, click on the TurboDemo tray icon. A
dialog box will then display the number of slides captured.
You will be prompted to end the session. Click to confirm.
In the second stage, youll have a collection of slides, which
are displayed as thumbnail images (see Figure 1).

New

&

Used

TurboDemo Professional 4

Figure 3: Double-clicking a balloon displays a dialog box that contains an editor


where you can enter text.

Figure 4: This Insert Click area displays a message after a successful compilation.

At this point you can replay the screen captures as a slideshow. To replay the screenshots, the first slide is already
selected, so double-click it. The whole screen will fill with
the first slide. Youll see a player bar floating above the
slide. Click the Go button on the player bar to replay the
slides. Its here that you check for any missing actions. I
suggest that its better to have too many slides, because its
easy to discard slides, but a little trickier to add slides.

The third effect is the Insert Click area. This feature


allows your demo to interact with your viewer. In the
review tutorial, I added an area surrounding the OK
button to display a success message after a successful
compilation (see Figure 4). That is, when the tutorial
displays that slide, it displays a success message after the
viewer clicks. There are additional features included in
TurboDemo that further enhance the viewers interaction
with the tutorial.

When youre happy with the slides, youre ready to


enhance the tutorial. Of course, you can simply forego
the enhancements and deliver the tutorial as-is. But in
my opinion, a tutorial that has no written commentary or
special effects is pointless, especially since TurboDemo
makes it so easy to enhance your tutorial.
To add enhancements to the tutorial, press 0 or the Tool
button to display all slides. Then decide which slide to
enhance (this is where a simple script is useful). In the
tutorial I built, I enhanced a few slides with some of the
special effects shown in Figure 2. The special effects listed here are only a subset of a larger set of effects available with TurboDemo.
To add one or more special effects to a slide, simply
double-click the slide and select a special effect from the
toolbar. I dont have room in this review to detail all the
special effects, but Ill describe a few to give you an idea
of TurboDemos capabilities.
The first effect I want to highlight is the balloon text.
Simply position the effect at the desired location. Doubleclick on the balloon to display a dialog box that contains
an editor (see Figure 3). You can enter text in any font and
style that is appropriate to the tutorial. The second effect
is the Pause button. Place this button in a strategic location
somewhere on the slide. When this slide is shown, the tutorial stops until the user clicks the Pause button. This is useful
if the slide, for example, has some lengthy text for the user
to read. In addition to the Pause button, TurboDemo enables
you to accept the preset amount of viewing time for each
slide, or you can easily change the viewing time for an individual slide or the whole project.
37

DELPHI INFORMANT MAGAZINE | March 2004

The last feature of TurboDemo Im going to highlight


is the addition of audio, which is very beneficial to the
tutorial and is quite simple. Clicking the Audio button
brings up the Audio Wizard. This wizard enables you to
make an impromptu commentary (providing you have a
microphone) or to add pre-recorded audio, such as voice
or even background music. The only limitation is that the
imported audio must be in .WAV format. (Note that if the
time you allocated to a slide is shorter than the playing
time for the audio, the viewing time will be adjusted to fit
the playing time.)
After enhancing the slides, youre now ready to compile
the slides into one of four available formats.
Formats to Suit Everyone
In the third stage of creating a new project, TurboDemo
4 Professional provides four formats: Flash, Java/Applet,
Standalone Executable, and AVI video. Figure 5 shows
these formats and their descriptions. Be aware that
TurboDemo 4 Standard only includes the Flash format.
If youre reaching your target audience by e-mail, then
you should create the tutorial in Flash or Java and
publish it to the Web. Then send the URL link of this
tutorial to your audience via e-mail. Both formats are
created with streaming technology. If youre creating a
specific demo for one customer, its better to create your
tutorial as a standalone executable and e-mail it as an
attachment. Figure 6 illustrates the different formats and
how they compared for the tutorial I created. You can see
that Flash is the slowest for compilation.

New

&

Used

TurboDemo Professional 4

Format

Description

Format

Flash (SWF)

This generates a Flash animation that a browser can view offline or online.

Flash

Java/Applet

This generates a Java applet that is smaller


and faster than a Flash animation.

Standalone EXE

Standalone EXE

This generates a single executable, which can


be sent via e-mail or written to a CD-ROM.

AVI

This generates an uncompressed AVI video


with no sound.

Figure 5: Selecting the appropriate format depends on the target audience and
the method of delivery.

Except for the standalone executable and AVI video


formats, youre prompted if you want the tutorial to be
run in the browser. If you chose the Flash or Java/Applet
format, your browser, which must be Java enabled,
will display instructions on how to install the tutorial
on the Web server. The instructions are clear and
straightforward.
In the online help there is some information on each
format, listing the pros and cons of each. For example,
if you want your tutorial to run on desktops as well
as Pocket PC devices, you need to choose the Flash or
Java/Applet format. My personal preference is the Java/
Applet, because it has a much smaller footprint than
Flash for Pocket PC devices. A standalone executable is
an excellent choice if you target only Windows users.
The AVI video format would seem to be a fine choice
as there is no limit to the size of the movie, but the AVI
video format is silent. The reason for the lack of audio
is that users want to add audio to one slide at a time.
The information on the formats in the online help repays
careful study.
The differences between the Professional and Standard
editions are substantial. For serious e-learning and
marketing projects, the Standard edition is not suitable.
With the Standard edition you cannot generate tutorials
in the Java/Applet, standalone executable, and AVI video
formats. That, I consider, is a big limitation. You would
do better to pay extra for the Professional edition to

Differences between TurboDemo


Professional and Standard
The standard edition does not support the following features:
Objects Action Assignment
Slide Action Automation
Rollovers Creation
Java/Applet Creation
AVI Movie
EXE-File Creation
Resizing a Demo/Tutorial
Standalone EXE Player
Skins with Your Look and Style
Printing of Slides
Time-limited Demos Creation
Import/Export from Clipboard

38

DELPHI INFORMANT MAGAZINE | March 2004

Java/Applet
AVI

Size

Compile Time (secs)

6668KB

260

882KB

75

6193KB

262

3.5GB

187

Figure 6: Comparisons of sizes and compilation speeds for the review tutorial.

do serious work. See the sidebar Differences between


TurboDemo Professional and Standard for a list of
options not included in the Standard edition.
Gremlins and Gripes
I used TurboDemo 4 Professional on two different
machines and noticed a few gremlins. One was a graphic
driver issue on the Dell Inspiron 8200 laptop. The major
symptom was that TurboDemo only captured part of the
active application. I attempted to cure the problem by
changing the native resolution of 1600 x 1200 pixels to
1024 x 780, but the clipping problem persisted.
Marc Schuler of the support department at Bernard D&G
partially resolved this problem by applying a patch to
TurboDemo 4, and I had to place the active application
in the top-right portion of the screen prior to screen
capture. A fix has been incorporated in the latest version
of TurboDemo, which completely cures this problem.
Apparently, this clipping problem only occurs on a few
machines in the Dell Inspiron range. The original version of
TurboDemo 4 ran with no problems on my desktop PC that
has a venerable AMD Duron 770 MHz processor and 256
MB RAM. I must add that the response from Bernard D&G
was most helpful and quick to answer any queries I had.
This is good news for any future purchaser. A product, no
matter how well it performs, is also judged by the quality
of support to the customer and Bernard D&G provided
excellent customer service.
As part of testing any new product I do things most users
wouldnt dream of doing. One thing I tried was to generate
a standalone executable twice in succession. TurboDemo
generated the executable twice perfectly, but the prompt
asking to play the executable did not come onscreen
the second time. Instead, the dialog box to generate the
executable appeared. Clicking the Cancel button didnt
work, which forced me to run the task manager to abort
TurboDemo. Within 24 hours of reporting this problem,
the developers at Bernard D&G reported that they hadnt
been able to reproduce the problem, so I must conclude
that I have an issue with my PC.
During screen capture I found using the left or right
mouse button to capture screens clashed awkwardly with
Delphi (which was the active application) because it
also uses mouse buttons to perform actions in the IDE.
Because of this I prefer to use the Pause button on the
keyboard to perform screen captures.
Conclusion
In spite of the initial problem with the graphics issue on
my laptop and the gremlins mentioned above, I highly

New

&

Used

TurboDemo Professional 4

Just the Facts


TurboDemo enables you to create professional online and
offline multimedia demos and tutorials using the point-andclick approach, and you dont need any programming knowledge to use it. For the small software house where resources
and money are scarce, the main benefit of using TurboDemo
is that the addition of demos or tutorials will increase the
value of your application.
Bernard D&G
Contact: sales@turbodemo.com
Web Site: www.turbodemo.com
Price: Visit the TurboDemo Web site for details.
recommend TurboDemo. For me, the novel thing about
TurboDemo is that it can create a tutorial to run on a
Web site either as a Flash object or Java/Applet. In these
days of strict security, this feature becomes more telling
because it permits potential customers to view a demo
of a product on your Web site without the need to install
the demo software on their machines. In any case, with
firewalls and other security measures now common, users
are not able to install trials or tutorials.
There are four other aspects I particularly like about
TurboDemo:
It uses the build, edit, and test cycle that we love so
well with Delphi within the TurboDemo IDE. Its a
small point, but any Delphi coder should feel right at
home. Its point and click methodology is excellent.
It has excellent wizards for the beginner, as well as
excellent online help that actually includes a dynamic
tutorial to demonstrate how to build a tutorial.
The Professional edition offers four formats for
presenting the tutorial.
It gives clear instructions for installing your tutorial
that will run on your Web server.
TurboDemo doesnt require any knowledge of a scripting
language to create useful tutorials, which is great news
for developers. After youve built a professional tutorial
for your killer application you will then have time to
create your next killer application. For the small software
house where resources and money are scarce, the main
benefit of using TurboDemo is that the addition of demos
or tutorials will increase the value of your application.
Bernard D&G recently released TurboDemo 5. Visit
www.turbodemo.com for complete details on upgrades. Ed.

John C. Penman is the owner of Craiglockhart Software, which specializes


in Internet and intranet software solutions. He is the co-author (with Alan
C. Moore, Ph.D.) of The Tomes of Delphi: Basic 32-bit Communications
Programming (Wordware Publishing, 2003). John can be reached at
jcp@craiglockhart.com.

39

DELPHI INFORMANT MAGAZINE | March 2004

T E X T F I L E

Advantage Database Server:


The Official Guide

et me get straight to the point:


Buy this book if you use Extended
Systems Advantage Database Server
(ADS). Not only is it the only title
in print exclusively devoted to ADS,
Advantage Database Server: The Official
Guide is also extremely well written.
Authors Cary Jensen and Loy Anderson, two well known and respected
Delphi advocates, offer a no-nonsense
approach that distills the subject into
lean morsels of meaningful instruction that quickly empower readers
with a clear understanding of the
product. This is no surprise, since the
authors have written many other Delphi and database-related books using
a similar style.
The book covers the full spectrum
of the product and how to leverage
it into various solutions. Its intelligently divided into four parts. The
first part, ADS and the Advantage
Data Architect, introduces the server
and related tools and walks through
the creation and usage of an ADShosted database. Chapters on creating
tables and views; defining indexes,
constraints, and triggers; using data
dictionaries; and creating Advantage
Extended Procedures (AEPs) using
the languages supported by the product, are all succinct and accompanied
by more helpful screen shots than
any user guide could attempt to offer.
Part II, Using Advantage SQL,
explains the dialect of SQL used in the
40

DELPHI INFORMANT MAGAZINE | March 2004

product and proper use of the syntax


within the product. This section concludes with administrative task management as it relates to access control
to the data dictionary objects.
Part III, Accessing ADS, demonstrates how to connect to and interact with ADS from Delphi, Java,
Visual Basic, .NET, ODBC, PHP, and
DBI/Perl. Three appendices in Part
IV round out the book. Appendix A
discusses ADS installation issues,
Appendix B covers the code samples
contained on the books CD-ROM,
and the last appendix closes the book
with a pointer to the books Web
site: www.JensenDataSystems.com/
adsbook.html. Among other features,
the Web site provides excerpts from
the book as samples of the authors
effective writing.
The CD-ROM also includes a singleuser-licensed copy of Advantage
Database Server 7.0 for Windows,
Linux, and NetWare; tools such as
the Advantage Data Architect; and
the data providers and drivers for all
the supported languages discussed
in the book. As a result, Advantage
Database Server: The Official Guide is
ideal for those interested in test driving ADS.
What I appreciated most were the
notes peppered throughout the book;
these key learning experiences from
the authors could save anyone who
interacts with ADS a lot of time.

Advantage Database Server:


The Official Guide
by Cary Jensen, Ph.D.
and Loy Anderson, Ph.D.,
Osborne/McGraw-Hill,
www.osborne.com.
ISBN: 0-07-223084-3
Cover Price: US$49.99
(468 pages, CD-ROM)

About the only thing missing is an


honest comparison chart from the
authors real-world point of view on
how ADS compares to other DBMSes
on the Windows and Linux platforms
competing in the same market.
The cost of the book is dwarfed by
the overall value it delivers, especially for any company considering the
ADS platform.
Mike Riley

F I L E

N E W

.NET Bookshelf 2004

By Alan C. Moore, Ph.D.

an there be any doubt that .NET is


at the top of the list of new technologies? Although it hasnt been that
long since I wrote about .NET books, an
avalanche of new titles has appeared.
This month Ill discuss the books Ive
examined. In a future column Ill discuss
additional advanced .NET books, dealing
with under-the-hood topics such as the
CLR (Common Language Runtime).
The .NET language. Microsofts relatively new programming language, C#,
closely associated with .NET, is continuing to gain in popularity. With its
type safety and other characteristics,
Andrew Troelsens characterization of
C# as a cleaned up version of Java
makes sense. His insightful book, C#
and the .NET Platform (Apress, 2003),
remains one of my favorites. This
month Ill introduce two more.
SAMS Teach Yourself the C# Language
in 21 Days by Bradley L. Jones (SAMS,
2003) assumes little prior knowledge
or experience. It starts by explaining
basic programming practices, OOP, and
getting the reader started writing and
compiling C# programs. It then explains
all the basic language elements, operators, and syntax. Next come more complex topics, such as classes and data
structures. The final chapters introduce
some of the popular programming
areas that .NET addresses, including
Windows Forms and applications, ADO,
Web applications, and Web Services.
The book is an excellent tutorial; I
especially recommend it to developers
with limited experience.
If on the other hand, youre looking for
a more advanced C# tome, I recommend
Joseph Mayos C# Unleashed (SAMS,
2002). This book covers essentially the
same basic language areas that Jones
does, but with some differences in the
more advanced topics. It also gets into
some of the essential application areas
41

DELPHI INFORMANT MAGAZINE | March 2004

with chapters on ADO.NET, ASP.NET,


Remoting, and Web Services. The large
section on OOP and components will
be especially helpful to those who need
help in these areas. Theres a topic
toward the end that I think will interest
many Delphi developers: cross-platform
programming with C#.
Specific .NET topics. There are issues
that must be addressed if you are planning to use COM components in a
.NET application, or .NET components
in a COM-based application. Adam
Nathans .NET and COM: The Complete
Interoperability Guide (SAMS, 2002)
tackles these important topics, and
more. It explains strategies for writing
.NET components that will work with
older COM applications. Theres considerable discussion on using PInvoke
to expose entry points in DLLs that
implement Win32 and other earlier
code. Code examples are written in
the main .NET languages, including
C# (I would prefer they all be in that
language, but alas...). In addition, the
book provides much helpful advice to
the would-be .NET component writer
on such topics as managed/unmanaged
code, imported assemblies, type libraries, and other more advanced topics.
As I pointed out in this column in the
April 2003 issue (Entering the .NET
World), one of .NETs main goals is to
provide better support for Web Services
and distributed programming. In that
column I reviewed several books that
deal with this topic in various ways.
Robert Tabors Microsoft .NET XML Web

Services (SAMS, 2002) is a nice addition


to that list. It may not be as comprehensive as some of the books I discussed
in that earlier column, but it does touch
on the essential technologies, including
ADO.NET, ASP.NET, SOAP, and more.
With code examples in C# and Visual
Basic, it targets programmers using
Visual Studio .NET or just the .NET
SDK. However, this book should provide
some helpful clues for Delphi folk as
well. The opening chapter provides an
overview of all these technologies, the
current state of Web Services, their relationships, and future projections.
This final book my favorite among
the five reviewed here could very
well be the most valuable one for Delphi
developers who want to dig deep and
discover the hidden secrets of .NET.
Kevin Burtons .NET Common Language
Runtime Unleashed (SAMS, 2002) really
gets under the hood, with detailed
discussions of the runtime environment,
the IL (Intermediate Language), assemblies, and other fundamental areas. I
especially liked the section titled Leveraging Existing Code with P/Invoke.
This is an area that interests me and
which I hope to explore more this year
porting some of my existing multimedia code to .NET. There are insightful
sections on memory management, localization, debugging, and profiling. This
book also touches on some of the same
areas as the above-mentioned works.
Stay tuned, as I plan to continue exploring .NET in additional columns later this
year. Until next time.

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-2004.
He has developed education-related applications with the Borland languages for more than 15 years. Hes the
author of The Tomes of Delphi: Win32 Multimedia API (Wordware Publishing, 2000) and co-author (with
John C. Penman) of The Tomes of Delphi: Basic 32-Bit Communications Programming (Wordware Publishing,
2003). 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 at acmdoc@aol.com.

You might also like