Sie sind auf Seite 1von 38

March

2003

Volume 9 Number 3

INSIDE
O N

T H E

C O V E R

.NET Tech

Using the .NET Compiler: Part III

ADO.NET is the next step in the evolution of ADO, say


Alexei Fedorov and Natalia Elmanova. And a .NET
compiler is the next step in the evolution of Delphi, so you
dont want to miss this article that brings them together
with plenty of examples, of course.

New Column: The Lighter Side of ... Tech Support

Delphi Informant
www.DelphiZine.com

March 2003 vol. 9, no. 3

The Complete Monthly Guide to Delphi Development

F E A T U R E S
Greater Delphi

Microsoft Message Queue

I know youre wondering: Why would I use MSMQ? For


starters, how bout to make a synchronous process
asynchronous? Matthew Hess demonstrates how to use the
MSMQ architecture to create simple, polling-based readers,
to sophisticated, transactional, events-based readers.

Columns & Rows

16

Creating ADO.NET Apps


with the .NET Compiler

Page 8

Using Microsoft Message Queue


with Delphi

Page 16

A Component to
$5.99 US

$8.99 CAN

Replace BatchMove

Page 24

Preparing for .NET:

The Framework Class Library

Page 34

Cover Art by Arthur A. Dugoni Jr.

Replacing BatchMove

The BDE is going away. Thats no surprise, and most


Delphi developers consider it good riddance. But what
about BatchMove? That was one useful program! Have no
fear; Bill Todd provides us with a component that copies
data from one table to another, imports data from an
ASCII file, and exports data to an ASCII file.

Foundation Class

25

Preparing for .NET: Part II

Alexei Fedorov and Natalia Elmanova are doing everything


they can to prepare you for the emerging .NET world. In
this installment, they provide a synopsis of the major
namespaces and classes that comprise the Microsoft .NET
Framework Class Library (FCL).

Toward the Light

30

Lighter Side of Tech Support

Dont let software development become all work and no play


especially during these tough times. Let Loren Scott keep
you in touch with the humorous side of programming. This
month, tech support gets the Lighter Side treatment.

R E V I E W S
33 DbAltGrid

Product Review by Warren Rachele

36

The Tomes of Delphi: Basic 32-Bit


Communications Programming
Book Review by Mike Riley

DELPHI INFORMANT MAGAZINE | March 2003

D E PA R T M E N T S
2 Toolbox
37 File | New by Alan C. Moore, Ph.D.

T O O L B O X
PASS Engineering Announces PASSOLO 4.0
PASS Engineering announced PASSOLO 4.0, a professional localization tool.
PASSOLO optimizes software localization
and ensures easy compilation, exchange,
and processing of translation data. PASSOLO requires neither time-consuming
and expensive training nor programming
experience. Many errors that occur during localization are avoided or automatically recognized by PASSOLO.
Software generally contains repetitive text. PASSOLO allows you to reuse
existing translations. Even text from programs that havent been translated with
PASSOLO can be used for the semi-automatic translation of new projects.
PASSOLO 4.0 handles binaries with
Windows standard resources, as well
as Delphi, C++Builder, Microsoft
.NET, Visual Basic 6, Java, and XML
files, and 16-bit programs. It comes
with an integrated menu, dialog and
bitmap editors, fuzzy matching, glossaries, and checking functions.
New features in PASSOLO 4.0 include
an optional file parser add-in for Delphi
and C++Builder files, an optional file
parser add-in for Microsoft .NET files,
an integrated file parser add-in for ID-

based XML files,


an integrated file
parser add-in for
Windows 16-bit
program files, an
individual user
interface with configurable toolbars
and dockable and
resizable output
window, simplified
project setup with
language default
settings, the ability
to enter and store
additional translator comments,
configurable order
and usage of translation sources for automatic translation, configurable handling
of different translations for equal source
texts, improved translation dialog with
simplified handling and enhanced user
information, improved dialog and menu
editors, and more. Visit the PASSOLO
Web site for further details.
The Professional Edition has a VBAcompatible scripting engine and OLE
automation. A Unicode version for

Elevate Software Offers Version 3.21 of DBISAM


Elevate Software announced
DBISAM version 3.21 is now available
for download. The DBISAM Database System is a small, embedded
database engine for use by Delphi 5,
6, and 7 and C++Builder 5 and 6
developers. It compiles directly into
an EXE with no external DLLs or
configuration files needed.
This release offers many upgrades
from previous versions. The most
notable changes to recent releases are
bug fixes, the addition of a Cancel button in the SQL Query window in the
database system utility, compatibility
changes in the engine for the ODBC
driver, the addition of the ability to
default the full text indexing parameters in the DBSYS utilitys create/
restructure dialog box, and changes to
the server so that it no longer converts
relative path names to full path names
(including drive) at the time of the
database addition or modification (it
now performs the conversion at run
2

DELPHI INFORMANT MAGAZINE | March 2003

time as needed). Visit the Elevate Web


site for a complete list of upgrades.
The utilities and their respective
source code have been split into a
separate installation download to
reduce the likelihood of a connection
timeout when downloading a new version from the Elevate Web site. Also,
to reduce the download size, the Customer Support Utility (DBCUST.EXE)
and the Trial Version Survey Utility
(DBTRIAL.EXE) have been removed
from the installations. Both of these
utilities have been (or will be) replaced
by a Web-based alternative.
The DBISAM ODBC driver is available
for an additional charge of US$199. The
DBISAM ODBC driver with complete
source code is available for an additional charge of US$599.
Elevate Software
Price: Varies; visit the Elevate Web site for a complete list of pricing options.
Contact: sales@elevatesoft.com
Web Site: www.elevatesoft.com

Asian and right-to-left languages is


included. Optional interfaces for TRADOS Workbench and STAR Transit are
available. PASSOLO runs on Microsoft
Windows 95 or higher, and Microsoft
Windows NT 4.0 or higher.
PASS Engineering GmbH
Price: Visit the PASSOLO Web site for distribution
and pricing details.
Contact: info@passolo.com
Web Site: www.passolo.com

Altova Releases
New Product Line
of XML Tools
Altova announced a new product
line, consisting of xmlspy 5, authentic
5, and stylevision 5, to facilitate and
advance the adoption of XML technologies. xmlspy 5 is an XML development
environment for designing, editing, and
debugging enterprise-class applications
that involve XML, XML Schema, XSL/
XSLT, SOAP, WSDL, and Web Services.
authentic 5 is a standards-based, browser-enabled document editor that allows
business users to capture thoughts and
ideas seamlessly, directly in XML format. stylevision 5 is a new XML tool for
Web developers that provides powerful
conversion utilities for migrating traditional HTML Web sites to advanced
XML-based sites in full compliance with
new Internet standards such as XML,
XSLT, and XML Schema.
Altova Inc.
Price: See Web site for pricing.
Contact: sales@altova.com
Web Site: www.altova.com

T O O L B O X
Add Imaging Capabilities with ImgX Controls
Atalasoft has released ImgX Controls
6.2, a collection of controls that lets
developers add imaging functionality to
their applications. ImgX Controls can be
used in any development environment
that supports ActiveX, including Delphi,
Visual Basic, .NET, Visual C++, and
FoxPro. This suite of controls lets you
add basic imaging functions such as capturing, scanning, and printing images,
as well as high-end image viewing and
manipulation capabilities.
ImgX will read and write most popular
graphics file formats, including JPEG,
PNG, TIFF, TIFF G3/G4 Fax, PCX, TGA,
BMP, PSD, PCD, GIF, EXIF, and WMF/
EMF. ImgX uses a low-level C++ imaging library with an ActiveX front end.
ImgX Controls provides more than
100 built-in filters, transforms, and
effects. You can draw lines, rectangles,
ellipses, and add text with a textured
background and antialiasing. You can

create linear and radial gradients,


and use them as texture for text
and shapes. ImgX Controls also
includes 20 resampling methods,
so your apps can resize images
and create high-quality output.
You can even overlay images with
an alpha transparency.
Using the ImgXOCX Control
you can display and zoom images with a scrollable viewport
and create ActiveX Web imaging.
With the ImgXPrint DLL Control
you can print settings, show dialogs boxes, and print images. The suite
includes a customizable image preview
dialog box for opening and saving
images with the ImgXDialog DLL. The
ImgTwain DLL provides twain support
for scanning images with basic and
advanced duplex features. The ImgCapture DLL provides screen capture support. ImgX is .NET ready.

Xapware Introduces Active! Focus


Active! Focus by Xapware is an inteconfigurable navigation systems, and
grated workbench for tracking project
advanced editing tools for documenting
requirements and team assignments.
items. Historical tracking of requireThis product brings awareness of a
ments and other factors include the
project to everyone involved, enhancing
ability to attach separate files, enabling
communication among all contributors
documentation of items to include files
and stakeholders in your software projof any format word processing docects. As a flexible full life-cycle engineeruments, diagrams, models, and others
ing system, Active! Focus integrates into
to be directly included as reference
teams regardless of their language, tool,
material. Detail views of items also
or methodology preferences.
support rich-text editing directly in the
Active! Focus presents a clear view
program, enabling better communicaof software projects throughout their
tion amongst team members.
lifecycles. Active! Focus accomplishes
Active! Focus includes Online Advithis with user-customizable displays,
sors, special documents written to

C# in a Nutshell

Peter Drayton, Ben Albahari,


and Ted Neward
OReilly & Assoc.
ISBN: 0-596-00181-9
Cover Price: US$39.95
(832 pages)
www.oreilly.com

ImgX requires little or no knowledge of


imaging. The control suite includes comprehensive context-sensitive help, as well
as a library of examples.
Atalasoft
Price: US$299; a Lite version, without some of
the image enhancing capabilities and additional
controls, is available for US$149. There are no
runtime royalty fees.
Contact: support@atalasoft.com
Web Site: www.atalasoft.com

assist in getting started with your


software projects. Covering diverse
topics such as Writing Good Requirements, Risk Assessment, and more,
these articles have been assembled
from the best industry experts on
software project management. The
Active! Focus Online Advisors help
you plan smart strategies to take your
development efforts to success.
Xapware Technologies, Inc.
Price: Single-seat license US$499; quantity discounts available.
Contact: sales@xapware.com
Web Site: www.xapware.com

A Programmers Guide to
ADO.NET in C#

C# Essentials,
Second Edition

Mahesh Chand
Apress
ISBN: 1-893115-39-9
Cover Price: US$44.95
(711 pages)
www.apress.com

Ben Albahari, Peter Drayton,


and Brad Merrill
OReilly & Assoc.
ISBN: 0-596-00315-3
Cover Price: US$24.95
(202 pages)
www.oreilly.com

DELPHI INFORMANT MAGAZINE | March 2003

C# and the .NET Platform

Andrew Troelsen
Apress
ISBN: 1-893115-59-3
Cover Price: US$59.95
(970 pages)
www.apress.com

. N E T
ADO.NET

T E C H

MICROSOFT .NET DELPHI FOR .NET COMPILER PREVIEW DELPHI 7

By Alexei Fedorov and Natalia Elmanova

Using the .NET Compiler


Part III: Creating ADO.NET Applications

his is the third installment of our series devoted to the Delphi for .NET Compiler Preview
that ships with Delphi 7 Studio. In previous

articles we demonstrated how to create console and


Windows Forms applications (see the November 2002
and January 2003 issues of Delphi Informant Magazine). This month, we focus on using ADO.NET to
access databases in .NET applications.
The Evolution of Data
Access Mechanisms
Many database management systems (DBMSes) provide
libraries that contain special interfaces (APIs) that make
available a set of functions to execute queries on databases
over a network. Using such APIs directly means that an
application will be DBMS-specific. This problem is solved
by creating a universal data access mechanism that provides
a standard set of common functions (classes, or services)
to use in client applications for different types of DBMSes.
These standard functions must be placed into libraries,
which are usually called database drivers or providers.
Delphi developers are familiar with a set of universal data
access mechanisms supported by Microsofts ODBC (Open
Database Connectivity): OLE DB, and ActiveX Data Objects
(ADO). Of course, theyre also familiar with the Borland
Database Engine (BDE) and dbExpress.
Before Delphi 5, the BDE was the principal way to access
data in Delphi. The BDE is a proprietary mechanism devel4

DELPHI INFORMANT MAGAZINE | March 2003

oped by Borland for its own products primarily for


the desktop databases, Paradox and dBASE. Data access
to client/server databases through the BDE was, in fact,
significantly influenced by a desktop vision. Now, Borland doesnt recommend using the BDE. Instead, Borland
provides another universal data access mechanism, named
dbExpress, which targets client/server DBMSes, multi-tiered
applications, and disconnected datasets.
ODBC, OLE DB, and ADO are industry standards supported
by all leading database vendors. ODBC is a widely used API
satisfying the ANSI and ISO standards for a database Call Level
Interface (CLI). ODBC supports access to any database (and
even to some
ADO
ADO.NET
non-database
files, such as
Connection xxxConnection
spreadsheets) for
SqlConnection
which an ODBC
OleDbConnection
driver is available.
xxxTransaction
Microsoft doesnt
SqlTransaction
recommend using
OleDbTransaction
ODBC when
Command
xxxCommand
developing a new
SqlCommand
version of soft OleDbCommand
ware, because further development
Recordset
DataSet
of this standard is
xxxDataReader
not planned.
SqlDataReader
OLE DB and ADO
are parts of the
Microsoft Universal Data Access
that provides

OleDbDataReader
xxxDataAdapter
SqlDataAdapter
OleDbDataAdapter
Figure 1: Mapping ADO classes to ADO.NET classes.

.NET

Te c h

Using the .NET Compiler

Namespace

Description

Class

Description

System.Data

A set of core ADO.NET classes,


including classes that make up
the disconnected part of the data
access architecture (such as the
DataSet class).

xxxConnection

Establishes a connection to a specific


data source. The SqlConnection class
connects to SQL Server data sources.

xxxCommand

Executes a command (SQL statement or


stored procedure) from a data source.
The SqlCommand class executes a command in a SQL Server data source.

xxxDataReader

Reads a forward-only, read-only


stream of data from a data source. Its
returned by the ExecuteReader method
of the xxxCommand class, typically as a
result of a SELECT SQL statement. The
SqlDataReader class reads data from
tables in a SQL Server data source.

System.Data.Common

Utility classes and interfaces that


are inherited and implemented
by .NET data providers.

System.Data.SqlClient

SQL Server .NET Data Provider.

System.Data.OleDb

OLE DB .NET Data Provider.

System.Data.SqlTypes

Classes and structures for native


SQL Server data types. These
classes provide a safer, faster
alternative to other data types.

System.Xml

Classes, interfaces, and enumerations that provide standards-based


support for processing XML.

Figure 2: Data-related namespaces in the Microsoft .NET Framework Class Library.

access to all data sources, including non-relational ones


such as file systems, e-mails, multidimensional databases,
etc. ADO is an application-level programming interface to
such data. OLE DB provides a low-level interface to data.
ADO uses OLE DB, but its also possible to use OLE DB
directly (as a set of COM objects). The OLE DB provider is
a DLL that a client application can use to access a specific
OLE DB data source.

Figure 3: Classes used in the connected scenario.

Data-related Namespaces
As mentioned, ADO.NET is implemented in the System.Data
namespace. Some parts of ADO.NET reside in the System.Xml
namespace, e.g. the XmlDataDocument class. The data-related
namespaces are shown in Figure 2. In general, the ADO.NET
object model consists of two major parts: the DataSet classes
and the .NET data provider classes. Well look at both of them
in the following sections.
The DataSet Classes
The fundamental concept of the ADO.NET disconnected
architecture is implemented in the DataSet class an
in-memory cache of data tables, relationships, and
constraints. We can populate a dataset from a SQL query,
an XML document, or by creating tables, relations, and
constraints programmatically.

