Professional Documents
Culture Documents
2004
Volume 10 Number 3
INSIDE
ON THE COVER
Columns & Rows
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
www.DelphiZine.com
ADO NET
In Development
Undocumented Delphi 8
10
Registry Hacks
Post Mortems
Working with .NET
Profiling
.NET Developer
14
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.
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
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
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
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
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
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
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-
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
C O L U M N S
ADO.NET
&
R O W S
By Bill Todd
Columns
&
Rows
Columns
&
Rows
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.
Columns
&
Rows
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;
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
Columns
&
Rows
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;
I N
D E V E L O P M E N T
ASSESSMENT
By Steven Beebe
In
Development
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
In
Development
12
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
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
. N E T
D E V E L O P E R
.NET FRAMEWORK
.NET COLLECTIONS
THE .NET FRAMEWORK
FILE I/O
By Xavier Pacheco
Purpose
Directory
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
FileInfo
FileSystemInfo
Path
Figure 1: File and directory classes (from Delphi for .NET Developers Guide).
.NET
Developer
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');
15
.NET
Developer
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;
Member
Description
Attributes
CreationTime,
CreationTimeUtc
Exists
Extension
FullName
LastAccessTime,
LastAccessTimeUtc
LastWriteTime,
LastWriteTimeUtc
Delete
FullPath
OriginalPath
Member
Description
Archived
Compressed
A compressed file.
Device
Unused/Reserved.
Directory
File is a directory.
Encrypted
Hidden
Normal
NotContextIndexed
Offline
ReadOnly
File is read-only.
ReparsePoint
SparceFile
System
Temporary
16
.NET
Developer
program Project1;
{$APPTYPE CONSOLE}
Value
Description
Attributes
CreationTime
DirectoryName
FileName
LastAccess
LastWrite
Security
Size
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
17
.NET
Developer
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
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.
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)
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
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
The
API
Calls
Perfect Timing
The
API
Calls
Function
Perfect Timing
Documented
Win98
Win2000
WinXP Pro
GetTickCount
1000
1000
15000
TimeGetTime
1000
1000
1000
15000
15625
15625
hardware
GetSystemTime
QueryPerformanceCounter
GetThreadTimes
15625
15625
GetProcessTimes
15625
15625
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).
Some Unexpected
Results
The result returned by the
QueryPerformanceCounter
function may unexpectedly
leap forward from time to
time. This leap may represent
several seconds.
The
API
Calls
Perfect Timing
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.
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.
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;
Fernando Vicaria
The
API
Calls
Perfect Timing
end;
liLast := liCurrent;
end;
end.
program QueryPerfTest;
{$APPTYPE CONSOLE}
uses
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;
%d'#13,
[j, liRecent[i].QuadPart]));
i := (i+1) div 10;
WriteLn(Format(
%d
delta = %d milliseconds'
procedure StartTime;
asm
RDTSC
mov shi, edx
mov slo, eax
end;
procedure FinishTime;
asm
RDTSC
mov flo, eax
end;
function GetTicks: Extended;
begin
var
%d:
%d'#13,
[j, liCurrent.QuadPart]));
liRecent[i].QuadPart := liCurrent.QuadPart;
i := (i+1) div 10;
end;
WriteLn;
23
DefaultAppPriority: TAppPriority;
end;
WriteLn(Format('
end;
'LEAP: Current:
var
begin
WriteLn(Format('
PriorityClass: Integer;
end;
// 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
// StdCall convention.
CALL Sleep
DW $0310F
// RDTSC
// Milliseconds.
end;
function Profile(FuncPointer: TProcedure): Extended;
begin
StartProfiler;
FuncPointer;
Result := StopProfiler;
// Operation to be profiled...
// result is in milliseconds.
end;
end.
24
SOUND+VISION
INTERNET EXPLORER INTERFACE
TWEBBROWSER
HTML
DELPHI 7
By Zoltan Kurczveil
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.
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
Figure 1: Borland C#Builder sports a good example of the new IE-based interface.
Sound
Vision
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>
<img src="HelloWorld.jpg."/>
An HTML page
featuring hyperlinks and
buttons to initiate tasks
provides users with a
familiar look and feel.
Command:Parameter
Custom tags are also used for images. Images are displayed in IE using the standard HTML tag:
Sound
Vision
i : Integer;
begin
AStream : IStream;
begin
ReplaceText := '';
AStream := nil;
try
HTML_TABLEENTRY, [FRecentFiles.Items[i],
AStream := TStreamAdapter.Create(m);
ExtractFilename(FRecentFiles.Items[i])]);
end
m.Write(Pointer(HTMLSource)^, length(HTMLSource));
begin
IStream implementation. }
ipStream.load(AStream);
finally
m.Free;
ipStream := nil;
AStream := nil;
end;
end;
<a href="openfile:C:\ARecentFile.txt">ARecentFile.txt</a>
Sound
Vision
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;
Command
URL
new:
openfile:C:\Text.txt
open:
exit:
Sound
Vision
29
UNDOCUMENTED
REGISTRY HACKS
DELPHI 8
By Corbin Dunn
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
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
Registry Entry
Package Description
$(BDS)\Bin\delphidotnetide71.bpl
$(BDS)\Bin\dotnetcoreide71.bpl
$(BDS)\Bin\dotnetdebugide71.bpl
$(BDS)\Bin\idefilefilters71.bpl
$(BDS)\Bin\vcldotnetdesignide71.bpl
Figure 3: For a bare bones version of Delphi, set all values but these to a
blank string.
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
N E W
&
U S E D
By Mike Riley
32
New
&
Used
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.
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
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
34
N E W
&
U S E D
By John C. Penman
TurboDemo Professional 4
Multimedia Presentations in a Snap
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
New
&
Used
TurboDemo Professional 4
Effect
Description
Eye-catcher through
attractors
Hot-Key area
Text area
Hyperlink to a Web
page or another
demo/tutorial
Record by using
audio wizard
New
&
Used
TurboDemo Professional 4
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.
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
Standalone EXE
Standalone EXE
AVI
Figure 5: Selecting the appropriate format depends on the target audience and
the method of delivery.
38
Java/Applet
AVI
Size
6668KB
260
882KB
75
6193KB
262
3.5GB
187
Figure 6: Comparisons of sizes and compilation speeds for the review tutorial.
New
&
Used
TurboDemo Professional 4
39
T E X T F I L E
F I L E
N E W
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.