ADO itself contains a set of libraries (DLLs) that implements the ADO object model, and that can be used in
client applications. ADO is a very popular way to access
data, since it comes
with Windows 2000
Datasets usually contain
In reality, ADO.NET is a marketing
and Windows XP,
a subset of the data in
term that covers the classes in the
as well as with such
a central database, or
popular products as
in a general case
System.Data namespace.
Microsoft Office and
in several databases.
Microsoft Internet
We can manipulate a
Explorer. Readers can find further information in the book dataset in a disconnected application. We can also write
Advanced Delphi Developers Guide to ADO (published by
out the contents of a dataset in XML format, and pass it
Wordware, 2000).
to other applications and services. Developers acquainted
with dbExpress may find some ADO.NET features
ADO.NET
familiar.
ADO.NET is the next step in the evolution of ADO. Although
ADO.NET doesnt have the same programming model as ADO,
If we dont need to cache data (for example, when we
it does shares much of its functionality. In contrast to ADO,
create a report), we can use the DataReader classes
ADO.NET provides extensive support for building disconnected
(either SqlDataReader or OleDbDataReader), which
applications, which are the basis of modern distributed sysprovide connected access to a forward-only dataset. This
tems. A typical example of a modern distributed application is
functionality is also well known to dbExpress users.
one for a mobile employee who downloads a private copy of
data from the database in the morning, works with this data
Managed Providers
during the day, and posts the changes back to the database at
The .NET Framework includes two data providers: the
the end of the day. Another example is an XML Web Service
SQL Server .NET Data Provider, and the OLE DB .NET
that returns an XML dataset according to a client request.
Data Provider. The SQL Server .NET Data Provider allows
optimized access to SQL Server 2000 and SQL Server
In reality, ADO.NET is a marketing term that covers the classes
7.0 databases. The OLE DB .NET Data Provider provides
in the System.Data namespace. The classes of this namespace,
access to SQL Server 6.5 (and earlier), Oracle, Sybase,
mapped to their similar ADO objects, are shown in Figure 1.
DB2, Microsoft Access, and other databases. In addition,

DELPHI INFORMANT MAGAZINE | March 2003

.NET

Te c h

Using the .NET Compiler

{$APPTYPE CONSOLE}
// Using the DataReader class.
program ReaderDemo;
uses
System.Data.SqlClient; // SQL Server .NET Data Provider.
var
Conn : SqlConnection;
Cmd : SqlCommand;
DR
: SqlDataReader;
begin
Conn := SqlConnection.Create(
'server=localhost;database=Northwind;uid=sa;pwd=');
Conn.Open;
Cmd := SqlCommand.Create('SELECT * FROM Employees',Conn);
DR := Cmd.ExecuteReader;
while DR.Read() <> False do begin
Console.WriteLine(DR['FirstName'].ToString + ' ' +
DR['LastName'].ToString);
end;
Conn.Close;
end.

Figure 4: Using the DataReader class.

Microsoft created managed providers for Oracle and ODBC


datasources (these are available as separate downloads
from the Microsoft Web site). Borland is creating a
managed provider for InterBase (in beta as of this writing).
The Connected Scenario
In a connected scenario, resources are held on the server
until the connection is closed. This process usually
consists of the following steps:
1) Open the connection.
2) Execute the command.
3) Process rows in the reader.
4) Close the reader.
5) Close the connection.
As mentioned, to implement this scenario we must use
one of the xxxDataReader classes to create a forward-only
dataset, along with the xxxConnection class to provide
a database connection, and the xxxCommand class to
implement a command to execute against the data store.
These classes are described in Figure 3.
All xxxConnection classes implement the IDbConnection
interface and inherit the same properties and methods. Among
them are: the ConnectionString property, which specifies a
string used to open a connection to a data store; the Database
property, which specifies the name of the current database;
the State property, which returns the current state of the
connection; the BeginTransaction method, which is used to
begin a database transaction; the Open and Close methods,
which are used to open or close the connection; and the
ChangeDatabase method, which is used to change the active
database. In addition, there are methods and properties
specific to the type of data provider property (such as
WorkstationID, which is specific to the SqlConnection class).
All xxxCommand classes implement the IDbCommand
interface. Among properties and methods of these classes
we can find: the CommandText property, which specifies
6

DELPHI INFORMANT MAGAZINE | March 2003

Figure 5: The result of running the code from Figure 4.

the SQL statement or the name of the stored procedure


to be executed in the datastore; the Connection property,
which identifies the xxxConnection object used to execute
the command; the Parameters property, which specifies
the collection of the parameters for this command; the
Cancel method, which cancels the execution of the current
command; the ExecuteNonQuery method, which executes the
queries that dont return datasets; the ExecuteReader method,
which executes a command against the connection and
returns a new DataReader object; the ExecuteScalar method,
which also executes a command against the connection, but
returns only the first column of the first row in the result
set returned by the query; and the Prepare method, which
creates a compiled version of the command in the data store.
All xxxDataReader classes implement the IDataReader
interface. Their most important properties and methods
are: the IsClosed property, which indicates whether the
data reader is closed; the Depth property, which returns the
level of the current row; the Close method, which closes
the DataReader object; the GetSchemaTable method, which
returns a DataTable with the column metadata; the Read
method, which moves the DataReader to the next row;
and the NextResult method, which moves the DataReader
to the next result when reading the results of a batch of
SQL statements. Managed providers also implement some
additional properties and methods specific to them.
Using the DataReader Class
In the examples for this article well use the SQL Server
database. Therefore, the classes involved in our first example
are SqlConnection, SqlCommand, and SqlDataReader. Here
well create a SQL Server connection, open it, create a
SqlDataReader, then create and execute a SqlCommand object,
move through the resulting dataset until it contains data to
read, then close the connection. The code for this example is
shown in Figure 4. The result of running this application is
shown in Figure 5.
Using the xxxCommand Class
As its name implies, the DataReader class is used to read
data extracted from the database. However, if we need to
execute a SQL statement or stored procedure that doesnt
return a dataset, we must use the xxxCommand class. The
example in Figure 6 illustrates this. It creates a connection,
opens it, creates a SqlCommand object containing an
INSERT statement, and executes it. The run-time result is
shown in Figure 7.

.NET

Te c h

Using the .NET Compiler

{$APPTYPE CONSOLE}
// Using the SqlCommand class.
program CommandDemo;
uses
System.Data.SqlClient; // SQL Server .NET Data Provider.
var
Conn : SqlConnection;
Cmd : SqlCommand;
I
: Integer;
begin
Conn := SqlConnection.Create(
'server=localhost;database=Northwind;uid=sa;pwd=');
Conn.Open;
Cmd := SqlCommand.Create('INSERT INTO Customers ' +
'(CustomerID, CompanyName) +
'VALUES (''XYZSS'', ''XYZ SuperShop'')', Conn);
I := Cmd.ExecuteNonQuery;
Console.WriteLine('Rows affected: ' + IntToStr(I));
Conn.Close;
end.

Figure 6: Using the SqlCommand class.

Figure 9: The DataSet class and related objects.

Figure 7: The result of running the code example from Figure 6.

Class

Description

xxxDataAdapter

Uses Connection, Command, and


DataReader classes to populate a dataset,
and to update the central data source
with any changes made to the dataset.
For example, the SqlDataAdapter class is
used to manage the interaction between a
dataset and a SQL Server database.

DataSet

Represents an in-memory cache of data


retrieved from a database.

Figure 8: Classes used in the disconnected scenario.

The Disconnected Scenario


In a disconnected scenario, resources arent held on the
server while the data is processed. This process generally
consists of the following steps:
1) Open the connection.
2) Fill the dataset.
3) Close the connection.
4) Process the dataset.
5) Open the connection.
6) Update the data source.
7) Close the connection.
To implement this scenario, use the xxxConnection class,
the xxxDataAdapter class that contains a set of commands
used to get data from a database, and put it into a dataset
(see Figure 8).
7

DELPHI INFORMANT MAGAZINE | March 2003

All xxxDataAdapter classes implement the IDataAdapter


interface. They have: the TableMappings property, which
indicates how the source tables are mapped to dataset
tables; the Fill method, which adds or refreshes rows
in the dataset to match the data source; the Update
method, which calls the INSERT, UPDATE, or DELETE
SQL statements for each inserted, updated, or deleted
row in the specified dataset; and the SelectCommand,
InsertCommand, UpdateCommand, and DeleteCommand
properties, which specify corresponding SQL statements to
execute against a data source when the Update method is
called. The SelectCommand property is required, but the
InsertCommand, UpdateCommand, and DeleteCommand
can be generated automatically by ADO.NET if we use the
CommandBuilder object for this DataAdapter.
The DataSet class has a Tables property, which gets
a collection of DataTable objects in the dataset, and
a Relations property, which gets a collection of the
DataRelation objects in the dataset. Any DataTable
object has the Rows and Columns properties that gets
the collection of DataRow and DataColumn objects that
represent rows and columns that belong to this table. This
is shown schematically in Figure 9.
Using the DataSet Class
The classes involved in our next example are
SqlConnection, SqlDataAdapter, and DataSet. Here we will
create a DataSet object and a SqlDataAdapter object, then
create and fill a DataTable object in the DataSet object. The
CommandBuilder object is used to fill the InsertCommand,
UpdateCommand, and DeleteCommand properties of the
SqlDataAdapter object. Its DeleteCommand property will
be used later, when we delete a row (that we added in our
previous example) in a disconnected dataset and update the
data store afterwards.

.NET

Te c h

Using the .NET Compiler

{$APPTYPE CONSOLE}
// Using the DataSet class.
program DatasetDemo;
uses
// SQL Server .NET Data Provider.
System.Data.SqlClient, System.Data;
var
DA
DS
DR
CB
DT
CN
I

:
:
:
:
:
:
:

SqlDataAdapter;
DataSet;
DataRow;
SqlCommandBuilder;
DataTable;
SqlConnection;
Integer;

begin
CN := SqlConnection.Create(
'server=localhost;database=Northwind;uid=sa;pwd=;');
CN.Open;
DS := DataSet.Create;
DA := SqlDataAdapter.Create(
'SELECT * FROM Customers', CN);
CB := SqlCommandBuilder.Create(DA);
DA.Fill(DS, 'Customers');
CN.Close;
DT := DS.Tables['Customers'];
if DT.Rows.Count > 91 then
begin
Console.WriteLine('Row to delete: ');
Console.WriteLine(DT.Rows[91].ItemArray[0].ToString);
DT.Rows.Item[91].Delete;
CN.Open;
DA.Update(DS, 'Customers') ;
Console.WriteLine ('After deleting a row: ');
for I := 85 to DT.Rows.Count-1 do begin
DR:=DT.Rows[i];
Console.WriteLine(DR.ItemArray[0].ToString + ' ' +
DR.ItemArray[1].ToString);
end;
CN.Close;
end
else
Console.WriteLine(
'Record to be deleted cannot be found');
end.

Figure 10: Using the DataSet class.

The code for this example is shown in Figure 10, and the
result of executing this application is shown in Figure 11.
Note that by using the SqlDataAdapter.Fill method we dont
need to explicitly open or close the database connection. In
the example above, the Connection object and its Open and
Close methods are used only for illustrative purposes.

DELPHI INFORMANT MAGAZINE | March 2003

Figure 11: The result of running the code from Figure 10.

In this article we covered some basics of ADO.NET and


discussed how to use connected and disconnected scenarios in console applications. However, in more common
implementations, such as Windows or Web-based applications, we usually employ data-aware controls. Therefore,
in the next installment of this series, well turn our attention to data-aware controls. See you then.
The three demonstration projects referenced in this article
are available for download on the Delphi Informant Magazine Complete Works CD located in INFORM\2003\MAR\
DI200303AF.

Alexei Fedorov is a developer and consultant based in Moscow, Russia. During


his 20 years of experience he has worked as a chief technology officer for a Swiss IT
company, has provided technical support for Borland languages, has participated
in development and software localization, and has created many Internet and
intranet sites. Alexei also contributes articles for asp.netPRO magazine, and has
co-authored many books, including Professional ASP 2.0, ASP 2.0 Programmers
Reference, and Advanced Delphi Developers Guide to ADO. Alexeis recent book,
A Programmers Guide to .NET, was published by Addison-Wesley in 2002.
Natalia Elmanova, Ph.D. is a developer and trainer based in Moscow,
Russia, as well as an executive editor for ComputerPress magazine (http:
//www.compress.ru). During the last 15 years she has trained several hundred
Delphi and Visual Basic developers and has been a team leader in several
software projects for various commercial companies, research institutes, and
governmental organizations. Natalia was a contributing author to the 10th,
11th, 12th, and 13th Annual Borland Conferences and has co-authored many
books, including Advanced Delphi Developers Guide to ADO.

G R E A T E R
MICROSOFT MESSAGE QUEUE

COM

D E L P H I

DELPHI 4-6

By Matthew Hess

Microsoft Message Queue


Putting It to Work from Delphi

icrosoft Message Queue (MSMQ) is a


part of Windows that lets you create,
manage, and use message queues. A

message queue is a special folder that contains an


ordered list of items called messages.
Why would you want to use such a thing? A messaging
architecture lets you decouple the various parts of
a larger process so theyre independent. This sort
of asynchronous processing guarantees that certain
processes will be completed and not keep others from
being held up unnecessarily. In other words, MSMQ
allows you to provide increased responsiveness and
guaranteed execution.
Heres a real-life example that illustrates both of these
benefits. Consider a program thats supposed to respond
to updates in Microsoft Outlook by changing a database.
This involves raising a Microsoft Exchange store event
and then updating the database, as shown in Figure 1.
There are a few problems with this approach. First,
the Exchange store event in question (OnSyncSave) is

synchronous and blocking. This means that the users


Outlook session will be held up until the database update
completes. What if the update takes a second? Or 10?
Second, the database may not always be available. What
if its off line? In the first case, the user will be left
hanging. In the second, well be forced to abandon the
update. A messaging architecture, as shown in Figure 2,
solves both problems.
Now the process is separated into two parts. Above the
dashed line is the queue writer, which detects that an update
needs to be performed and calls for the update. Below the
dashed line is the queue reader, which actually performs
the update. When used this way, MSMQ is really a way of
remoting a method call; queue writers can be seen as
function callers and queue readers as function executors.
In this case the queue writer does its work quickly, so the
users Outlook session isnt blocked. And the queue reader
takes as long as it needs to do the update, and can easily
wait for the database to come back on line if its down.
Getting Set Up
Before you can write any MSMQ queue code, you must
first install MSMQ. This is an optional part of Windows,
so grab your Windows 2000 CD and select Start | Settings |
Control Panel | Add Remove Programs | Add

to install
Microsoft Message Queuing Services.
Depending on your environment,
the installation may try to find an
Active Directory domain controller
for managing public queues. For the
examples in this article, however, you
only need support for private queues.
Remove Windows Components

User saves
item in
Outlook

Raise
Exchange
store event

Update
database

Figure 1: A sample problem: update a database based on changes in Outlook. Heres what the initial, synchronous solution looks like.

DELPHI INFORMANT MAGAZINE | March 2003

Greater

Delphi

Microsoft Message Queue

Next you need to import the MSMQ


type library. In Delphi 5, select Project
| Import Type Library and then select the
Microsoft Message Queue 2.0 Object (see
Figure 3). Add the resulting unit,
MSMQ_TLB.pas, to any project that
needs MSMQ.

User saves
item in
Outlook

Read
message
from queue

Last, create two private message queues


to use for testing. Right-click on My
Computer | Manage. Expand the nodes for
Services and Applications | Message Queuing
| Private Queues.

Raise
Exchange
store event

Write
message to
queue
Update
database

Figure 2: A messaging architecture lets us process the update asynchronously, which keeps the Outlook ses-

Right-click and select


sion from being held up, and allows us to provide a guaranteed update of the database.
New queue (see Figure 4). Name the first
queue Test_Queue and dont mark it as transactional. Create a
second queue, name it Test_Queue_Transactional and mark it
as transactional.
Now you have everything set up that you need to begin
writing MSMQ-enabled applications. Well start by creating a simple writer.
A
To

Simple Queue Writer


write a message to a message queue:
create and configure a message,
open a queue using a queue info object, and
call the send method of the message passing in the
queue as a parameter.

The complete code for a simple queue writer is shown in


Figure 5. In this code sample, youll notice that the COM
objects used are constructed using calls to the CoClass
constructors defined in the MSMQ_TLB unit, for example,
CoMSMQQueue.Create. Its important to emphasize that
these are COM objects, not Delphi objects. This means
they dont descend from TObject and you dont (and in
fact cant) call Free on them. COM objects manage their
own lifetimes using internal reference counting. All you
have to do is set your pointer back to nil and let the variable go out of scope, and the object will free itself.
The first property of the message that we set was Label_.
This can be a useful property, since the label is what you
see in the listing of queue messages when you look at the
contents of the queue in the computer management console, as shown in Figure 6.

Figure 3: Importing the MSMQ 2.0 type library.

The next message property we set was Priority. Messages


in the queue are ordered first by priority, and then by
when they were placed in the queue. This means that
a message with higher priority will show up ahead of a
message with lower priority that was posted before it. You
can set priority from 0 to 7; MQ_MAX_PRIORITY = 7 and
is a predefined constant in the MSMQ type library. If you
use priorities other than 0 and 7, you may want to create
your own constants for the intermediate levels.
The last message property we set was Body. When you
think of MSMQ as a way to remote a method call, you
see that the body of the message contains the parameters
of the call. As a result, this property deserves some special attention.
10

DELPHI INFORMANT MAGAZINE | March 2003

Figure 4: Creating a private message queue.

Greater

Delphi

Microsoft Message Queue

procedure WriteToQueue(const sLabel, sBody: string);


var
pQ: MSMQQueue;
pQInfo: MSMQQueueInfo;
pQMsg: MSMQMessage;
begin
try
// Create the COM objects.
pQ := CoMSMQQueue.Create;
pQInfo := CoMSMQQueueInfo.Create;
pQMsg := CoMSMQMessage.Create;
// Prepare the message.
with pQMsg do begin
Label_ := sLabel;
Priority := MQ_MAX_PRIORITY;
Body := sBody;
end;
// Open the queue.
pQInfo.PathName := '.\Private$\Test_Queue';
pQ := pQInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE);
// Send the message.
pQMsg.Send(pQ, EmptyParam);
pQ.Close;
finally
if pQ <> nil then
begin
if (pQ.IsOpen <> 0) then pQ.Close;
pQ := nil;
end;
pQMsg := nil;
pQInfo := nil;
end;
end;

Figure 5: A simple queue writer.

Creating the Message Body


There are three approaches we can use when setting the
body of an MSMQ message:
use a string
use a byte array
use a COM object
The first approach, using a string, is simple and, for most
situations, completely sufficient. One of the nice things
about using a string for the body is that if you have to, you
can look at the Body tab of the message properties and actually read the body (see Figure 7).
Its easy enough to convert most non-string values to a
string representation, and then concatenate them into a
single delimited string to use as your message body. In
this case, packing and unpacking the body is simply a

Figure 6: Messages in a private queue.

11

DELPHI INFORMANT MAGAZINE | March 2003

Figure 7: The string body of a message.

matter of concatenating and parsing a string. For example,


if you wanted to pass three parameters a string, an
integer, and a Boolean you could pack them into a
pipe-delimited string like this:
sBody := sString + '|' + IntToStr(iInt) + '|' +
IntToStr(Ord(lBool));

The second approach, using a byte array, lets you get a bit
fancy. First you need to define the parameters of your method call as a record:
TBodyRec = record
StrParam: string[50];
IntParam: Integer;
BoolParam: Boolean;
end;

// Must use short string type.

Then, you can pack and unpack the record into a


byte array using functions like those shown in Figure 8.
Its important to note that the variables you use in the
record you want to pack cannot be heap-allocated, pointer-type variables, e.g. WideString.
If you try to do that, youll get
mysterious, (and usually fatal)
memory errors (thanks to Binh Ly of
www.techvanguards.com for pointing
this out).
The last approach, using a persistable
COM object, presents some interesting
possibilities. In his book Programming
Distributed Applications with COM+
and Visual Basic 6.0, Ted Pattison
explains that, MSMQ can store objects
in a message body only if they imple-

Greater

Delphi

Microsoft Message Queue

function PackBody(rBody: TBodyRec): OleVariant;


var
pData: Pointer;
begin
Result := VarArrayCreate([0, SizeOf(rBody)], VarByte);
pData := VarArrayLock(Result);
Move(rBody, pData^, SizeOf(rBody));
VarArrayUnlock(Result);
end;
function UnpackBody(vBody: OleVariant): TBodyRec;
var
pData: Pointer;
begin
pData := VarArrayLock(vBody);
Move(pData^, Result, VarArrayHighBound(vBody, 1));
VarArrayUnlock(vBody);
end;

Figure 8: Creating the body of an MSMQ message using a byte array.

ment one of two standard COM interfaces: IPersistStream or


IPersistStorage. (Pattison, p. 437) So, for example, you can
use a disconnected ADO record set as the body of an MSMQ
message. In theory, you could hand-roll your own COM
object to serve as the message body. Still and all, I have
found that the first approach, using a delimited string, does
the trick in most cases.
A Simple Queue Reader
Now that weve written a message to our queue, we need
to get it out. The simple queue reader looks a lot like the
simple queue writer (see Figure 9), except that we call
the ReceiveCurrent method of the queue object to retrieve
the first message in the queue.
ReceiveCurrent gets you the first message in the queue
and removes that message from the queue. This is a really
important point remember that in this context, read
means read and remove. Once youve read a message,
its gone.
When you call ReceiveCurrent, you can specify a TimeOut.
If the queue has a message, ReceiveCurrent returns
immediately with the message. If the queue is empty,
ReceiveCurrent waits for TimeOut milliseconds, and then
returns. A TimeOut of 0 means dont wait. A TimeOut of
-1 (the default) means wait forever. This is an easy way
to write a queue reader thats always ready and listening,
though it does have the drawback of blocking the thread
forever as well. If you choose to use a simple reader with
a TimeOut greater than 0, you can test for a successful
receive by seeing if your message is nil.
Last, I want to point out that I wrapped the call to
pQMsg.Body in the VarToStr function. Because the
message body is a variant, its possible that it will be
Unassigned, Null, or some other
variant type that wont convert to a
string. Using VarToStr allows us to
Read
avoid variant conversion errors.
Transactional Queues
With a transactional queue, the acts
of writing to, or reading from, a
12

DELPHI INFORMANT MAGAZINE | March 2003

message
from queue

procedure ReadFromQueue(var sLabel, sBody: string);


var
pQ: MSMQQueue;
pQInfo: MSMQQueueInfo;
pQMsg: MSMQMessage;
vTimeOut: OleVariant;
begin
try
// Initialize COM objects.
pQ := CoMSMQQueue.Create;
pQInfo := CoMSMQQueueInfo.Create;
vTimeOut := 0; // Don't wait if there's no message.
// Open the queue.
pQInfo.PathName := '.\Private$\Test_Queue';
pQ := pQInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE);
// Receive the first message in the queue.
pQMsg := pQ.ReceiveCurrent(EmptyParam, EmptyParam,
EmptyParam, vTimeOut, EmptyParam);
if pQMsg <> nil then // Make sure we have a message.
begin
sLabel := pQMsg.Label_;
// Careful with variants.
sBody := VarToStr(pQMsg.Body);
end;
finally
// etc.
end;
end;

Figure 9: A simple queue reader.

queue are embedded in a native, MSMQ transaction. This


allows us to commit or abort the operation. In the context
of a write, abort simply means that the message is not
written to the queue. With a read, abort means that the
item is returned to the queue in its previous position.
To see how useful this can be, consider the situation
depicted in Figure 10.
If the database has gone off line, were going to need to
try the update again later. This means were not really
done with the message. Without a transaction, were
left with the messy task of writing the message back
either to the original queue or to a retry-queue. There
are many problems with this. The write-back could fail,
which means the message is lost. Even if the writeback succeeds, the ordering will have changed since the
message will now have a new delivery date.
And, MSMQ isnt going to let us have the same queue
open for read and write at the same time. This means that
if we want to reuse the same queue well have to cache
the message body somewhere, close the queue for reading,
re-open it for writing, and put the message back. What a
mess! But with a transaction, we simply say Abort and the
message goes right back where it came from.
Figure 11 shows some code that makes our simple queue
reader transactional. For this example, Ive used the

Update
database

Figure 10: The case for using transactions: what if the database is off line?

Database
off line
now what?

Greater

Delphi

Microsoft Message Queue

procedure ReadFromTransactionalQueue;
var
pQ: MSMQQueue;
pQInfo: MSMQQueueInfo;
pQMsg: MSMQMessage; vTimeOut: OleVariant;
pQTransDispenser: MSMQTransactionDispenser;
pQTrans: MSMQTransaction;
vQTrans: OLEVariant;
begin
try
// Initialize COM objects.
pQ := CoMSMQQueue.Create;
pQInfo := CoMSMQQueueInfo.Create;
pQTransDispenser := CoMSMQTransactionDispenser.Create;
pQTrans := CoMSMQTransaction.Create;
vTimeOut := 0;
// Start a transaction.
pQTrans := pQTransDispenser.BeginTransaction;
vQTrans := pQTrans;
// Open the queue.
pQInfo.PathName :=
'.\Private$\Test_Queue_Transactional';
pQ := pQInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE);
// Receive the first message in the queue.
pQMsg := pQ.ReceiveCurrent(vQTrans, EmptyParam,
EmptyParam, vTimeOut, EmptyParam);
// Commit or abort as needed.
if pQMsg <> nil then
if UpdateDatabase(VarToStr(pQMsg.Body)) then
// Remove from queue.
pqTrans.Commit(EmptyParam, EmptyParam, EmptyParam)
else
// Leave in queue.
pQTrans.Abort(EmptyParam, EmptyParam);
finally
vQTrans := Unassigned;
pQTrans := nil;
pQTransDispenser := nil;
// etc.
end;
end;

Figure 11: Updating a queue using transactions.

fantasy function UpdateDatabase to represent the process


of doing the SQL update.
There are a few things to keep in mind. If you want to
use transactional logic in your queues, you must mark the
queue as transactional when you create it, and you must
both write and read messages within a transaction no
exceptions. A transactional writer is much like the reader;
we create a transaction dispenser, start a transaction, use
it in the call to Send, and then commit:
pQTrans := pQTransDispenser.BeginTransaction;
vQTrans := pQTrans;
pQMsg.Send(pQ, vQTrans);
pQTrans.Commit(EmptyParam, EmptyParam, EmptyParam);

Also, if you choose to use a retry-scheme with multiple


queues, youll still want to use transactions. By including
the write to the retry queue inside the same transaction
as the receive from the original queue, you can make
sure that the message isnt removed from the first queue,
unless it was successfully written to the second one.
A Peek-and-scroll Queue Reader
The transactional queue reader we just looked at
implements a traditional, first-in-first-out (FIFO) way of
13

DELPHI INFORMANT MAGAZINE | March 2003

handling a queue. There may be times, however when we


want to skip a message and move on to the next one in line.
Perhaps we want to scan the queue of messages looking
for one that meets a certain criteria. Perhaps if a message
(which, again, is really a remoted method call) fails, we
want to jump over it and give the next one a try. The way to
do this is with a peek-and-scroll queue reader.
MSMQ provides a series of peek methods that let you
look at a message without removing it from the queue. In
addition, an MSMQ queue has an implicit cursor, which
allows you to traverse the messages by using special receive
and peek methods such as PeekCurrent and PeekNext. In
my experience, this cursor can be a little tricky to use; what
happens depends upon in which sequence you call the
various functions.
Here are two sequences that work. First, theres a pattern
showing a peek followed by a receive:
PeekCurrent (message 1)
ReceiveCurrent (message 1)
PeekCurrent (message 2)
Second is a pattern showing a peek followed by another peek:
PeekCurrent (message 1)
PeekNext (message 2)
If you combine these two patterns with transactions, you
can make a powerful reader that can selectively receive
messages and can put messages back when it needs to. In
this example, I use another fantasy function, DoWeCare, to
represent the process of looking at the body and seeing if
this is a message we want to receive and process. The code
is shown in Listing One (on page 14).
This kind of peek-and-scroll reader can be very helpful
in dealing with the poison message syndrome. The
poison message syndrome is where one bad message in
a traditional FIFO queue blocks all subsequent messages
from ever being processed. The normal solution to this is
to remove the bad message from the queue and place it in
a retry queue. Sometimes this is implemented as a whole
cascading series of retry queues ending in a dead letter
queue (such as with COM+ QC). With peek-and-scroll,
however, you can simply leave the bad message in place,
scroll to the next one, and come back to the failed message
later. Obviously, you dont want to clutter your queue with
an endlessly growing list of undeliverable messages, so
remove the ones that you know will never be deliverable,
and leave the ones you want to return to and try again.
An Events-based Queue Reader
So far, with all of our queue readers, weve had to
explicitly open a queue and look for messages. Practically
speaking, this means well have to set up a polling
architecture where some timer-based program comes by
and checks the queues periodically. Wouldnt it be nice if
the queue reader could automatically process each message
as it arrives? True, we could set a timeout of -1, which
means the call to receive or peek would wait forever, but
theres a better way to configure a queue for automatic
receiving of messages: MSMQ Events.

Greater

Delphi

Microsoft Message Queue

The idea behind MSMQ Events is pretty straightforward.


After opening a queue for read access, you call the
EnableNotification method and pass in a pointer to an
event sink. This works like a callback. Whenever a
message enters the queue, the event is fired and your
reader code starts running. To implement an events-based
queue reader, well make use of a true Delphi TObject
descendant, TMSMQEvent, that gets defined when the
MSMQ_TLB library is imported. Since this technique
requires scooping some unit level variables, and since an
events-based reader really needs to be its own class, the
entire unit is shown in Listing Two (on page 15).
Its worth mentioning that an events-based queue reader
commits us to true FIFO processing; we cant really use peekand-scroll inside an events-based reader. Instead, we need to
process the messages as they come, and move them to retry
queues if they fail. Of course, nothing prevents us from using
a polling-based peek-and-scroll reader for the retries.
Conclusion
We started with a simple problem of making a
synchronous process asynchronous using an MSMQbased messaging architecture. Now we see that MSMQ
actually gives us several options from simple, pollingbased readers, to sophisticated, transactional, eventsbased readers. Of course, this is hardly the full extent
of what MSMQ can do. I recommend that you visit the
MSDN platform SDK documentation for MSMQ. There
you can read about the many other MSMQ techniques
such as using a MSMQQuery object to discover remote
queues, using MSDTC transactions instead of local MSMQ
transactions, and configuring messages to send delivery
receipts back to the sender.
In addition, you may want to look at some of the other
MSMQ resources that are out there. In his June 2002
Delphi Informant review, Tom Lisjac says that Borland
Delphi 6 Developers Guide by Steve Teixeira and Xavier
Pacheco has an excellent section on MSMQ. Binh
Lys www.techvanguards.com presents an alternative
approach to handling MSMQ events. And Programming
Distributed Applications with COM+ and Visual Basic
6.0 by Ted Pattison provides excellent coverage of
COM+ and distributed, RPC-based technologies such as
MSMQ. Hopefully this article will have given you a good
foundation for exploring these concepts, and will make
extending your knowledge of MSMQ a productive and
enjoyable process.
The code referenced in this article are available for download on the Delphi Informant Magazine Complete Works
CD located in INFORM\2003\MAR\DI200303MH.

Matthew Hess is a Delphi developer working for ProLaw Software, the


legal industrys leading practice management provider. At ProLaw, Matthew
specializes in COM programming and cross-product integrations. Readers may
contact him at matthew@mlhess.com.

14

DELPHI INFORMANT MAGAZINE | March 2003

Begin Listing One Peek-and-scroll


Reader
procedure PeekAndScroll;
var
pQ: MSMQQueue;
pQInfo: MSMQQueueInfo;
pQMsg: MSMQMessage;
pQTransDispenser: MSMQTransactionDispenser;
pQTrans: MSMQTransaction;
vQTrans: OLEVariant;
vTimeOut: OleVariant;
lDoWeCare: Boolean;
begin
try
// Initialize COM objects.
pQInfo := CoMSMQQueueInfo.Create;
pQ := CoMSMQQueue.Create;
pQTransDispenser := CoMSMQTransactionDispenser.Create;
// Open the queue.
pQInfo.PathName :=
'.\Private$\Test_Queue_Transactional';
pQ := pQInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE);
// Peek to see if we have a message.
vTimeOut := 0;
pQMsg := pQ.PeekCurrent(EmptyParam, EmptyParam,
vTimeOut, EmptyParam);
// If we do, enter the main loop.
while pQMsg <> nil do begin
// See if we care about this message.
lDoWeCare := DoWeCare(VarToStr(pQMsg.Body));
if lDoWeCare then
try
// Start a transaction.
pQTrans := CoMSMQTransaction.Create;
pQTrans := pQTransDispenser.BeginTransaction;
vQTrans := pQTrans;
// Recieve the message.
vTimeOut := -1;
pQMsg := pQ.ReceiveCurrent(vQTrans, EmptyParam,
EmptyParam, vTimeOut, EmptyParam);
// Commit or abort as needed.
if UpdateDatabase(VarToStr(pQMsg.Body)) then
pQTrans.Commit(EmptyParam, EmptyParam,
EmptyParam)
else
pQTrans.Abort(EmptyParam, EmptyParam);
finally
// Clear the transaction pointers between
// each message.
vQTrans := Unassigned;
pQTrans := nil;
end;
// Look for the next message.
vTimeOut := 0;
if lDoWeCare then
pQMsg := pQ.PeekCurrent(EmptyParam, EmptyParam,
vTimeOut, EmptyParam)
else
pQMsg := pQ.PeekNext(EmptyParam, EmptyParam,
vTimeOut, EmptyParam);
end;
finally
// etc.
end;
end;

End Listing One

Greater

Delphi

Microsoft Message Queue

Begin Listing Two Event-based


Message Queue Reader Class
unit EventBasedMSMQReader;
interface
uses
ComObj, ActiveX, StdVcl, MSMQ_TLB;
type
TEventBasedMSMQReader = class(TObject)
public
procedure Start;
procedure Arrived(Sender: TObject;
var Queue: OleVariant; Cursor: Integer);
procedure ArrivedErr(Sender: TObject;
var Queue: OleVariant; ErrorCode: Integer;
Cursor: Integer);
end;
var
FQueue: MSMQQueue;
FEvent: TMSMQEvent;

// Defined in MSMQ_TLB.pas.

implementation
uses
ComServ, SysUtils;
procedure TEventBasedMSMQReader.Start;
// This function starts the queue reader. Somewhere in your
// application you need a procedure that creates a
// TEventBasedMSMQReader and calls Start. This should only
// happen once.
var
pQInfo: MSMQQueueInfo;
vTimeOut: OleVariant;
begin
if Assigned(oFEvent) then
Exit; // Don't re-register the event sink.
pQInfo := CoMSMQQueueInfo.Create;
FQueue := CoMSMQQueue.Create;
FEvent := TMSMQEvent.Create(nil);
FEvent.OnArrived := Arrived;
FEvent.OnArrivedError := ArrivedErr;
pQInfo.PathName := '.\Private$\Test_Queue_Transactional';
vTimeOut := -1;
FQueue := pQInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE);
FQueue.EnableNotification(

15

DELPHI INFORMANT MAGAZINE | March 2003

FEvent.DefaultInterface, EmptyParam, vTimeOut);


end;
procedure TEventBasedMSMQReader.Arrived(Sender: TObject;
var Queue: OleVariant; Cursor: Integer);
// This event fires when a message enters the queue. It
// allows you to receive the message. I've used a
// transaction for the receive. Note that when you're done
// with the message, you must re-enable notification in
// order to get the next message when it comes.
var
vQMsg: OleVariant;
pQTransDispenser: MSMQTransactionDispenser;
pQTrans: MSMQTransaction;
vQTrans: OLEVariant;
vTimeOut: OleVariant;
begin
try
pQTransDispenser := CoMSMQTransactionDispenser.Create;
pQTrans := CoMSMQTransaction.Create;
vTimeOut := 0;
// Start a transaction.
pQTrans := pQTransDispenser.BeginTransaction;
vQTrans := pQTrans;
// Receive the message.
vQMsg := Queue.Receive(vQTrans, EmptyParam, EmptyParam,
vTimeOut, EmptyParam);
pQTrans.Commit(EmptyParam, EmptyParam, EmptyParam);
finally
vQMsg := Unassigned;
vQTrans := Unassigned;
pQTrans := nil;
pQTransDispenser := nil;
// Very important: Restart event notification
// on this queue.
vTimeOut := -1;
Queue.EnableNotification(FEvent.DefaultInterface,
EmptyParam, vTimeOut);
end;
end;
procedure TEventBasedMSMQReader.ArrivedErr(Sender: TObject;
var Queue: OleVariant; ErrorCode, Cursor: Integer);
begin
// Your error handling code goes here.
end;
end.

End Listing Two

C O L U M N S
BORLAND DATABASE ENGINE

&

BATCHMOVE

R O W S
DELPHI 2-7

By Bill Todd

Replacing BatchMove
Use These Classes, Now That the BDE Is Gone

orland has discontinued the Borland Database


Engine. The BDE SQL Links drivers have been
deprecated, and they ceased shipment at the

end of 2002 (see http://community.borland.com/

article/0,1410,28688,00.html for details). As a result,


much has been written about converting BDE
applications to other data access components, and
other databases. However, none of the other dataset
components that come with Delphi have a component
that provides the functions of TBatchMove.
The three main reasons that developers use BatchMove are:
to copy data from one table to another, import data from
an ASCII file, and export data to an ASCII file. This article
describes some simple components that will perform these
functions. To minimize changes to existing code, the property and method names are the same as the corresponding
properties and methods of the BatchMove component.
type
TDataCopy = class(TComponent)
private
FSource: TDataSet;
FDestination: TDataSet;
FMappings: TStringList;
FRecordCount: Integer;
FRecordsCopied: Integer;
protected
procedure CopyAllColumns;
procedure CopyMappedColumns;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Execute;
published
property Source: TDataSet read FSource write FSource;
property Destination: TDataSet
read FDestination write FDestination;
property Mappings: TStringList read FMappings;
property RecordCount: Integer
read FRecordCount write FRecordCount;
property RecordsCopied: Integer read FRecordsCopied;
end;

Figure 1: Type declaration for the TDataCopy class.

16

DELPHI INFORMANT MAGAZINE | March 2003

Copying Data Between Tables


Figure 1 shows the type declaration for the TDataCopy
class. This class, like TBatchMove, has Source, Destination,
Mappings, and RecordCount properties. It also has a readonly RecordsCopied property that contains the number of
rows copied after calling the Execute method. The Source
and Destination properties are of the TDataSet type. You can
use the data access components of your choice for Source
and Destination, because most Borland and third-party table
access components are descendants of TDataSet.
The Mappings property works like the TBatchMove.Mappings
property. Mappings is a StringList, and each string has the
form DestinationFieldName=SourceFieldName. If Mappings is
empty, fields are copied from Source to Destination based on
position. The value of the first field from Source is copied to
the first field in Destination, and so on, through the last field in
Source. If there are entries in Mappings, the fields will be copied by name, and only the fields in Mappings will be copied.
Note that the DataCopy component doesnt have a Mode
property. This components only mode is equivalent to
TBatchMoves batAppend. The destination table must exist
and the records from the source dataset will be appended to
it. If you need to create the destination table, you must do
so in your code before using this component.
Figure 2 shows the overridden constructor and destructor. The
only reason the constructor is overridden is to create the StringList used for the Mappings property. The destructor is overridden to automatically free the StringList used for Mappings.
Figure 3 shows the Execute method, which raises an exception
if either the Source or Destination properties are not assigned.
Next, it makes sure that both Source and Destination are
open and that Source is positioned to the first record. Finally,
Execute calls CopyAllColumns if the Mappings property is
empty, and CopyMappedColumns if it isnt.
CopyAllColumns, shown in Figure 4, checks to see if the
RecordCount property is greater than zero and, if it is, exits
when the number of records copied equals RecordCount.
The next line appends a new row to the Destination dataset.
The for loop uses the Assign method of TField to assign
the value from the source field to the destination field. This

Columns

&

Rows

Replacing BatchMove

constructor TDataCopy.Create(AOwner: TComponent);


begin
inherited;
FMappings := TStringList.Create;
end;
destructor TDataCopy.Destroy;
begin
FMappings.Free;
inherited;
end;

Figure 2: The overridden constructor and destructor.

procedure TDataCopy.Execute;
begin
// Ensure Source and Destination have been assigned.
if (not Assigned(FSource)) then
raise Exception.Create('Source dataset not assigned.');
if (not Assigned(FDestination)) then
raise Exception.Create(
'Destination dataset not assigned.');
// Make sure the Source and Destination are open.
if (not FSource.Active) then
FSource.Open;
if (not FDestination.Active) then
FDestination.Open;
// Start with the first record in Source.
FSource.First;
// If Mappings property is empty, copy all columns from
// Source to Destination; otherwise, copy only the
// mapped columns.
if (FMappings.Count = 0) then
CopyAllColumns
else
CopyMappedColumns;
end;

Figure 3: The Execute method.

works with all compatible field types, including BLOBs.


After copying the field values, CopyAllColumns posts the
destination row, increments the record counter, and moves
to the nest row in the source dataset.
The CopyMappedColumns method, shown in Figure 5, is
almost identical to CopyAllColumns, except for the for loop. In
this method the for loop executes once for each string in the
Mappings StringList. Copying the values from the source field
to the destination field is done using the FieldByName method,
with the field name coming from the Mappings entry.
When working with a StringList whose strings are in the form
Name=Value, the Names property returns the string on the
left side of the equals sign. The Names property is indexed by
an integer that gives the position of the string in the StringList.
The Values property returns the value on the right side of the
equals sign, and is indexed by a string that specifies the name
for that value. The complete listing for the CopyData component is shown in Listing One (beginning on page 21).
Exporting Data to Delimited ASCII
Figure 6 shows the class declaration for the TDelimitedExport
component. The Source property is the dataset whose data
you want to export. Use a query if you want to export selected
rows and/or columns. FileName contains the path to the ASCII
file that will be created. Delimiter contains the character that
17

DELPHI INFORMANT MAGAZINE | March 2003

procedure TDataCopy.CopyAllColumns;
// Copies all columns based on the column count of the
// Source dataset. Source and Destination must have
// compatible columns in the same order.
var
I:
Integer;
Count: Integer;
begin
Count := 0;
while (not FSource.EOF) do begin
// If the RecordCount property is > zero and the number
// of records copied equals the RecordCount property
// do not copy any more records.
if (FRecordCount > 0) and (Count >= FRecordCount) then
Exit;
// Copy the record to the destination dataset.
FDestination.Append;
for I := 0 to FSource.FieldCount - 1 do
FDestination.Fields[I].Assign(FSource.Fields[I]);
FDestination.Post;
// Increment the count and move to the next record in
// the source dataset.
Inc(Count);
FSource.Next;
end;
FRecordsCopied := Count;
end;

Figure 4: The CopyAllColumns method.

procedure TDataCopy.CopyMappedColumns;
// Copies columns based on the column names in the Mappings
// property. Mappings contains one entry for each column to
// be copied in the form DestColumnName=SourceColumnName.
// Values are copied by column name, not column order.
var
I:
Integer;
Count: Integer;
begin
Count := 0;
while (not FSource.EOF) do begin
// If the RecordCount property is > zero and the number
// of records copied equals the RecordCount property
// do not copy any more records.
if (FRecordCount > 0) and (Count >= FRecordCount) then
Exit;
FDestination.Append;
for I := 0 to FMappings.Count - 1 do
FDestination.FieldByName(Mappings.Names[I]).Assign(
FSource.FieldByName(Mappings.Values[
Mappings.Names[I]]));
FDestination.Post;
Inc(Count);
FSource.Next;
end;
FRecordsCopied := Count;
end;

Figure 5: The CopyMappedColumns method.

will be placed between fields. QuoteChar contains the character


that will surround strings. The constructor is overridden to
assign default values to Delimiter and QuoteChar.
Figure 7 shows the Execute method where all the work is
done. This method starts by ensuring that the FileName
property has a value, and that the source dataset is open
and positioned to the first record. The next block of code
opens the ASCII file and saves the index of the last field in
the source dataset in the variable LastField.

Columns

&

Rows

Replacing BatchMove

type
TDelimitedExport = class(TComponent)
private
FSource: TDataSet;
FFileName: string;
FDelimiter: Char;
FQuoteChar: Char;
public
constructor Create(AOwner: TComponent); override;
procedure Execute;
published
property Delimiter: Char
read FDelimiter write FDelimiter;
property FileName: string
read FFileName write FFileName;
property QuoteChar: Char
read FQuoteChar write FQuoteChar;
property Source: TDataSet read FSource write FSource;
end;

Figure 6: Class declaration for TDelimitedExport.

procedure TDelimitedExport.Execute;
var
AsciiFile:
TextFile;
I, LastField: Integer;
begin
// Make sure the user specified a file name.
if FileName = '' then
raise Exception.Create('FileName is blank.');
// Make sure the DataSet property is set, the dataset is
// open, and the current record is the first record.
if Source = nil then
raise Exception.Create(
'Source dataset is not assigned.');
if (not Source.Active) then
Source.Open;
Source.First;
// Open the delimited file.
AssignFile(AsciiFile, FileName);
Rewrite(AsciiFile);
LastField := Source.FieldCount - 1;
while (not Source.EOF) do begin
for I := 0 to LastField do begin
// If the field is a BLOB skip it.
if Source.Fields[I].DataType in [ftBlob, ftGraphic,
ftMemo, ftOraBlob, ftOraClob, ftADT,
ftInterface, ftIDispatch, ftArray, ftReference,
ftParadoxOle, ftDbaseOle, ftFmtMemo] then
Continue;
// If this isn't first field, write Delimiter.
if (I > 0) then
Write(AsciiFile, Delimiter);
// If field isn't numeric, write opening quote
// character.
if (Source.Fields[I].DataType in [ftString,
ftFixedChar, ftWideString]) then
Write(AsciiFile, QuoteChar);
// Write the field value.
Write(AsciiFile, Source.Fields[I].AsString);
// If field type isn't numeric, write closing
// delimiter character.
if (Source.Fields[I].DataType in [ftString,
ftFixedChar, ftWideString]) then
Write(AsciiFile, QuoteChar);
end;
// Write carriage return/line feed at end of record.
Writeln(AsciiFile, '');
Source.Next;
end;
CloseFile(AsciiFile);
end;

Figure 7: The TExportDelimited.Execute method.

18

DELPHI INFORMANT MAGAZINE | March 2003

type
TDelimitedImport = class(TComponent)
private
FFieldList: TStringList;
FFileName: string;
FImportFile: TextFile;
FMappings: TStringList;
FDestination: TDataSet;
procedure SetDelimiter(const Value: Char);
procedure SetQuoteChar(const Value: Char);
function GetDelimiter: Char;
function GetQuoteChar: Char;
protected
procedure AddRecordToDataSet;
function GetNextRecord: Boolean;
procedure OpenTextFile;
procedure CloseTextFile;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Execute;
published
property Destination: TDataSet
read FDestination write FDestination;
property Mappings: TStringList read FMappings;
property FileName: string
read FFileName write FFileName;
property Delimiter: Char
read GetDelimiter write SetDelimiter;
property QuoteChar: Char
read GetQuoteChar write SetQuoteChar;
end;

Figure 8: Class declaration for TDelimitedImport.

The while loop exports all the records in the source dataset.
The for loop iterates through all the field objects in the source
record and exports each one. The first if statement skips any
field types, such as BLOBs, that cannot be exported to ASCII.
The second if statement writes the delimiter character if at
least one field value has been written to the ASCII file. The
third if statement writes the open quote character if the field
is a string. Next, the field value is written, then another if
statement writes the closing quote character if the field is a
string. Finally, the carriage return/line feed that terminates the
record is written to the file, and the source dataset is positioned
to the next record. The complete code for the DelimitedExport
component appears in Listing Two (beginning on page 22).
Importing Delimited Data
The delimited import components class declaration is shown
in Figure 8. The Destination property is the destination dataset.
The FileName property is the path to the delimited text file.
The Delimiter and QuoteChar properties are identical to those
in the DelimitedExport component. The Mappings property
determines which field in the delimited text file is placed in
which field of the Destination dataset and contains entries in
the form DestinationFieldName=DelimitedFileFieldNumber.
DelimitedFileFieldNumber is the zero-based sequential number
of the fields in the delimited text file.
The overridden constructor creates the FFieldList and
FMappings StringLists, and assigns the default values to
the Delimiter and QuoteChar properties. Note that in this
component the Delimiter and QuoteChar properties have
getter and setter methods that store the values of these

Columns

&

Rows

Replacing BatchMove

properties in the Delimiter and QuoteChar properties of the


FFieldList StringList.
The DelimitedImport components Execute method, shown in
Figure 9, raises an exception if the Mappings property hasnt
been set. Next, it calls the components OpenTextFile method,
which makes sure the FileName property has a value, then
opens the file. The while loop executes if the GetNextRecord
method returns True. The loop calls the AddRecordToDataSet
method, which appends the record in the FFieldList StringList
to the Destination dataset.
Figure 10 shows the GetNextRecord method. GetNextRecord
begins by checking for the end of a file on the text
file, and returns False if the end of the file has been
reached. Next, it reads a record from the text file into a
string variable. It then clears the FFieldList StringList,
and assigns the record to the StringLists DelimitedText
property. The StringList automatically parses the string
assigned to its DelimitedText property using the characters
assigned to its Delimiter and QuoteChar properties, and
places the contents of each field in a separate string in
the StringList.
Figure 11 shows the AddRecordToDataSet method. This
method starts by appending a new row to the destination
dataset. The method then loops through all the strings
in the FFieldList StringList. The name of the field in
the destination dataset is saved in FldName, and the
number of the field in the text file is converted to an
integer and saved in ImportFldNum. Next, the value of
the field identified by ImportFldNum is extracted from the
FFieldList StringList, and is assigned to the field in the
dataset. The new row is posted after all the field values
have been assigned to the new row in the dataset. This
works because the AsString property of the field object
automatically converts the string assigned to it to the
correct data type for the field in the dataset.
Exporting Data to Fixed Length ASCII
The FixedExport component is similar to the DelimitedExport
component. The first difference is that it doesnt have the
Delimiter or QuoteChar properties. The second difference is the
Execute method (see Figure 12). Execute begins by checking
that the FileName and Source properties have values. Next, it
opens the Source dataset, if necessary, and positions it to the
first record. The next block of code opens the ASCII file and
saves the index number of the last field in the Source dataset.
The while loop iterates through the Source dataset and
exports each record. The Mappings property has an entry for
each field in the form: SourceFieldName=AsciiFieldLength.
The for loop iterates through each string in the FMappings
StringList. The field name is extracted from the Names
property, and the field length is extracted from the Values
property of the FMappings StringList. The if statement skips
field types that cannot be exported to ASCII.
The code saves the value from the field as a string and checks
its length. If the value is longer than the length specified in
the Mappings property, the method raises an exception. If
the value is shorter than the specified length, it is padded on
19

DELPHI INFORMANT MAGAZINE | March 2003

procedure TDelimitedImport.Execute;
begin
if (FMappings.Count = 0) then
raise Exception.Create('No field mappings specified.');
OpenTextFile;
while GetNextRecord do
AddRecordToDataSet;
CloseTextFile;
end;

Figure 9: The TDelimitedExport.Execute method.

function TDelimitedImport.GetNextRecord: Boolean;


// Read record from text file and parse it into FFieldList
// StringList. Return False when end-of-file is reached.
var
S: string;
begin
Result := True;
if (EOF(FImportFile)) then begin
Result := False;
Exit;
end;
Readln(FImportFile, S);
// Parse record and put each field in separate string
// in the FieldList StringList.
FFieldList.Clear;
FFieldList.DelimitedText := S;
end;

Figure 10: The TDelimitedImport.GetNextRecord method.

procedure TDelimitedImport.AddRecordToDataSet;
// Add record in FFieldList StringList to Destination
// dataset using information in FMappings StringList. Each
// entry in FMappings has the form DestFieldName=9
// where 9 is zero-based number of field in file.
var
I, ImportFldNum: Integer;
FldName:
string;
begin
with Destination do begin
Append;
for I := 0 to FMappings.Count - 1 do begin
FldName := Mappings.Names[I];
ImportFldNum := StrToInt(Mappings.Values[FldName]);
FieldByName(FldName).AsString :=
FFieldList[ImportFldNum];
end;
Post;
end;
end;

Figure 11: The TDelimitedImport.AddRecordToDataSet method.

the right with spaces. Its then written to the file. After all
the fields in the current record have been written, the call
to Writeln writes a carriage return/line feed to terminate the
record. The complete listing for this component is shown in
Listing Three (beginning on page 23).
Importing Fixed Length ASCII
The TFixedImport component also resembles its delimited
cousin, except that it lacks the Delimiter and QuoteChar
properties. The Execute method is also different in that it
opens the ASCII file and calls the ImportData method (see
Figure 13), which does all of the work. This component
works with fixed-length text files only. Each record in the
ASCII file must end with a carriage return/line feed.

Columns

&

Rows

Replacing BatchMove

procedure TFixedExport.Execute;
var
AsciiFile:
TextFile;
I, LastField: Integer;
FldName:
string;
FldSize:
Integer;
FldValue:
string;
begin
// Make sure the user specified a file name.
if FileName = '' then
raise Exception.Create('FileName is blank.');
// Make sure DataSet property is set, dataset is open,
// and the current record is the first record.
if Source = nil then
raise Exception.Create(
'Source dataset is not assigned.');
if (not Source.Active) then
Source.Open;
Source.First;
// Open the ASCII file.
AssignFile(AsciiFile, FileName);
Rewrite(AsciiFile);
LastField := Source.FieldCount - 1;
while (not Source.EOF) do begin
for I := 0 to FMappings.Count - 1 do begin
FldName := FMappings.Names[I];
FldSize := StrToInt(FMappings.Values[FldName]);
// If the field is a BLOB skip it.
if Source.FieldByName(FldName).DataType in [ftBlob,
ftGraphic, ftMemo, ftOraBlob, ftOraClob, ftADT,
ftInterface, ftIDispatch, ftArray, ftReference,
ftParadoxOle, ftDbaseOle, ftFmtMemo] then
Continue;
// Get the field value as a string.
FldValue := Source.FieldByName(FldName).AsString;
// If value is longer than size of export field,
// raise an exception.
if (Length(FldValue) > FldSize) then
raise Exception.Create(
'Field value greater than export field size.');
// If field value is smaller than export field size,
// pad it with spaces.
if (Length(FldValue) < FldSize) then
FldValue := FldValue +
StringOfChar(' ', FldSize - Length(FldValue));
// Write the field value.
Write(AsciiFile, FldValue);
end;
// Write carriage return/line feed at end of record.
Writeln(AsciiFile, '');
Source.Next;
end;
CloseFile(AsciiFile);
end;

Figure 12: The TFixedExport.Execute method.

Mappings contains one entry for each field to be imported


in the format TableFieldName=StartingPosition,Length.
TableFieldName is the name of the field in the dataset
specified in the Destination property, StartingPosition is
based on the position of the first character of the field in
the ASCII record, and Length is the length of the field in
the ASCII record.
The ImportData method reads each record in the ASCII file,
until the end of the file is reached. Each record is read into a
string variable, Rec, by calling the Readln procedure. The for
loop iterates through the FMappings StringList, and extracts
the field name from the StringLists Names property. The
value on the right side of the equals sign in the StringList
20

DELPHI INFORMANT MAGAZINE | March 2003

procedure TFixedImport.ImportData;
// Add record in FFieldList StringList to Destination
// dataset using information in the FMappings StringList.
// Each entry in FMappings has the form:
// DestFieldName=StartPosition,Length
var
I, FldStart: Integer;
FldLength:
Integer;
FldName:
string;
S, Rec:
string;
begin
while (not EOF(FImportFile)) do begin
Readln(FImportFile, Rec);
with Destination do begin
Append;
for I := 0 to FMappings.Count - 1 do begin
FldName := Mappings.Names[I];
S := Mappings.Values[FldName];
FFieldList.Clear;
FFieldList.DelimitedText := S;
FldStart := StrToInt(FFieldList[0]);
FldLength := StrToInt(FFieldList[1]);
SetLength(S, FldLength);
Move(Rec[FldStart], S[1], FldLength);
FieldByName(FldName).AsString := Trim(S);
end;
Post;
end;
end;
end;

Figure 13: The TFixedImport.ImportData method.

is extracted using the Values property, and is assigned to


the DelimitedText property of the FFieldList StringList. This
parses the string using the default comma delimiter, and
places the field starting position in the first string in the
FFieldList, and the field length in the second string in the
FFieldList. These values are converted to integers and stored
in the FldStart and FldLength variables.
The length of the string variable, S, is set to FldLength by
calling SetLength, so S will be the right size to hold the value
from the ASCII file. The call to the Move procedure copies
FldLength bytes from Rec, the string variable that holds the
ASCII record, to S. This works because string variables can
also be accessed as an array of characters. You can read the
call to Move as: Copy FldLength bytes starting at position
FldStart in Rec, to position one in S.
Move is very efficient, but you must be careful to get the
parameters right, because it performs no error checking.
If you tell it to copy more bytes than the destination can
hold, it will blindly overwrite whatever is stored after the
destination variable in memory.
Finally, the value in the variable S is trimmed to remove leading or trailing white space, and is assigned to the field in the
destination dataset. The complete code for this component is
shown in Listing Four (on page 23).
Using the Components
You can download the two sample applications that accompany this article to get the source code for the components and
examples of using them. Figure 14 shows the main form of
the COPY application. Clicking the buttons in the Setup Database
group box will create the InterBase database, create the Source
table, and load it with data and create the Destination table.

Columns

&

Rows

Replacing BatchMove

Begin Listing One TCopyData Class


unit DataCopy;
interface
uses
SysUtils, Classes, DB;

Figure 14: The COPY applications main form.

Use the Empty Destination Table button to empty the destination


table after you click one of the copy buttons.
Figure 15 shows the main form of the ASCII application that
demonstrates the import and export components. This program
uses the same
InterBase database
used by the COPY
program, so you
must run COPY
and create the database and the tables
before you run the
ASCII program.
Conclusion
If youre moving
away from the
BDE, youll no
longer be able to
use the BatchMove
component. The
components in this
article provide the
Figure 15: The ASCII applications main form.
major features of
BatchMove in a
way that is as database-independent as possible. The components presented here are simple, and can be easily modified
and enhanced to meet your specific needs. Dont forget to look
at the utilities that come with the database youre using if you
need more sophisticated data import and export tools.
Two example applications that include all the components
referenced in this article are available for download on the
Delphi Informant Magazine Complete Works CD located in
INFORM\2003\MAR\DI200303BT.

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 an internationally known
trainer and is a frequent speaker at Borland Developer Conferences in the United
States and Europe. Readers may reach him at bill@dbginc.com.

21

DELPHI INFORMANT MAGAZINE | March 2003

type
TDataCopy = class(TComponent)
private
FSource: TDataSet;
FDestination: TDataSet;
FMappings: TStringList;
FRecordCount: Integer;
FRecordsCopied: Integer;
protected
procedure CopyAllColumns;
procedure CopyMappedColumns;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Execute;
published
property Source: TDataSet read FSource write FSource;
property Destination: TDataSet
read FDestination write FDestination;
property Mappings: TStringList read FMappings;
property RecordCount: Integer
read FRecordCount write FRecordCount;
property RecordsCopied: Integer read FRecordsCopied;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('DI', [TDataCopy]);
end;
procedure TDataCopy.CopyAllColumns;
// Copies all columns based on the column count of the
// Source dataset. Source and Destination must have
// compatible columns in the same order.
var
I:
Integer;
Count: Integer;
begin
Count := 0;
while (not FSource.EOF) do begin
// If RecordCount is > zero and number of records
// copied equals RecordCount, don't copy any more
// records.
if (FRecordCount > 0) and (Count >= FRecordCount) then
Exit;
// Copy record to destination dataset.
FDestination.Append;
for I := 0 to FSource.FieldCount - 1 do
FDestination.Fields[I].Assign(FSource.Fields[I]);
FDestination.Post;
// Increment count; move to next record in source
// dataset.
Inc(Count);
FSource.Next;
end;
FRecordsCopied := Count;
end;
procedure TDataCopy.CopyMappedColumns;
// Copies columns based on column names in Mappings.
// Mappings contains one entry for each column to be copied
// in the form DestColumnName=SourceColumnName. Values are
// copied by column name, not column order.

Columns

&

Rows

Replacing BatchMove

var
I, Count: Integer;
begin
Count := 0;
while (not FSource.EOF) do begin
// If RecordCount is > 0 and number of records copied
// equals RecordCount, don't copy any more records.
if (FRecordCount > 0) and (Count >= FRecordCount) then
Exit;
FDestination.Append;
for I := 0 to FMappings.Count - 1 do
FDestination.FieldByName(Mappings.Names[I]).Assign(
FSource.FieldByName(Mappings.Values[
Mappings.Names[I]]));
FDestination.Post;
Inc(Count);
FSource.Next;
end; // while
FRecordsCopied := Count;
end;
constructor TDataCopy.Create(AOwner: TComponent);
begin
inherited;
FMappings := TStringList.Create;
end;
destructor TDataCopy.Destroy;
begin
FMappings.Free;
inherited;
end;
procedure TDataCopy.Execute;
begin
// Ensure Source and Destination have been assigned.
if (not Assigned(FSource)) then
raise Exception.Create('Source dataset not assigned.');
if (not Assigned(FDestination)) then
raise Exception.Create(
'Destination dataset not assigned.');
// Make sure Source and Destination are open.
if (not FSource.Active) then
FSource.Open;
if (not FDestination.Active) then
FDestination.Open;
// Start with the first record in Source.
FSource.First;
// If Mappings is empty, copy all columns from Source to
// Destination; otherwise, copy only the mapped columns.
if (FMappings.Count = 0) then
CopyAllColumns
else
CopyMappedColumns;
end;
end.

End Listing One

Begin Listing Two TDelimitedExport


Class
unit DelimitedExport;
interface
uses
SysUtils, Classes, DB;
type
TDelimitedExport = class(TComponent)
private
FSource: TDataSet;
FFileName: string;

22

DELPHI INFORMANT MAGAZINE | March 2003

FDelimiter: Char;
FQuoteChar: Char;
public
constructor Create(AOwner: TComponent); override;
procedure Execute;
published
property Delimiter: Char
read FDelimiter write FDelimiter;
property FileName: string
read FFileName write FFileName;
property QuoteChar: Char
read FQuoteChar write FQuoteChar;
property Source: TDataSet read FSource write FSource;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('DI', [TDelimitedExport]);
end;
constructor TDelimitedExport.Create(AOwner: TComponent);
begin
inherited;
FDelimiter := ',';
FQuoteChar := '"';
end;
procedure TDelimitedExport.Execute;
var
AsciiFile:
TextFile;
I, LastField: Integer;
begin
// Make sure the user specified a file name.
if FileName = '' then
raise Exception.Create('FileName is blank.');
// Make sure DataSet is set, the dataset is open, and
current record is the first record.
if Source = nil then
raise Exception.Create(
'Source dataset is not assigned.');
if (not Source.Active) then
Source.Open;
Source.First;
// Open the delimited file.
AssignFile(AsciiFile, FileName);
Rewrite(AsciiFile);
LastField := Source.FieldCount - 1;
while (not Source.EOF) do begin
for I := 0 to LastField do begin
// If the field is a BLOB skip it.
if Source.Fields[I].DataType in [ftBlob, ftGraphic,
ftMemo, ftOraBlob, ftOraClob, ftADT,
ftInterface, ftIDispatch, ftArray, ftReference,
ftParadoxOle, ftDbaseOle, ftFmtMemo] then
Continue;
// If this is not first field, write Delimiter
character.
if (I > 0) then
Write(AsciiFile, Delimiter);
// If the field is not numeric write the opening
quote character.
if (Source.Fields[I].DataType in [ftString,
ftFixedChar, ftWideString]) then
Write(AsciiFile, QuoteChar);
// Write the field value.
Write(AsciiFile, Source.Fields[I].AsString);
// If the field type is not numeric write the closing
delimiter character.
if (Source.Fields[I].DataType in [ftString,
ftFixedChar, ftWideString]) then
Write(AsciiFile, QuoteChar);
end; // for

Columns

&

Rows

Replacing BatchMove

// Write carriage return/line feed at end of record.


Writeln(AsciiFile, '');
Source.Next;
end; // while
CloseFile(AsciiFile);
end;

procedure Register;

if (not Source.Active) then


Source.Open;
Source.First;
// Open the ASCII file.
AssignFile(AsciiFile, FileName);
Rewrite(AsciiFile);
LastField := Source.FieldCount - 1;
while (not Source.EOF) do begin
for I := 0 to FMappings.Count - 1 do begin
FldName := FMappings.Names[I];
FldSize := StrToInt(FMappings.Values[FldName]);
// If the field is a BLOB skip it.
if Source.FieldByName(FldName).DataType in [ftBlob,
ftGraphic, ftMemo, ftOraBlob, ftOraClob, ftADT,
ftInterface, ftIDispatch, ftArray, ftReference,
ftParadoxOle, ftDbaseOle, ftFmtMemo] then
Continue;
// Get the field value as a string.
FldValue := Source.FieldByName(FldName).AsString;
// If value is longer than size of export field,
// raise an exception.
if (Length(FldValue) > FldSize) then
raise Exception.Create(
'Field value greater than export field size.');
// If field value is smaller than export field size
// pad it with spaces.
if (Length(FldValue) < FldSize) then
FldValue := FldValue + StringOfChar(
' ', FldSize - Length(FldValue));
// Write the field value.
Write(AsciiFile, FldValue);
end; // for
// Write carriage return/line feed at end of record.
Writeln(AsciiFile, '');
Source.Next;
end; // while
CloseFile(AsciiFile);
end;

implementation

end.

end.

End Listing Two


Begin Listing Three TFixedExport
Class
unit FixedExport;
interface
uses
SysUtils, Classes, DB;
type
TFixedExport = class(TComponent)
private
FFileName: string;
FSource: TDataSet;
FMappings: TStringList;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Execute;
published
property FileName: string
read FFileName write FFileName;
property Mappings: TStringList read FMappings;
property Source: TDataSet read FSource write FSource;
end;

procedure Register;
begin
RegisterComponents('DI', [TFixedExport]);
end;
// TFixedExport
constructor TFixedExport.Create(AOwner: TComponent);
begin
inherited;
FMappings := TStringList.Create;
end;
destructor TFixedExport.Destroy;
begin
FMappings.Free;
inherited;
end;
procedure TFixedExport.Execute;
var
AsciiFile: TextFile;
I:
Integer;
LastField: Integer;
FldName:
string;
FldSize:
Integer;
FldValue: string;
begin
// Make sure the user specified a file name.
if FileName = '' then
raise Exception.Create('FileName is blank.');
// Make sure DataSet property is set, dataset is open,
// and current record is the first record. if Source =
nil then
raise Exception.Create(
'Source dataset is not assigned.');

23

DELPHI INFORMANT MAGAZINE | March 2003

End Listing Three

Begin Listing Four TFixedImport


Class
unit FixedImport;
interface
uses
SysUtils, Classes, DB;
type
TFixedImport = class(TComponent)
private
FFileName: string;
FDestination: TDataSet;
FMappings: TStringList;
FImportFile: TextFile;
FFieldList: TStringList;
protected
procedure ImportData;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Execute;
published
property Destination: TDataSet
read FDestination write FDestination;
property Mappings: TStringList read FMappings;
property FileName: string
read FFileName write FFileName;
end;

Columns

&

Rows

Replacing BatchMove

procedure Register;
implementation
procedure Register;
begin
RegisterComponents('DI', [TFixedImport]);
end;
// TFixedImport
procedure TFixedImport.ImportData;
// Adds record that's in FFieldList StringList to the
// Destination dataset using information in the FMappings
// StringList. Each entry in FMappings has the form:
// DestFieldName=9 where 9 is the zero-based number of the
// field in the delimited text file.
var
I:
Integer;
FldStart: Integer;
FldLength: Integer;
FldName:
string;
S:
string;
Rec:
string;
begin
while (not EOF(FImportFile)) do begin
Readln(FImportFile, Rec);
with Destination do begin
Append;
for I := 0 to FMappings.Count - 1 do begin
FldName := Mappings.Names[I];
S := Mappings.Values[FldName];
FFieldList.Clear;
FFieldList.DelimitedText := S;
FldStart := StrToInt(FFieldList[0]);
FldLength := StrToInt(FFieldList[1]);
SetLength(S, FldLength);
Move(Rec[FldStart], S[1], FldLength);
FieldByName(FldName).AsString := Trim(S);
end;
Post;
end;
end;
end;
constructor TFixedImport.Create(AOwner: TComponent);
begin
inherited;
FMappings := TStringList.Create;
FFieldList := TStringList.Create;
end;
destructor TFixedImport.Destroy;
begin
FMappings.Free;
FFieldList.Free;
inherited;
end;
procedure TFixedImport.Execute;
begin
if (FMappings.Count = 0) then
raise Exception.Create('No field mappings specified.');
if (FFileName = '') then
raise Exception.Create('No file name specified.');
AssignFile(FImportFile, FFileName);
Reset(FImportFile);
ImportData;
CloseFile(FImportFile);
end;
end.

End Listing Four

24

DELPHI INFORMANT MAGAZINE | March 2003

F O U N D A T I O N
MICROSOFT .NET FRAMEWORK

C L A S S

MICROSOFT .NET FCL

By Alexei Fedorov and Natalia Elmanova

Preparing for .NET


Part II: The Microsoft .NET Framework Class Library

his article concludes a two-part introduction to


Microsoft .NET for the Delphi developer. The
first installment offered an overview of the .NET

Framework its main parts and architecture (see the


January 2003 issue of Delphi Informant Magazine). In
this installment well provide a synopsis of the major

However, we can also refer to an assembly that contains the


namespace. In Delphi, this reference could look like the following code snippet:
uses
System.Windows.Forms;
type
TDemoForm = class(Form)

namespaces and classes that comprise the Microsoft


.NET Framework Class Library (FCL).
All classes in the FCL are organized into namespaces. Each
namespace can contain classes and types that relate to a specific task or a specific set of tasks, as well as other namespaces (similar to how files are organized in directories).
The root namespace for fundamental types in the FCL is
the System namespace. It contains classes (for example, the
Object class, which is a base class for all other FCL classes;
the Console class, which is used for creating console applications; and many others), as well as several namespaces
(for example, the System.IO namespace, which contains
classes for implementing input and output operations; the
System.Data namespace, which contains classes responsible for data access with ADO.NET; and so on).
From this example, we can see the naming conventions
for the namespaces. The name of the namespace contains
the name of its ancestor and its own name, separated by
a dot, e.g. System.Data.OleDb. The full name of a class
or type in .NET also contains its namespace name and
its own name separated by a dot, e.g. System.IO.FileInfo
represents the FileInfo class, which belongs to the
System.IO namespace.
We can refer to any class by its full name; for example:
type
TDemoForm = class(System.Windows.Forms.Form)

25

DELPHI INFORMANT MAGAZINE | March 2003

Please note that the System namespace is referred to by


default. In addition to FCL namespaces, we can create our own
namespaces or use third-party libraries, which expose their
own set of namespaces. In this case, the naming scheme is
the same, which makes it easy to create hierarchical groups of
types and name them descriptively. The recommended names
for custom namespaces are CompanyName.ProductName (the
name of the company and its product, correspondingly). For
example, Delphi for .NET comes with namespaces such as
Borland.Delphi.Classes, Borland.Delphi.Types, and so on.
It should be mentioned that namespaces can reside in a single file and in multiple assemblies. This is significant when
we refer to namespaces in compiler options.
The Microsoft .NET FCL, which is part of the .NET Framework,
is a set of namespaces, classes, interfaces, and value types
that can be used in .NET applications. Using these classes and
types, we can create the following kinds of applications:
Windows Forms. Rich Windows GUI applications that
can take advantage of the user interface features provided by the Windows family of operating systems, and
that can access databases and call XML Web Services.
Types used to build Windows Forms applications can be
found in the System.Windows.Forms namespace and its
sub-namespaces.
Web Forms. HTML-based applications, also called Dynamic
Web applications or ASP.NET applications. Types used
to build Web Forms applications can be found in the
System.Web.UI namespace and its sub-namespaces.

Foundation

Class

Preparing for .NET

XML Web Services. Objects and methods, accessible


through standard Internet protocols. Types used to build
XML Web Services can be found in the System.Web.Services
namespace and its sub-namespaces.
Windows Console Applications. Windows applications with
a minimal interface that can be used to create command-line
tools, utilities, and so on. Console applications are based on
the System.Console class that can be found in the System
namespace.
Windows Services. Service applications used through
Windows Services Control Manager. Types used to build
Windows Services applications can be found in the
System.ServiceProcess namespace.
Component Library. Stand-alone components that can be
used in other types of applications.
The System Namespace
The main namespace of the FCL is the System namespace.
It contains the Object base type, which is the ultimate
ancestor for all other types in the FCL. Additionally, the
System namespace contains types for integers, characters,
strings, exception handling, and console I/O, as well as
utility types used to perform conversions between data
types, format data types, generate random numbers, perform various math functions, and so on.
Types found in the System namespace can be used in a variety
of applications. The following list shows several types implemented in the System namespace:
System.Array
System.Console
System.Convert
System.Environment
System.EventArgs
System.Exception
System.GC
System.Math
System.OperatingSystem
System.Random
System.String
System.Version
The System namespace also contains definitions for all types
used across the FCL: value types, which are allocated on the
stack; and reference types, which are stored on the heap. All
value types are based on the ValueType type. In addition to the
value types there are Boolean, Byte, Char, Int16, Int32, Int64,
Single, Double, Decimal, DateTime, Enum, and Struct. Reference types are Interface, Delegate, Class, String, and Array.
Because all the types in the FCL are based on the Object type,
they share a common set of methods:
Equals. Used to test if an object is the same instance as
another object.
ReferenceEquals. Determines if two objects are the same
instance or both are null references.
Finalize. If overridden in the derived class, this method
can be used to perform some cleanup before the garbage collector reclaims the object.
GetHashCode. Used to generate a hash value for the object.
MemberwiseClone. Creates an exact clone of the object.
ToString. Returns a text representation of the object.
GetType. Returns the Type class for this instance of the class.
26

DELPHI INFORMANT MAGAZINE | March 2003

This may seem unusual for Delphi developers. For example,


even integers expose these methods in the FCL. However,
such possibilities are quite useful in practice.
The System Namespace and Its
Sub-namespaces
The System namespace contains several subnamespaces. The System.Collections namespace contains
implementations of the various collections of objects,
such as lists, queues, hash tables, and dictionaries. Also,
this namespace contains several interfaces used to define
some basic functionality for collections.
The System.Diagnostics namespace provides classes
that allow us to interact with system processes (the
Process class), event logs (the EventLog class), and
performance counters (the PerformanceCounter class).
The System.Globalization namespace contains classes
that define culture-related information, including the
language; the country/region; the calendars in use; the
format patterns for dates, currency, and numbers; and
the sort order for strings. In addition to the classes
implemented in this namespace, well find the Calendar
class, which represents time in such divisions as
weeks, months, and years; the CultureInfo class, which
represents information about a specific culture; the
RegionInfo class, which contains information about the
country/region; as well as several others.
The System.IO namespace contains classes used to
synchronously and asynchronously read and write to/
from data streams and files. Here we will find classes
that implement readers, writers, and streams, as well as
classes that provide access to the file system: Directory,
DirectoryInfo, File, FileInfo, and Path.
The System.Reflection namespace contains classes and
interfaces that provide a managed view of loaded types,
methods, and fields, with the ability to dynamically
create and invoke types. One of the entry points into the
System.Reflection namespace is the Type object, which
can be obtained through the GetType method of any class
in the FCL.
The System.Resources namespace provides classes and
interfaces that allow us to create, store, and manage
various culture-specific resources used in an application.
The main class implemented in this namespace that
works with the resources is ResourceManager.
The System.Security namespace provides the underlying
structure of the Common Language Runtime security
system, including base classes for permissions. The main
access point for classes interacting with the security
system is the SecurityManager class.
The System.Text namespace contains classes representing
ASCII, Unicode, UTF-7, and UTF-8 character encodings;
abstract base classes for converting blocks of characters
to and from blocks of bytes; and a helper class that
manipulates and formats the String object without
creating intermediate instances of it.

Foundation

Class

Preparing for .NET

The System.Threading namespace provides classes and


interfaces that enable multithreaded programming: a
ThreadPool class to manage groups of threads, a Timer
class used to enable a delegate to be called after a
specified amount of time, and a Mutex class used for
synchronizing mutually exclusive threads.
The System.Windows.Forms
Namespace
Classes implemented in this namespace are used to create
Windows GUI applications that can take advantage of the
user interface features provided by the Windows family of
operating systems.
The primary class used to create Windows applications is
the Form class, which represents the main window of the
application. Other types include classes that implement
user interface elements (menus, buttons, input boxes,
text boxes, list boxes, scroll bars, and so on), as well as
a set of delegates used to handle various user and system
activity. Besides user interface elements implemented
in the System.Windows.Forms namespace, well find
common dialog boxes (Color, Font, Open File, Save File,
and several others used to print), and message boxes.
The System.Web Namespace
This namespace and its secondary namespaces contain
classes, interfaces, delegates, and enumerations
that are used to enable communication between the
Web browser and server. Types implemented in the
System.Web namespace provide basic functionality
for ASP.NET, Microsofts technology for creating Web
applications and dynamic Web sites. In addition to the
classes in the System.Web namespace, well find the
HTTPRequest class, which envelopes the HTTP request
and contains information about it; the HTTPResponse
class, which sends the servers output to the client;
the HTTPServerUtility class, which is used to access
server-side utilities and processes; and HTTPApplication,
HTTPBrowserCapabilities, HTTPContext, HTTPCookie,
HTTPRuntime, and several other classes.
Secondary namespaces of the System.Web namespace
provide such functionality as caching frequently used
resources (System.Web.Caching), setting up the ASP.NET
applications (System.Web.Configuration), hosting ASP.NET
applications (System.Web.Hosting), sending e-mails
(System.Web.Mail), implementing ASP.NET security
(System.Web.Security), and supporting the session state
(System.Web.SessionState).
The System.Web.UI namespace implements two types
of Web application user controls: HTMLControls and
WebControls. It includes the Control class, which is the
base class for all Web user interface controls; the Page
class, which represents a single ASP.NET page; and a set
of derived classes that implement particular controls, i.e.
input boxes, buttons, links, anchors, forms, validation,
and data-binding controls.
Classes implemented in the System.Web.Services namespace
and its secondary namespaces are used to implement XML27

DELPHI INFORMANT MAGAZINE | March 2003

based Web Services on the ASP.NET platform reusable


Web components that can be invoked from any platform
capable of communicating over the Internet. The classes in
this namespace include WebService, which provides access
to the common ASP.NET objects; WebServiceAttribute, which
is used to add additional information to a Web Service;
WebMethodAttribute, which is used to define a Web method
exposed by a Web Service; and WebServiceBindingAttribute,
which is used to declare the binding between Web Service
methods.
Secondary namespaces of the System.Web.Services
namespace implement Web Services-related protocols:
WSDL (System.Web.Services.Description), UDDI
(System.Web.Services.Discovery), SOAP and other
protocols (System.Web.Services.Protocols), and
configuration tools (System.Web.Services.Configuration).
The System.Data Namespace,
AKA ADO.NET
This namespace and its secondary namespaces
contain data-related classes known by the common
name ADO.NET. The System.Data namespace consists
of classes, interfaces, delegates, and enumerations
its central class is the DataSet class, which is the
disconnected data container that serves as an in-memory
cache of data. This class contains collections of tables, as
well as parent-child relations between tables, columns,
rows, and constraints, etc.
The System.Data.Common namespace contains several
classes used by .NET data providers: DataAdapter,
DataColumnMappingCollection, DataTableMappingCollection,
and others.
The System.Data.OleDb namespace contains classes
that implement the OLE DB data provider. The basic
classes are OleDbDataAdapter, a set of commands and
a connection to a database; OleDbCommand, a SQL
statement or stored procedure that will be executed
against a data source; OleDbConnection, an open
connection to a data source; and OleDbDataReader, a
reader on a stream of data rows from a data source.
The System.Data.SqlClient namespace contains classes
that implement the SQL Servers data provider. The basic
classes are SqlClientDataAdapter, a set of commands
and a connection to a database; SqlClientCommand, a
SQL statement or stored procedure that will be executed
against a data source; SqlClientConnection, an open
connection to a data source; and SqlClientDataReader, a
reader on a stream of data rows from a data source.
The System.Data.SqlTypes namespace contains classes
that represent the SQL Servers native data types.
The System.XML Namespace
Classes in this namespace and its secondary
namespaces implement XML support in the Microsoft
.NET Framework. The most important classes in the
System.XML namespace are XmlTextReader, which
represents a reader that provides fast, non-cached

Foundation

Class

Preparing for .NET

forward-only access to XML data; XmlTextWriter, a writer


that provides a fast, non-cached forward-only way to
generate streams of XML data; XmlDocument, a class
that represents an entire XML document; XmlNodeList,
an ordered collection of nodes within an XML document;
XmlNamedNodeMap, a collection of nodes that can be
accessed by name or index; XmlNodeReader, a reader that
is used to access data in an XmlNode; and XmlNode, a
single node in the XML document.
Secondary namespaces of the System.XML namespace
provide additional functionality:
The System.XML.XSL namespace contains classes
that allow us to transform XML documents using XSL
stylesheets.
The System.XML.XPath namespace contains classes
used to navigate XML documents with XPath syntax.
The System.XML.Schema namespace contains
classes to work with XSD schemas and validate XML
documents.
The System.XML.Serialization namespace contains
classes that enable objects to be serialized to XML.
The System.Drawing Namespace
This namespace and its sub-namespaces contain all
graphics functions available in the FCL and provide a
high-level interface to the GDI+. The main class in the
System.Drawing namespace is Graphics, which provides the
device context for drawing. Other classes include Brush,
Pen, Font, Color, Bitmap, Icon, Region, and several others.
The System.Drawing.Drawing2D namespace contains
classes and enumerations that provide advanced twodimensional and vector graphics functionality. Using
this namespace, we can use gradient brushes, perform
geometric transformations with the Matrix class, and
create paths by using the GraphicsPath class.
The System.Drawing.Imaging namespace provides imaging
functionality based on advanced GDI+ functions. Here
well find the Metafile class used for recording and saving
metafiles, the Encoder and Decoder classes for working
with images in different formats, and the PropertyItem
class for storing and retrieving metadata in image files.
The System.Drawing.Text namespace provides
advanced GDI+ typography functionality. It contains
the FontCollection class, which is the base class for
InstalledFontCollection (a collection of fonts currently
installed on the system), and PrivateFontCollection
(a collection of fonts used by the currently running
application).
The System.Drawing.Printing namespace contains nearly
all of the classes used to print text and graphics. Here
well find the PrintDocument class, which implements
the basic printing functionality; the PrintController class,
which controls the document printing process; as well as
several other helper classes.
Other Namespaces
In addition to the major namespaces weve explored,
28

DELPHI INFORMANT MAGAZINE | March 2003

the FCL contains several other namespaces, which well


briefly cover here.
The System.Net namespace provides support for
building applications that use networking and Internet
protocols to send and receive data. Here we can find the
HTTPWebRequest, FileWebRequest, HTTPWebResponse,
and FileWebResponse classes that implement different
schemes to request remote or local resources. Well also
find classes for implementing Internet authentification
and authorization, implementing cookies, using DNS
functions and servers, sending and receiving data,
processing Web exceptions, and implementing socketbased communications.
The System.Messaging namespace contains classes for
building applications that can connect to, monitor, and
administer message queues (for example, MSMQ queues)
on the network, and send, receive, or peek messages.
Here we can find:
the MessageQueue class, which lets us read and write
messages to the queue, and create, iterate, and delete
queues;
several formatter classes that allow us to serialize
and deserialize messages sent and received from
queues; and,
classes for support-code access and ACL-based
security, filtering message properties when reading
messages from a queue, and using transactions when
sending and receiving messages.
The System.EnterpriseServices namespace provides an
infrastructure for enterprise applications, and in
particular .NET objects with access to COM+ services.
Here we can find the ContextUtil class, which obtains
information about the COM+ object context; the
ResourcePool class, which stores objects in the current
transaction; and classes for wrapping COM+ DTC
interfaces, managing shared properties, setting the COM+
component attributes, and handling COM+ exceptions.
The System.Management.Instrumentation namespace
provides the functionality necessary for instrumenting
applications for management and exposing their
management information and events through a Windows
Management Instrumentation interface to potential
consumers, such as Microsoft Application Center or
Microsoft Operations Manager. It contains the BaseEvent
class that is a base class for management event classes,
the Instance class that is a base class for management
instrumentation instance classes, the Instrumentation
class that provides helper functions for exposing events
and data for management, as well as several classes for
installing instrumented assemblies and defining attributes
of instrumented classes.
Conclusion
This ends our brief introduction to .NET as a platform.
In this two-part introduction to Microsoft .NET for the
Delphi developer, we discussed the .NET Framework,
Common Language Runtime, managed code, Intermediate
Language and just-in-time compiler, .NET assemblies and

Foundation

Class

Preparing for .NET

modules, Common Type System, and the types of .NET


applications. We also provided an overview of the major
namespaces and classes that comprise the Microsoft .NET
Framework Class Library.
We hope you now have some background to play with
the Delphi for .NET Compiler Preview that comes with
Delphi 7. Youll find more articles about this compiler in
forthcoming issues of Delphi Informant Magazine.

Alexei Fedorov is a developer and consultant based in Moscow, Russia.


During his 20 years of experience he has worked as a chief technology
officer for a Swiss IT company, has provided technical support for Borland
languages, has participated in development and software localization,
and has created many Internet and intranet sites. Alexei also contributes
articles for asp.netPRO magazine, and has co-authored many books,
including Professional ASP 2.0, ASP 2.0 Programmers Reference, and
Advanced Delphi Developers Guide to ADO. Alexeis recent book, A
Programmers Guide to .NET, was published by Addison-Wesley in 2002.
Natalia Elmanova, Ph.D. is a developer and trainer based in Moscow,
Russia, as well as an executive editor for ComputerPress magazine (http:
//www.compress.ru). During the last 15 years she has trained several hundred
Delphi and Visual Basic developers and has been a team leader in several software
projects for various commercial companies, research institutes, and governmental
organizations. Natalia was a contributing author to the 10th, 11th, 12th, and
13th Annual Borland Conferences and has co-authored many books, including
Advanced Delphi Developers Guide to ADO.

29

DELPHI INFORMANT MAGAZINE | March 2003

T O W A R D
HUMOR

T H E

L I G H T

DELPHI 1-7

By Loren Scott

The Lighter Side of...


Technical Support

echnical support is like the front lines of


a battlefield. You generally deal only with
people who have a problem (either real or

mental), and in some rare cases have already


exhausted all possible means to solve the problem.
Rarely does someone call or e-mail you just to say:
Everything is fine. Keep up the good work!

Business Rule #1. A good business rule to follow is the


ever-popular: The customer is always right. For example,
if a guy calls up technical support and begins the conversation by saying, This is probably a stupid question..., a
good tech support person should always immediately agree
with the customer by replying, Youre right, sir. It probably
is a stupid question.

course, is not as easy as it sounds. First, you must navigate


the maze of the automated answering system.
For questions on installation, press one. For questions on
installing the installation program, press two. For questions
on how to install the software that performs the installation
of the software, press three. ... and so on.
Contrary to popular belief, this type of system was not
invented to make it easier for the caller. No, no, no. It
was designed to weed out the weak ones. After nine or 10
(sometimes recursive) button presses, the easily frustrated
ones will just throw in the towel and will, once again, try
to figure out the problem on their own. With any luck, they
will succeed, and you have not wasted your own valuable
time helping them waste theirs.

On-site support. For a nominal (aka huge) annual fee, the


vendor will walk, drive, or fly to your location to personally
share your pain and suffering. After a sufficient amount of
simulated technical probing, the support technician can then
remedy the problem which probably involves just installing the latest revision of the software without making the
client feel like theyve troubled him too much.

E-mail support. Generally free, e-mail support allows the


customer to send your tech support staff a question via email. Then, after an industry-standard average of six to eight
weeks, the customer should expect one of the following,
automatic mail server-generated replies:
Thank you for your technical inquiry. Our technical support group will get back to you soon with an answer.
(The term soon means that same industry-standard
average of six to eight weeks.)
Thank you for your technical inquiry. Our technical
support group requires additional information and/or
sample code from you. Please just take a guess at what
additional information they need and send it whenever
you have time. Then, they will get back to you whenever
they have time (six to eight weeks).
Thank you for your technical inquiry. Our technical support group hasnt got a clue what the hell you are asking
about in your message. Are you sure you have contacted
the right company? Please get a clue, and then contact
us again.

Voice support. For a moderate (aka not quite as huge as


the previous type) annual fee, the vendor will allow you
to call their technical support staff on the phone. This, of

If the customer claims not to have an e-mail account, its


your civic duty to shame them into catching up with the
times. For example:

By agreeing with the customer in this way, you make it clear


from the outset that you are on their side.
Levels of Support
The levels of technical support available for software products varies from No Support Whatsoever to Well fly out
to your site, hold your hand, and kiss up to you until the
problem has been solved, or until you run out of money,
whichever comes first.

30

DELPHI INFORMANT MAGAZINE | March 2003

To w a r d

the

Light

The Lighter Side of ...

YOU: ...Okay, well... Ill just e-mail you once we have an


answer for you.
THEM: Oh, I dont have an e-mail account. Can you fax me
instead?
YOU: Hmmm... No e-mail account, eh?
THEM: Nope. Not yet.
YOU: I see. Where are you located again?
THEM: A small town in Montana.
YOU: Im sorry.
THEM: (Louder) I said I live in a small town in Montana!
YOU: No, no. I heard you. ... Im just sorry.
Fax support. Basically, the same as e-mail support, except
that the prompt and courteous tech replies noted above
come to the customers fax instead of via e-mail.

THEM: Two ninety-five?


YOU: (Confirming the previous suspicion, you decide to
play with him a bit) Thats right. Three forty-nine.
THEM: (Thrown for a loop) Wait a minute! (dumbfounded
pause) Three forty-nine? But, you just said two ninety-five!!
YOU: Thats right. Two ninety-five. (Warning: Playing with
a potential customers mind is risky. You might make him
angry and lose the sale. However, you might still want to
try and determine if the person youre dealing with probably should not be playing with your software anyway. If
this is the case, turn on the sarcasm. If youre lucky, the
guy will immediately phone your main competitor and
waste their time instead.)
THEM: Oh...okay. And, uhhh...what time do you guys
close?
YOU: Five oclock.

KMABYOYO support. Simply put, this acronym stands for:


Kiss My *** Baby. Youre On Your Own! Enough said.

THEM: Five oclock?

Customer Types
There are several different types of customers that youll
come across in tech support. Being able to recognize these
different types of customers will help you adjust your conversation to meet their personal needs.

THEM: Wait... Four o clock? Or...Five oclock?

The Repeater. The Repeater feels compelled to repeat back


every answer you give him/her, as you say it. Presumably,
this is just to catch those extremely rare cases when, at
the precise moment you answered the original question,
something somewhere in the universe might have shifted
which affected something else to cause the answer you
just gave to have now become completely obsolete.
A typical conversation with a Repeater might go something
like this:
YOU: Tech support. This is Dave. How can I help you?
THEM: Yeah, uhhh... Whos this again?
YOU: This is Dave.
THEM: Hi, Dave. I was thinking of upgrading our systems
from Windows 3.1 to Windows XP. Does your product currently support Windows XP?
YOU: Yes.
THEM: It does support Windows XP?
YOU: Yes. Yes, it does.
THEM: Does that come with a printed manual?
YOU: Yes.
THEM: Oh, it does come with a printed manual?
YOU: (Now suspecting that we just might be dealing with a
Repeater) Yes, yes. It really does include a printed manual.
THEM: I see... And, uhh... how much does it cost?
YOU: Two ninety-five.
31

DELPHI INFORMANT MAGAZINE | March 2003

YOU: Yessir. Four oclock.

YOU: Thats right.


THEM: What?
YOU: Five oclock.
THEM: Ooookay... (suspecting that youre on to him)
Umm...do you accept VISA?
YOU: Yes, we do.
THEM: You do?
YOU: No, we dont
THEM: But...I thought you just said that you did accept
VISA.
YOU: Ah-hah! So, you did hear me!
THEM: Hey! Are you getting smart with me?
YOU: I am too!
THEM: Huh?
YOU: Nevermind...
The Explainer. The Explainer generally asks a question,
and if he receives a negative answer, feels that explaining
his entire reason for living might make the answer change.
Of course, sometimes the Explainer may actually clarify
something, which just might affect the answer. However,
99.9999% of the time, this is not the case, and the Explainer is just wasting your time. They are probably just lonely
and need someone to talk to. A typical conversation with
an Explainer might go something like this:
YOU: This is Steve. How can I help you?
THEM: Hi, Steve. My name is Louie. Im trying to find a
software product that will allow me to convert all of our old
COBOL applications into highly-optimized C++ code. Do
you have anything thatll do this?

To w a r d

the

Light

The Lighter Side of ...

YOU: No. We dont have any products like that.

YOU: You say that youre using version 4.3 of our product?

THEM: Oh. (pause) I see. (another pause) Ahhhh... (and


then the explaining begins...) Cause ysee weve got
about 750,000 lines of COBOL code, and manually porting
this over to C++ could take weeks. (And youre thinking: Weeks? Did he say weeks? Get real! It would take
you years to do what youre talking about, Louie. I say
call your headhunter now and find a new job.) So, we
figured that if we could find a utility that could automatically convert it all over to C++ for us, that wed save a
lot of time and money... Cause yknow what they say...
Time is money! Ha ha ha... uhhhh... Are you there?

THEM: Thats right. Four point three! Its been working fine
for me for the past five years under Windows 95. Now, all I
do is completely change out the operating system on the PC
and KA-BOOM!!! No workie.

YOU: (Quickly racing back to the desk with your freshlyfilled cup of coffee and releasing the speaker-phone button)
Yes! Yes, Im still here.
THEM: So, uhhhh...You dont have any tools like this?
YOU: No, sir. We still dont.
The Complainer. The Complainer doesnt call to get a solution to a problem if there really is a problem. He just
wants to vent. A typical conversation with a Complainer
might go something like this:

YOU: Yessir, but... version 4.3 of our product was released


before Windows XP was released. We have an updated version
4.5 of our product that works under Windows XP just fine.
THEM: Is this a paid upgrade? (Hoping youll say Yes so
he can complain some more.)
YOU: No, sir. In fact, this is a free update.
THEM: (Disappointed) Oh...I see...
YOU: You can download the update right now from our Web
site. Its at www...
THEM: Internet, eh? (Quickly searching for something else
to bitch about.) Well, what if I spend hours downloading
this update and it still doesnt work?

YOU: Tech support. This is Beavis. Can I help you?

YOU: Well, we have several thousand customers that are


running version 4.5. Many of them, Im sure, are also running Windows XP. And, we dont have any problem reports
from them.

THEM: (Loud exhale) Probably not.

THEM: I dont know...

YOU: Sorry?

YOU: Sir. If you could just download the update, and if it


doesnt work for you, call us back and well be happy to
help you again.

THEM: You should be. You people call yourselves a software


company? I pay good money for your so-called product,
and it doesnt work. Then, Ive got to sit on hold listening
to (mocking the DJs campy voice) ...All love songs...All
the time.... Finally, after what? five or 10 minutes on
hold, I get to talk to a human. Unreal.
YOU: (Trying to remain calm) Is there something I can help
you with, sir?
THEM: Well, Im using version 4.3 of your product, and I
just upgraded my operating system from Windows 95 to
Windows XP. Now...all of a sudden...
YOU: Okay, sir. I know what the prob...
THEM: Oh, I aint finished yet!

THEM: Oh, sure! Ive gotta call you back if it doesnt work!
Spend my money calling you back if it doesnt work. Why
dont you call me back if it doesnt work?
YOU: Huh?
THEM: Nevermind. Whats the URL for the Web site again?
Things a Tech Support Person
Should Never Say
One. If a customer (or potential customer) asks, Does your
current release have any known bugs? a good tech support
person should never answer, Uhhh...youre not planning to
use this in conjunction with systems for pacemakers or air
traffic control radar, are you?

YOU: But...
THEM: Hang on there, sport! Theres more. Now all of a
sudden my programs that used to run just fine on Windows
95 are locking up Windows XP. I mean locking up all of
Windows XP. Not just one task. Noooo! The whole damn
thing. Locked right up!
YOU: Yessir; I see, but...
THEM: Do you know how hard it is to lock up all of Windows XP?

Two. If a customer starts a conversation with something


like, Yea, Im using version 1.0 of your product... the tech
support person should never wince audibly and say, Version 1.0? Damn!!! You are one gutsy S.O.B.!
Three. If a customer is having trouble grasping a concept of
your software, and if he gets exasperated and says, Well, I
guess Im just stupid!, do not I repeat do not attempt
to be helpful by saying, Well, I didnt say you were stupid.
Youre just ignorant.

YOU: Well, actually...


THEM: Whats that, smart boy?
YOU: Sir, if I could just say something...
THEM: Fine. Go for it.
32

DELPHI INFORMANT MAGAZINE | March 2003

Loren Scott is the co-author of Delphi Database Development [M&T Books,


1996] and has served in the trenches in software tech support, development,
marketing, consulting, and management since 1990. Currently serving time as
a Senior Software Engineer for a large Southern California company, Loren can
be reached via e-mail at loren@WillowRocks.com.

N E W

&

U S E D

By Warren Rachele

DbAltGrid
Powerful, Flexible DBGrid Replacement

ne of the first additions a database programmer often makes to Delphis standard set of
VCL components is a replacement for that

data-display workhorse, the DBGrid component. The


original control performs well in standard tasks, but
a programmer creating commercial software with
myriad user requirements may soon be bumping up
against its limitations.

Countless shops have produced replacement grid components


with a wide range of features and prices; nearly all of them
offer the promise of an improved user experience when their
grid is integrated into your project. DbAltGrid by Quasidata is
a relatively new entry into the component arena that proposes
a new approach to the data grid interface, one that enables
the developer to build a unique user interface.
In addition to the grid itself, the DbAltGrid Suite contains a
variety of supporting components that enable advanced data
display features. In its default configuration, the DbAltGrid
component is a fully compatible direct replacement for the
DBGrid in your project. Figure 1 shows the two components
in their default configurations, demonstrating the direct
replacement capabilities. As exciting as this is, most
programmers wont purchase the component suite simply
to replace the existing DBGrids in their projects. Theyre
likely looking for the upgraded features and additional value
offered by the replacement.
Have It Your Way
The data grid is often selected for data display purposes
over the use of individual edit boxes because of the ease
with which it can be implemented. The grid is connected
to a data source and the table is displayed in an easyto-comprehend format. The increasing sophistication of
users and their Internet-influenced interface requirements
demand more flexibility in the way data is made available
to them. This flexibility is the impetus behind DbAltGrid.
33

DELPHI INFORMANT MAGAZINE | March 2003

The advantages of the component are seen in its relatively


free-form display capabilities (see Figure 2).
The data field layout is controlled through configuration
of the associated field headers in the title bar. Notice
that the BMP field has been moved to a second row in
the grid. This action is performed by simply dragging the
title header corresponding to the field to the new position
within the grid. You might think that a new row could
affect the metrics of the grid, but this isnt the case. All
rows associated with a single record are considered to
be a single row for navigation purposes. In Figure 2,
notice that the position indicator at the left of the grid
encompasses both rows of the record. Once positioned,
the height of each of the rows can then be resized to fit
the data, or the look youd like.
Each of the columns can be manipulated to generate the
display configuration that best fits your project. In Figure 3,
the grid appears less like a table and more free form. Each
of the fields has been configured individually within the
Columns editor to have unique display properties. DbAltGrid
provides a wide variety of display options that the standard
grid doesnt have, and most of the properties are easily
modified through the Object Inspector. However, there are

Figure 1: Delphis DBGrid vs. DbAltGrid.

New

&

Used

DbAltGrid

Figure 2: DbAltGrids display flexibility is a plus.

Total Control
The display of different types of data
within the DbAltGrid component is
controlled by templates. Quasidata
developed templates for the display and
manipulation of date and time, numeric
data, Boolean data, memo types, hyperlinks, and image data. The application
shown in Figure 4 has templates embedded in the Date, Time, and Ticket Price
fields for use in editing the data. The
templates are activated through buttons embedded in the cell which appear
when the user moves the cell selector to
that part of the grid. The dagCalendar
component is used for easier editing of
the event dates (see Figure 5).
The Portfolio application shown in Figure 6 puts more
features of DbAltGrid to work. The DbAltGrid footer
panel is used in this application at the bottom of the grid.
This panel can be enabled and disabled at will, and is
designed to contain summary data from the table. The
Sum of the Shares column is displayed in the Portfolio.
This is a handy tool because it stays in place during scroll
and other navigational operations.
In addition to summarizing the data, a user can sort
the SYMBOL column by clicking on the title header.
Programmers should be aware that the summarization
and sorting functionality must be coded by hand. The
DbAltGrid component is a data display control, not a data
manipulation tool. In Figure 6, there are a series of SQL
statements and Query controls that perform the actual
SUM and ORDER BY manipulations.

Figure 3: A free form grid.

Making the Move


Say your application is in use by thousands of people and
it was completely developed using the DBGrid control.
Understandably, youre probably hesitant to rewrite all
of your code to integrate the improved functionality of
DbAltGrid. In actuality, the process could not be simpler.
Quasidata made the upgrade path easy by including the
dagBackup control in the Suite. This tool automates the
process of moving from one component to the other.
Start by placing a dagBackup control on the form that contains the original DBGrid. Next, associate the backup controls Grid property with the DBGrid you want to upgrade.
Finally, right-click the dagBackup control and select Convert
from the context menu. The DBGrid code in your application is replaced with references to a DbAltGrid object of the
same name. In other words, your code originally referred
to a DBGrid object named Grid1. This same name is now
pointing to a DbAltGrid object. Your application simply
needs to be recompiled; no manual changes are required.

Figure 4: Templates help display data.

a few properties that arent published that the programmer


will have to dig for. For example, the Bandheight property
controlling how tall the individual rows appear is modified
with code, not the Object Inspector.
34

DELPHI INFORMANT MAGAZINE | March 2003

The dagBackup control has further utility beyond the conversion process. All of the properties applicable to the grid
can be written to a text file, a resource file, or the registry,
and then read back later. For example, one grid in your project can be configured with all of the desired options and the

New

&

Used

DbAltGrid

Figure 5: The dagCalendar component.

properties saved to a file. Duplicate grids can then be created by dropping a default grid on the form, associating the
dagBackup control with them, and then selecting Load from
the context menu. This is also a fast way of offering a user
variable views of the grid. During the development process,
you can lay out the grid in a variety of configurations, saving each one to a separate resource file. In the final distribution, clicking a button could then change a users default
table view to a more sophisticated record-type view.
The Price to Pay
All of this new functionality is wonderful, if you can figure out how to use it. However, DbAltGrid isnt shipped
with a manual or a users guide for getting started with
the product. An excellent help file is included with the
installation, and 1 provides you with immediate access
to the details of all properties and methods.
Where the documentation leaves the developer hanging is in
the area of learning to use the components within a project.
There are no examples embedded in the documentation, so
your only source of guidance is contained in the demonstrations that ship with the package. These program examples
work to your advantage if you can clearly determine what the
author is trying to demonstrate. Unfortunately, Quasidata was

Just the Facts


DbAltGrid is a great replacement for Delphis DBGrid component. It contains a variety of supporting components that
enable advanced data display features. The only drawback is
its sparse documentation.
Quasidata, Ltd.
121, 13 Novgorodskaya
Moscow, 127576 Russia
Phone: +7 902 631 0492
E-Mail: quasidata@quasidata.com
Web Site: www.quasidata.com
Price: US$145

35

DELPHI INFORMANT MAGAZINE | March 2003

Figure 6: The DbAltGrid footer panel stays in place during navigation.

bent on demonstrating every feature of DbAltGrid in as few


demonstration programs as possible. This makes it extremely
difficult to identify individual features you might want to
implement. Its also a bit of work to figure out where DbAltGrid ends and your coding responsibility begins.
More complications occur because of the use of another
Quasidata product, Transfer@once, in the demonstration code. This can make it difficult to determine if the
feature you are trying to use comes from DbAltGrid or
Transfer@once. (It should be noted that purchase and registration of the full DbAltGrid Suite includes Transfer@once
in the price.) Quasidata supports DbAltGrid through
e-mail, online forums, and a FAQ on their Web site
(www.quasidata.com). I was able to determine how to
implement a number of features through a combination
of the FAQ and forum postings, but the time invested in
researching could have been better spent on other tasks.
Conclusion
DbAltGrid is a well conceived and executed replacement
for the standard VCL DBGrid. The component suite
supports Delphi 4, 5, and 6, and C++Builder 4, 5, and
6. It installed automatically without error, and all of the
code ran flawlessly in the test programs. The coding is
tight (source is included in the package), and the compiled
projects performed nicely. Documentation of the methods
and properties is good, but lacks the examples needed by
a programmer new to the product. Developers will need to
set aside some time to experiment and learn how to use the
Suite before trying to implement most of the features. Other
than the weaknesses in the documentation, DbAltGrid is a
sound investment for the database programmer looking to
upgrade Delphis default DBGrid.

Warren Rachele is chief architect of The Hunter Group, a software-development


company in Evergreen, CO that specializes in database-management software.
He has written two books, Learn Object Pascal with Delphi and The Tomes of
Delphi: Win32 Database Developers Guide (both from Wordware Publishing).
Rachele also teaches programming, hardware architecture, data communications,
and database management at colleges in Colorado. Readers may contact him at
wrachele@earthlink.net.

T E X T F I L E

The Tomes of Delphi:


Basic 32-Bit Communications Programming

elphi developers pride themselves in knowing all they can


about their favorite language and
the APIs that it can manipulate. Two
such Delphi practitioners, Alan Moore,
Ph.D. and John Penman, eagerly share
their experience with Microsoft Windows communication libraries. Specifically, their book details the Winsock
library and the Telephony Application
Programming Interface (TAPI). As
with other Tomes of Delphi books published by Wordware, Basic 32-Bit Communications Programming describes
all the major functions of the subject
matter; each section teems with explanations and code samples.
Its really two books in one. The first
half, written by Delphi Informant
contributing author John Penman,
explores all things Winsock. The
Winsock API, Fundamentals, Communications, and Options, are each
explored in their respective chapters.
Additionally, both Winsock version
1.1 and 2 resolutions are discussed.
The authors are clearly indebted to
the hard work of Project JEDI (of
which co-author Moore is the director), the members of which helped
convert and test the Delphi headers
necessary to the language translations. Without Project JEDI, the
books code would have been considerably more cumbersome.
The second half, written by wellknown Delphi Informant contributing editor Alan C. Moore, Ph.D., is
devoted to TAPI. Moore expends a
bit more space on the explanation of
36

DELPHI INFORMANT MAGAZINE | March 2003

fields, parameters, and constants than


Penman, and the book is better for
it. TAPI topics covered include line
devices, handling line messages, placing outgoing and accepting incoming
calls, and a reference for additional
basic TAPI functions.
Three appendices provide a glossary
of terms, error code explanations, and
a programming resource bibliography,
respectively. Plenty of print and Web
articles have been published about
Winsock, with a ratio I estimate to be
at least 10-to-1 Winsock-to-TAPI. This
makes the TAPI information provided
here especially valuable. All of the
TAPI articles that Moore wrote for
Delphi Informant in the late 90s obviously paved the way for a superior
investigation of the Delphi-TAPI connection. Unlike Moores articles, the
book provides him with considerably
more space to explain the finer points
of TAPI, which allows him to spend
more time and code on the lesserknown TAPI functions. If you enjoyed
Moores TAPI articles but wish he had
dug deeper into the details, this book
grants your wish.
The book is bundled with a CD-ROM
containing Winsock and TAPI header
translations, as well as the code from
the book. Only users of Delphi 5 and
higher versions will benefit from the
book; the sample code and some of
the headers are not compatible with
earlier versions. Curiously, no commercial, shareware, or freeware communication-related components were
included on the CD.

The Tomes of Delphi: Basic 32-Bit


Communications Programming
by Alan C. Moore, Ph.D. and John
C. Penman, Wordware Publishing,
www.wordware.com.
ISBN: 1-55622-752-3
Cover Price: US$59.95
(555 pages, CD-ROM)

Probably the most notable drawback


to Basic 32-Bit Communications Programming is its cost especially if
a reader is only interested in one of
the two subjects. However, although
its possible that Wordware could
have published separate Tomes, its
understandable that the two vaguely
similar topics were bound into one.
And who knows? You may buy it for
Winsock information, only to land a
Delphi project that requires TAPI.

Mike Riley

F I L E

N E W

.NET: Great Hope or Great Hype

By Alan C. Moore, Ph.D.

or Microsoft, the .NET initiative


holds much hope; the company
has certainly promoted this brave new
development platform in many venues, including television. Borland has
also invested in this language-agnostic
technology, with plans to make Delphi
a major player in the .NET universe.
Although initial reactions among many
leading Delphi developers have been
generally positive, some developers
have doubts and concerns. Regardless
of how we may feel about it, .NETs
revolutionary approach to programming represents a fundamental shift
for Microsoft one that will require
us to learn new terms, concepts, and
approaches to programming.
Well begin our exploration of .NET by
concentrating on early developer reactions. Next month well examine printed
resources that enable us to get up to
speed. Well also discuss the major features of this important platform through
a glossary of some of the basic terms
and concepts that define it. Well explore
online resources in a third installment.
Competing technological approaches.
How does .NET fit into the world of
information technologies? A close
friend of mine, a systems analyst in
Iowa, put it very succinctly: Things
are going to come down to .NET vs.
Java, at least in the enterprise. Youre
going to have to be able to write in or
interface with one or the other, if not
both. Given the increased importance
of the Internet, distributed computing,
and Web Services, I agree.
Wisely, Borland supports both approaches. With JBuilder, Borland is already a
major player in the world of Java development; with Delphi for .NET, it hopes
to gain an increased share of future Win37

DELPHI INFORMANT MAGAZINE | March 2003

dows development. Its no accident that


the two major themes at the last Borland
conference were Java and .NET, with top
developers from each camp as keynote
speakers (James Gosling, the inventor of
Java, and Anders Hejlsberg, the former
Borland engineer who helped invent C#).
My friend concluded his message with
this prediction: .NET will become the
prime development platform for Windows and Java will continue to be a contender for other platforms, adding the
pointed question, Do you see Microsoft
porting .NET to Solaris?
Most developers agree that .NET will
define the future of Windows development. The Windows developers I spoke
with at the last Borland conference
seemed interested in talking about
nothing besides .NET. A manager at a
top Delphi tools company told me he
was requiring all his programmers to
learn C#. A contributor to an Internet
discussion from which I will quote
liberally put it this way: .NET is not
going to vanish just because many of
us dislike (or even hate) Microsoft and
the way they operate. I would much
rather see Borland produce topnotch
products to develop on .NET than
work with VB .NET or C# .NET. However, some Delphi people seem enamored with those Microsoft tools.
Developer reactions. Reactions to .NET
are as varied as the individuals who
make up the Delphi community. On one
extreme are those who will never give
Microsoft credit for doing anything right,
who always abbreviate the companys
name as M$. On the other extreme are
those who wait with baited breath for
each new Windows version or feature.
One Microsoft critic opined, Most
users Ive talked with have no inten-

tion of upgrading to the XP (.NET)


platform, and continued with a
complaint about a perceived need to
support two source code versions.
Although I question equating .NET
with XP (despite the dependence of
that new Windows version on this
technology), I do agree that moving to
the new development platform wont
be trivial. This will become quite clear
in next months column.
.NET is language agnostic but hardly OS agnostic. Indeed, it is intended
mainly for the Windows operating
system, but Microsoft has made some
moves toward porting .NET to the
Macintosh OS. For example, an article
on Macworlds MacCentral Online
(http://maccentral.macworld.com/
news/0107/20.net.shtml), reported
that Kevin Browne, general manager
of Microsofts Macintosh Business
Unit, said in a special Macworld
Expo New York presentation that Mac
users would be available to be part of
Microsoft .NET. When you consider
that Microsoft has ported many of its
products to the Macintosh OS, this
isnt a surprise. Interestingly, much of
the initial developer reactions I saw
compared the Visual Studio .NET IDE
with that of Delphi.
IDE wars. When I asked Lino Tadros
a question about Delphi and Visual
Studio .NET he answered, It is not
about languages anymore. It is now
the battle of the IDEs. A lively discussion, from which I will repeat
several messages, took place on the
Delphi Advocacy Group Mailing List
(http://www.egroups.com/links/tdag).
Interestingly, many of the .NET enthusiasts in that discussion spoke favorably of Visual Studio .NET, not the
.NET Framework itself.

File

New

Reporting on the VS .NET product


launch in Hong Kong, a fan of that tool
emphasized productivity features such
as collapsible code blocks, automatic
capitalization and indentation, and more.
Commenting on the usefulness of collapsible code blocks, another respondent
mentioned that they definitely help with
collapsing lengthy procedure/function/
method/event statements which might
improve the productivity of the developers. This is a feature I would love to see
in Delphis code editor.
But another developer pointed out
that Delphi has a lot of these things
already and can be customized to add
them. He and others recommended
CodeRush and GExperts as two sources
of Delphi IDE enhancements. He posed
this important question to VS .NET
fans: Does VS .NETs IDE allow you
to build in your own tools? The Open
Tools API in Delphi/C++Builder
allows that and is really cool. With the
Open Tools API, just about any of the
things you mentioned that Delphi does
not already do could be added.
Acknowledging the lack of an Open
Tools API in the Microsoft tool, the VS
.NET fan nevertheless praised the user

38

DELPHI INFORMANT MAGAZINE | March 2003

interface design. He pointed out that


it maximized the workspace of the
code window by auto hiding unnecessary stuff like the Toolbox, Server
Explorer and Solution Explorer. He
also liked the docking of the various
windows and their multiple tabs. Having worked with VS .NET a bit myself,
I particularly agree with his praise for
the products help window, which is
always visible. While I prefer Delphis
simpler user interface, I do think that
Borland should take another look at
the help system.
Of course, peoples views can change.
One former Microsoft critic said, I am
in love with .NET and VS .NET. As you
say, a lot of the things the VS .NET
IDE can do, Delphi can do too. I do
however feel as comfortable using the
VS IDE as I do the Delphi IDE. I used
to be a big anti-MS dude, but with the

release of .NET, Windows 2000 and


Windows XP [the company] is really
starting to put a sparkle in my eyes.
Some of the other contributors to the
discussion agreed.
In my view, the most important aspects
of .NET have nothing to do with VS
.NET or its IDE. But discussions like the
one I reported above should give Borland and third-party tool makers some
clues about perceived shortcoming in
the Delphi IDE. The important parts of
.NET are the framework and the new
approach to executing code. The move
from APIs to namespaces, and the ability of disparate programming languages
to reference each others code are
significant. Well concentrate on these
aspects of .NET next month, examining
some of the .NET books available, and
presenting a lexicon of .NET terms and
concepts. Until then...

Alan Moore is a professor at Kentucky State University, where he teaches music theory and humanities.
He was named Distinguished Professor for 2001-2002. He has been named the Project JEDI Director for
2002-2003. He has developed education-related applications with the Borland languages for more than 15
years. Hes the author of The Tomes of Delphi: Win32 Multimedia API (Wordware Publishing, 2000) and
co-author (with John 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 on the Internet at acmdoc@aol.com.

Das könnte Ihnen auch gefallen