February 1999, Volume 5, Number 2
Cover Art By: Darryl Dennis
ON THE COVER
6 Searching for Records — Cary Jensen, Ph.D. 33 Greater Delphi
Dr Jensen takes on a topic nearly every Delphi developer can benefit from: The BDE Made Easy — Bill Todd
efficient data retrieval. Three general approaches are described and tested Mr Todd describes an extraordinarily useful — and economical —
for speed: sequential searches, TDataSet search methods, and parameter- technique for sharing the Borland Database Engine over a network,
ized SQL SELECT queries. The time-trial results may surprise you. while retaining maximum flexibility from application to application.
FEATURES 38 At Your Fingertips
10 Informant Spotlight They Were There All Along — Robert Vivrette
MTS Development: Part III — Paul M. Fairhurst Uncovering some useful gems from the SysUtils unit, Mr Vivrette shares
Concluding his three-part series on Microsoft Transaction Server, Mr tips for handling command-line parameters, searching for multiple files,
Fairhurst turns to some advanced aspects of MTS, such as security, and replacing multiple instances of text.
DCOM, transactions, callbacks, and more.
17 Algorithms
Hash It Out — Rod Stephens
Mr Stephens provides three Delphi implementations of hash tables, REVIEWS
data structures that allow you to quickly store and retrieve items 40 LEADTOOLS Imaging 10
based on a key. Product Review by Warren Rachele
23 Columns & Rows 44 Delphi 4 Bible
Multi-tier Database Applications: Part II — Book Review by Warren Rachele
Thomas J. Theobald
Last month, Mr Theobald described the steps for planning and building
multi-tier database applications. This month, he turns to the implemen-
tation, i.e. get set for some code. DEPARTMENTS
2 Delphi Tools
28 The API Calls 4 Newsline
For Your Eyes Only — Mujahid Beg
Mr Beg demystifies the Microsoft Cryptographic Application 45 File | New by Alan C. Moore, Ph.D.
Programming Interface (CryptoAPI for short), and shares a
tCryptography class to make its use from Delphi easier.
1 February 1999 Delphi Informant
Delphi PRICE Systems Announces ForeSight 2.0
PRICE Systems, L.L.C. 2.0 include more implementa- Systems, and Telecommuni-
T O O L S announced ForeSight 2.0, the tion tools, including complexi- cations applications; and Auto
newest version of its project ty profiles for Delphi, Visual Lock ability to protect entered
New Products management software solution J++, PowerBuilder, JavaScript, or changed values and unlock
and Solutions for forecasting time, effort, VBScript, HTML, and these values.
and costs for commercial and FrontPage; more application
non-military government soft- types in the Project Wizard PRICE Systems, L.L.C.
ware projects. selection screen, including Price: US$975 per single-user license;
The Project Wizard includes Internet, Text Processing, bulk rate and site license pricing are
a Quick Estimate feature, Database, Human Resources, available.
which enables users to develop Logistics, Management, Office Phone: (800) 43-PRICE
a first-glance forecast by Productivity, Operating Web Site: http://www.pricesystems.com
answering a few critical ques-
tions. This estimate may be HyperAct Announces eAuthor Help 3.05
used to specify a project’s costs HyperAct, Inc. announced controls for Delphi,
as new information becomes eAuthor Help 3.05, the com- C++Builder, Visual Basic,
Mastering Delphi 4
Marco Cantù
available. pany’s template-based RAD MFC, and other environ-
SYBEX The new version includes a authoring tool for HTML ments.
Microsoft Project 98 interface, Help, hard-copy documents, New templates are created
which uses Component Web sites, and HTML-based easily with the included tem-
Object Model (COM) archi- e-mail messages. plate composer, including
tecture. Using this interface, eAuthor Help 3.02 includes wizards. The product also
users can interface any a WYSIWYG editor, HTML includes a comprehensive
ForeSight 2.0 project created code editor, hierarchical pro- SDK for software integration.
with Project 98. COM further ject view, instant preview,
enables ForeSight 2.0 to oper- object inspector, and proper- HyperAct, Inc.
ate within an integrated enter- ty editors. The product Price: US$250
prise environment. comes with royalty-free Phone: (402) 891-8827
ISBN: 0-7821-2350-3
Price: US$49.99 (1,247 pages)
Other benefits of ForeSight HTML Help deployment Web Site: http://www.hyperact.com
http://www.sybex.com
Pervasive Announces Developers Kit
Pervasive Software Inc. scripting languages or multi- Workstation Engine,
announced the Pervasive.SQL layered data access methods. Developer’s Resource Center,
Software Developers Kit Applications built on ODBC Driver, and a Java
(SDK), a set of rapid applica- Pervasive.SQL integrate with Class Library.
Charlie Calvert’s tion development resources, third-party database controls,
Delphi 4 Unleashed including the I*net Data such as APEX True DBGRID Pervasive Software Inc.
Charlie Calvert
SAMS Publishing Server, ActiveX controls, a and Sheridan Data Widgets. Price: US$295
pure Java API, and support Pervasive.SQL SDK also Phone: (800) 287-4383
for Windows development features the Pervasive.SQL Web Site: http://www.pervasive.com
environments to speed
development of applica-
tions based on Pervasive’s
embedded database
engine.
The I*net Data Server
utility lets developers
write Pervasive.SQL-
ISBN: 0-672-31285-9 based applications and
Price: US$49.99
(1,152 pages, CD-ROM) run them as Web- or
http://www.samspublishing.com Internet-based
client/server solutions.
Developers can create a
single-user workstation,
as well as Internet-based
mobile applications,
without cumbersome
2 February 1999 Delphi Informant
Lingscape Announces MultLang Suite 2.11 for Delphi and C++Builder
Delphi Lingscape Ltd. announced made from other products or MultLang Suite 2.11 works
T O O L S
MultLang Suite 2.11, a new implementations, such as with Delphi 2, 3, and 4 and
version of the company’s Inprise Delphi Translation C++Builder 1 and 3.
New Products
globalization tool for Delphi Suite. You may instantly turn
and Solutions
and C++Builder. these into multi-language Lingscape Ltd.
MultLang is based on the projects without writing Price: US$998
Unicode standard, and, com- additional code. Web Site: http://www.lingscape.com
bined with a conversion
engine, provides support for Wise Introduces Installation Suites
Japanese, Chinese, Arabic, Wise Solutions, Inc. advanced features, including
Hebrew, Hungarian, Russian, announced three new instal- an integrated debugger,
and all European languages. lation suites: InstallMaker, built-in Windows API call-
The integration with the InstallBuilder, and ing, and a script editor.
IDE and compiler helps pro- InstallMaster. New features InstallMaster is for profes-
duce thin, localized EXEs, or include enhancements for sional developers who want
link multi-language support installation scripting, soft- complete control over their
into the same EXE. ware patching, repackaging, installations. InstallMaster
The Universal Language and Web deployment. includes Wise Installation
Modules API can contain InstallMaker provides System 7.0 and advanced
context-sensitive dictionaries point-and-click steps that features, including custom
that are self-describing and allow developers to create dialog and graphics editing;
have their own interface. basic Windows installation SetupCapture for repackag-
The MultLang Suite 2.11 programs in minutes without ing other installations into
contains a quality assurance writing any code. Wise scripts; and
Absolute Solutions Releases
ShortcutBar for Delphi 1.65 wizard, which keeps projects InstallMaker includes Wise WebDeploy for efficient
Absolute Solutions released from showing defect user Installation System 7.0 plus installations from a Web
ShortcutBar for Delphi 1.65, a interfaces, and checks for SmartPatch, which creates site or intranet.
full implementation of a
Microsoft Outlook bar for workable short cuts, correct compact installation patches.
Delphi. It features unlimited character set displayed, InstallBuilder builds more Wise Solutions, Inc.
groups, unlimited shortcuts per approval of translated sophisticated installations, Price: InstallMaker, US$199;
group, scrolling groups and
shortcuts, reactive drag-and- works, etc. with script-writing capability. InstallBuilder, US$399; InstallMaster,
drop, small and large shortcuts, This new version has a InstallBuilder includes Wise US$799.
and shortcut re-ordering. migration utility to collect Installation System 7.0, Phone: (800) 554-8565
ShortcutBar is available for
Delphi 3 and 4, and comes and re-use globalizations SmartPatch, and other Web Site: http://www.wisesolutions.com
complete with integrated help
and a sample application. Automagic Ships Y2K Components
For more information, visit the
Absolute Solutions Web site at Automagic Software aware components also provided on an as-is
http://dspace.dial.pipex.com/ announced its suite of Y2K (TascY2KDBCombobox and basis. The Y2K suite of
absolutesolutions. Delphi Components. Using TascY2KDBEdit) and two components supports
these components, develop- non-data-aware compo- Delphi 1, 3, and 4.
ers can ensure the explicit nents (TascY2KCombobox
entry of four digits to rep- and TascY2KEdit). Automagic Software
resent the year. The suite is ActiveX versions of the Price: US$99
comprised of two data- non-data-aware controls are E-Mail:
[email protected] WetStone Announces SMARTCrypt 1.2
WetStone Technologies, Cryptographic Standard that support ActiveX or
Inc. announced (PKCS) #11. OCX controls.
SMARTCrypt 1.2, an SMARTCrypt provides SMARTCrypt is compatible
ActiveX security control that abstracted functionality for with Windows 95/98/NT.
allows developers to build file signing, file encrypting,
applications that employ and key management. The WetStone Technologies, Inc.
SmartCards and crypto- SMARTCrypt component Price: US$995 for single-user license;
graphic tokens. integrates with Delphi, US$2,995 for site license.
SMARTCrypt 1.2 abstracts Microsoft Visual J++ and Phone: (607) 539-9981
the RSA Public Key Visual Basic, and other tools Web Site: http://www.wetstonetech.com
3 February 1999 Delphi Informant
Delphi Sylvan Ascent Introduces SylvanMaps/OCX-3
Sylvan Ascent, Inc. released Sylvan Ascent, Inc. Phone: (800) 362-8971 or
T O O L S
SylvanMaps/OCX-3, the Price: From US$495 (Import/Export (505) 986-8739
company’s embedded map- ActiveX control only). Web Site: http://www.sylvanmaps.com
New Products
ping controls. This
and Solutions
release features a caching
system that increases
redrawing speed, support
for ADO, geo-referenced
raster images, on-the-fly
projection conversion,
and file translations.
There’s early testing sup-
port for OpenGIS, and
address geocoding has
been improved.
Black Diamond Offers Components for Delphi
Black Diamond Software, text from a template that is from the version resource of
Inc. introduced DataAware filled in, either programmat- an application or dynamic
toolsfactory Announces Class and Special-Purpose compo- ically or from a database library; and visually create
Explorer for Inprise Tools nents for Delphi. query similar to a word collections for use in their
toolsfactory, a provider of Three new DataAware processor mail-merge. applications.
object-oriented class manage-
ment development tools, components (BDSIncSearch, Three Special-Purpose
announced ClassExplorer Pro BDSTempTable, and components Black Diamond Software, Inc.
2.1, an integrated software BDSTemplateStringList) (VersionResource, EventLog, Price: DataAware Bundle, US$179, or
development tool that provides
object-oriented code navigation, enable Delphi developers to and CollectionExpert) US$269 with source; Special-Purpose
creation, and documentation for incrementally search a enable Delphi applications Bundle, US$169, or US$259 with source;
Inprise’s Delphi and C++Builder DataSet; manipulate Sybase to read and write messages components are also available separately.
development environments.
ClassExplorer Pro 2.1 supports or Microsoft SQL Server to the Windows NT Event Phone: (203) 431-9600
features such as Class View and temporary tables; and create Log; extract information Web Site: http://www.bds.com
Class Hierarchy, which simplify
the source view and class navi-
gation of complex projects. Class
4Developers LLC Announces COM Explorer 1.5
member creation features simpli- 4Developers LLC files using an Explorer-like EXE Servers. The software
fy the creation of methods, prop- launched COM Explorer user interface. provides a centralized view
erties, and fields to classes.
Code documentation features 1.5, a tool designed to help COM Explorer’s interface of COM objects, as well as
offer completely customizable developers and system enables users to view, man- detailed information, such
automatic online help generation administrators explore, age, and repair ActiveX as GUID, TypeLib, version,
from source code, including
indexes and hierarchical tables. manage, and repair COM Controls, DLL Servers, and and file information.
toolsfactory will be working in An inventory report
cooperation with Inprise to pro- generator allows devel-
vide integrated companion solu-
tions for Inprise development opers to generate HTML
tools. For more information, visit or comma-delimited text
the toolsfactory Web site at reports that include a list
http://www.toolsfactory.com.
of the COM objects
installed. The report
generator supports
strong filtering and cus-
tomization options.
4Developers LLC
Price: Single license, US$129;
site license, US$350; corporate
license, US$650.
Phone: (877) 353-7297
Web Site: http://www.
4developers.com
4 February 1999 Delphi Informant
News JBuilder 2 Wins Two Awards
New York, NY — Inprise
Corp. announced that
VisiBroker for Java was
named a finalist in the best
Choice Awards. The awards
were given out at the Java
L I N E JBuilder 2, its family of visu- General Class Library catego- Business Expo trade show in
al development tools for cre- ry in the JavaWorld Editors’ New York City.
February 1999 ating Java business and data-
base applications for the Inprise Announces Brazilian Subsidiary
enterprise, has won two Sao Paulo, Brazil — Inprise José Rubens M. Tocci, direc-
awards at Java Business Expo: Corp. announced it had tor of Engine Informatica, will
the Editors’ Choice Award signed a letter of intent to head the Brazilian subsidiary
from the Java Developer’s acquire Engine Informatica as Country Manager. He will
Journal for Best Java Ltda., its Sao Paulo, Brazil- report directly to Kurt A.
Development Environment, based partner and distributor. Heck, Inprise’s general manag-
and the Editors’ Choice Pending final contract resolu- er of Latin America. Inprise do
Award from JavaWorld maga- tion, Engine Informatica will Brasil’s primary focus will be
zine for Best Integrated become a wholly owned to deliver solutions-oriented
Development Environment. Inprise subsidiary, called sales and services to the enter-
In addition, Inprise’s Inprise do Brasil, Ltda. prise market, as well as provide
local support for the retail sales
Inprise Offers to License JBuilder to Microsoft channel. It will immediately
Tokyo, Japan — Inprise JDK 1.2, JFC/Swing compo- assume responsibility for all
Corp. announced an offer to nents, JavaBeans, Enterprise existing partner relationships
license its JBuilder Java devel- JavaBeans, and JDBC, many in Brazil, leveraging the key
DPR Launches New relationships that Inprise
Delphi Courses
opment tools to Microsoft of which may be issues in
Database Programmers Retreat Corp. This offer is in response developing and deploying maintains with Visionnaire
(DPR) announced two new cours- to a U.S. district court’s platform-independent Java (VisiBroker product line), and
es aimed at experienced Delphi retail partners PARS and
developers: Web Application
injunction against Microsoft solutions with Microsoft’s
Development with Delphi and requiring the company to con- Visual J++. IngramMicro.
Delphi 4 Multi-tier Development form to Sun Microsystems’
with MIDAS. The courses will be
Pure Java specification. Inprise to Acquire Apogee Information Systems
held at monthly intervals at the
company’s training center in Inprise has worked closely Scotts Valley, CA — Inprise ness processes. Apogee clients
Gloucestershire. as a partner to Sun and Corp. announced it has include Beloit Corporation,
Web Application Development acquired Apogee Dun & Bradstreet, Sheraton
with Delphi covers the design,
Microsoft. Inprise helped
coding, and implementation of a Sun develop the JavaBeans Information Systems, Inc., a Hotels, Bay Networks (Nortel),
Web-enabled application using specification, and was first privately held enterprise sys- and the Massachusetts
HTML, client-side Java scripts, and tems integration and con- Department of Revenue.
CGI. This is a five-day course and
to ship a development envi-
costs US$1,995 per delegate. ronment with support for sulting firm based in Designed to assist corporate
Delphi 4 Multi-tier Development 100% Pure Java and JDK Marlboro, MA. As a result, customers and systems-integra-
with MIDAS focuses on the issues Apogee will become part of tion partners develop and
involved in creating multi-tier data-
1.1 with the initial release
base applications. Participants will of JBuilder 1.0. Inprise’s Professional Services implement enterprise solutions,
learn how to create a complete The JBuilder product fami- organization. Inprise Professional Services
multi-tier application from scratch, Using multi-tier information integrates the company’s con-
and how to convert an existing
ly supports Sun’s Pure Java
database application to multi-tier. specifications, including Java systems, the 22-person compa- sulting, training, and technical
This is a three-day course and Native Interface, RMI, Java ny assists global clients with the support expertise into a single,
costs US$1,295 per delegate. integration of data and busi- world-wide organization.
For more information, call
event handling, JDK 1.1.x,
(800) 279-9717 or
+44 (0) 1452 814 303, or visit InterBase Releases InterBase 5.5
the DPR Web site at Scotts Valley, CA — support for user-specified Windows, and the UDF
http://www.dp-retreat.com.
InterBase Software Corp. character sets. library has been expanded.
announced InterBase 5.5, a Stability in InterBase 5.5 Performance enhancements
new version of the compa- has been improved by adding include more efficient mem-
ny’s embedded database. such features as protection ory usage and a multi-thread-
InterBase 5.5 includes for online metadata updates ed ODBC 3.0 driver.
InterClient 1.5, a high-speed of Triggers and Stored For pricing and other infor-
JDBC driver that connects Procedures by the InterBase mation, call (888) 345-2015
Java applications and applets 5.5 versioning engine. User or (831) 431-6500, or visit
to InterBase databases, and Defined Functions (UDFs) the InterBase Web site at
adds direct international have added safety features in http://www.interbase.com.
5 February 1999 Delphi Informant
On the Cover
Data Retrieval / Queries / TDataSet
By Cary Jensen, Ph.D.
Searching for Records
Delphi Database Development: Part VI
I n last month’s “DBNavigator,” you learned how to use DataSets and TField
objects to navigate and edit data. The series continues this month with a look
at record-searching techniques. Record searching refers to the process of quick-
ly locating a record based on data stored in that record, e.g. searching for a
particular record in a customer account table based on an account number, or
finding an invoice based on the date of purchase.
There are three general approaches to record search times increase in direct proportion to
searching, which are demonstrated in the fol- the number of records in your DataSet.
lowing sections:
sequential searches The use of a sequential search on the
TDataSet search methods CUSTNO field of the CUSTOMER.DB
parameterized SQL SELECT queries table is demonstrated in the example
SEQUENCE project (all example projects
Sequential Record Searches for this article are available for download;
The first, and often least desirable, searching see end of article for details). The main
technique is sequential searching. Sequential form for this project is shown in Figure 1.
searches are performed by scanning a DataSet,
record by record, testing each record for a The code shown in Figure 2 is attached to the
value or values. Sequential searches can’t make Find button on this project’s main form. There
use of indexes to improve the speed with is a technique used in this code worth men-
which a given record is found, and average tioning. Specifically, a TField variable is
declared, and it’s assigned to the TField object
returned by the FieldByName method. This
variable is then used throughout the event
handler to reference the LastName field in the
table. As you learned in last month’s
“DBNavigator,” the use of FieldByName intro-
duces a slight performance penalty compared
with the direct access achieved through the use
of the Fields property. However, by using a
variable to hold the TField returned by
FieldByName, the overhead of FieldByName is
incurred only once, while maintaining the
code readability afforded by FieldByName.
Normally, sequential searches are only used
with local tables, including Paradox and
dBASE tables. When a remote database is
Figure 1: The SEQUENCE project demonstrates a sequential search. involved, sequential searches are typically
6 February 1999 Delphi Informant
On the Cover
procedure TForm1.Button1Click(Sender: TObject);
whether the search will be case-sensitive, as well as whether to
var perform a partial match on the last search field. Locate has
SearchField: TField; the following syntax:
begin
SearchField := Table1.FieldByName('LastName');
Table1.DisableControls; function Locate(const SearchFields: string;
try const SearchValues: Variant; Options: TLocateOptions):
Table1.First; Boolean;
while not Table1.EOF do begin
if SearchField.Value = Edit1.Text then begin
StatusBar1.SimpleText := Locate requires three parameters. The first is a string that lists
'Located Employee ' + Edit1.Text;
Exit;
the field or fields being searched. If the search is being per-
end; formed on more than one field, the field names are separated
Table1.Next; with semicolons.
end;
StatusBar1.SimpleText := 'Could not find '+Edit1.Text;
finally The second parameter is a variant containing the value or
Table1.EnableControls; values for which to search. If the first parameter specifies a
end;
single field, this second parameter can be either a variant or
Figure 2: Code associated with the Find button on the any valid expression type (variable, constant, literal, etc.). If
SEQUENCE project main form. more than one field is listed in the first parameter, this sec-
ond parameter must be a variant array.
The third parameter is a set that includes zero, one,
or both of the following flags: loPartialKey and
loCaseInsensitive.
When you invoke Locate, it first checks for an
index that includes the field or fields you listed
in the first parameter. If one is found, Locate
uses that index; otherwise it performs a sequen-
tial search. If a single field is being searched,
Locate attempts to find the first record (the order
being based on the identified index order) that
contains the data specified in the second para-
meter within the field named in the first para-
meter. If more than one field is listed, Locate
attempts to find the record where the first
Figure 3: The example LOCATE project. named field contains the value specified in the
first element of the variant array, the second
only performed if the table being searched is very small. named field contains the value in the second element of
Performing sequential searches on remote tables requires that the variant array, and so on.
every search record be retrieved from the server. When the
remote table is large, a sequential search can have a large neg- If the third parameter includes the loCaseInsensitive flag, the
ative impact on application performance and network traffic. comparison of string fields is case-insensitive. When the flag
loPartialKey is included in the third parameter, the last field
TDataSet Record-searching Methods in the field list must be a string field, and is considered a
It’s usually better to use a searching method instead of match if the search field contains data that begins with the
sequential searches, except when your table is very small. same characters specified in the corresponding search value.
There are two primary searching methods: FindKey and
Locate. FindKey is available for Table components, and Locate If a matching record is found, Locate repositions the cursor to
is available for all DataSets. the located record and returns a value of True. If no match is
found, the cursor doesn’t move, and Locate returns False. The
The primary advantage of searching methods is that they can LOCATE project demonstrates the use of the Locate method
use a table’s index to perform nearly instantaneous searches. (see Figure 3). The code in Figure 4 is associated with the
Unlike sequential searches, where the time it takes to perform OnClick event handler of the Find button.
a search is directly proportional to the number of records in
the table, search methods are largely unaffected by table size. While Locate is the easiest searching method to use, it’s
only available in 32-bit versions of Delphi. If you need to
The most flexible of the searching methods is Locate. Locate create 16-bit applications, you must rely on FindKey.
permits you to search on one or more fields, and to choose FindKey is only available for Table components, and it
7 February 1999 Delphi Informant
On the Cover
only operates on the current index. FindKey has the fol- Searching with Parameterized Queries
lowing syntax: The third search technique involves the use of parameter-
ized queries. A parameterized query is one that includes
function FindKey(const SearchValues: array of const): one or more variables, referred to as parameters, in a SQL
Boolean; SELECT statement’s WHERE clause. By changing the
value of one or more parameters, you change which
The FindKey method requires a single parameter consisting records are affected by the query. For example, consider
of an array of values to search. The first element in the array the following SELECT statement:
is compared with data in the first field of the current index,
the second element of the array is compared with the second SELECT * FROM CUSTOMER
field of the index, and so on. Similar to Locate, if a match is WHERE (COUNTRY = :CTRY AND CITY = :CITYNAME)
found, the cursor is moved to the matching record, and the
value True is returned. When no match is found, the cursor The two parameters in this query are CTRY and CITYNAME.
doesn’t move, and FindKey returns False. Note that these parameters are identified in the SQL state-
ment by preceding their parameter names with a colon.
Another method similar to FindKey is FindNearest. This Assuming the preceding SQL statement is assigned to the
method, which also takes an array of search values as its sole SQL property of a query named Query1, the following
parameter, moves the cursor to the record that best matches statements assign values to the two parameters, and then
the search array. Unlike FindKey, FindNearest always finds a execute the query:
match, and, therefore, always moves the cursor.
Query1.Close;
procedure TForm1.Button1Click(Sender: TObject); Query1.ParamByName('CTRY').Value := Edit1.Text;
var Query1.ParamByName('CITYNAME').Value := Edit2.Text;
SearchList: Variant; Query1.Open;
SearchOptions: TLocateOptions;
begin
SearchList := VarArrayCreate([0,1],VarVariant);
SearchList[0] := Edit1.Text; If you read the second installment in this series (“Delphi
SearchList[1] := Edit2.Text; Database Development Part II: Tables, Queries, and Stored
SearchOptions := []; Procedures” in the October, 1998 issue of Delphi
if CaseInsensitiveCheckBox.Checked then
SearchOptions := SearchOptions + [loCaseInsensitive]; Informant), you may recall that parameterized queries need
if PartialKeyCheckBox.Checked then to be prepared only once before being executed the first
SearchOptions := SearchOptions + [loPartialKey]; time. Subsequent executions of parameterized queries that
if Table1.Locate('COUNTRY;CITY',
SearchList, SearchOptions) then have been explicitly prepared are much faster. You may
StatusBar1.SimpleText := 'Match found' also recall that if you fail to explicitly prepare a query,
else Delphi will prepare it for you, but will also unprepare the
StatusBar1.SimpleText := 'No match found';
end; query when it’s closed. As a result, whenever you use para-
meterized queries, it’s essential that you explicitly prepare
Figure 4: The OnClick event handler of the Find button on the them before their first execution, and unprepare them once
LOCATE project.
they’re no longer needed.
The use of a parameterized query is
demonstrated in the project named
PARAMQRY, shown in Figure 5. The
query used in this project is explicitly
prepared before its first execution. This
task is performed with the following
code, which appears on the query’s
BeforeOpen event handler:
if not Query1.Prepared then
Query1.Prepare;
and it’s explicitly unprepared with the fol-
lowing code from the form’s OnClose event
handler:
Query1.Close;
if Query1.Prepared then
Figure 5: The PARAMQRY project demonstrates the use of a parameterized query. Query1.UnPrepare;
8 February 1999 Delphi Informant
On the Cover
The COMPARE project provides you with a means
of testing the relative performance of sequential
searches, FindKey, Locate, and parameterized queries
(see Figure 6). After selecting an alias and a table,
clicking the Load Search Array button causes the appli-
cation to load an array with 10 values selected at ran-
dom from the first field of the specified table, and to
construct a parameterized SQL SELECT statement.
Clicking one of the other buttons performs the speci-
fied search repeatedly on the first field of the selected
table, using the values in the array (again, chosen at
random) as the value being searched for. After the
search has been performed the specified number of
repetitions, the number of milliseconds required for
the test is displayed above the corresponding button.
I ran this example many times on a wide range of
Figure 6: The COMPARE project tests relative performance of search techniques. tables and databases, and the images in Figures 6 and 7
are representative of what I observed. Figure 6 shows
the results from the 15-record CUSTOMER table
from the InterBase EMPLOYEE.GDB database that
ships with Delphi. With this small table, the sequential
search was significantly faster than any other tech-
nique, taking about one millisecond on average to
locate a given record. The query was the slowest.
Figure 7 displays the results from a table of more than
100,000 records (it was generated by duplicating the
CUSTOMER table records over 6,700 times, gener-
ating unique customer numbers for each record).
With the large table, the sequential search was disas-
trous, taking more than one hour to perform the 100
searches. Both FindKey and Locate required approxi-
mately 700 milliseconds to perform the 100 searches.
Interestingly, the parameterized query was nearly 30
Figure 7: The results from a table of more than 100,000 records. percent slower than the search methods, requiring just
over one second to perform the 100 searches.
You’ll notice from Figure 5 that the SELECT statement
returns only the record or records that satisfy the WHERE Conclusion
clause. This effect is different from what is produced by the Sequential searches are the fastest technique when working with
search methods Locate and FindKey, which change the cursor a small number of records, but are quickly becoming a poor
position in the database. It’s important to keep this differ- choice as the size of the table increases. FindKey and Locate, by
ence in mind when choosing one technique over the other. comparison, are relatively unaffected by table size, providing for
consistently fast performance. Ironically, parameterized queries
Searching Performance were always slower than the search methods, but still fast and
The three searching techniques described here each use a largely uninfluenced by the number of records in the table. ∆
different mechanism to select a record. They also differ in
performance. This begs the question: Which technique The files referenced in this article are available on the Delphi
results in the best performance? Informant Works CD located in INFORM\99\FEB\DI9902CJ.
Logic would suggest that sequential searches will be the
slowest, parameterized queries the fastest, and the search- Cary Jensen is president of Jensen Data Systems, Inc., a Houston-based database
ing methods somewhere in between. But what is logical development company. He is co-author of 17 books, including Oracle JDeveloper
isn’t always correct. It’s been my experience that if you’re [Oracle Press, 1998], JBuilder Essentials [Osborne/McGraw-Hill, 1998], and
Delphi in Depth [Osborne/McGraw-Hill, 1996]. He is a Contributing Editor of
really concerned about performance, you need to do some
Delphi Informant, and is an internationally-respected trainer of Delphi and Java.
direct comparisons and determine empirically which of For information about Jensen Data Systems consulting or training services, visit
your options is best. That’s just what I did with these http://idt.net/~jdsi or e-mail Cary at
[email protected].
search techniques.
9 February 1999 Delphi Informant
Informant Spotlight
MTS / Security / Delphi 4
By Paul M. Fairhurst
MTS Development
Part III: Security, DCOM, and More
I f you’ve been following this series on Microsoft Transaction Server (MTS),
you’ll know that we’ve built a simple three-tier application to provide online
banking to customers of The Delphi Bank, a fictitious bank. We used a Paradox
database for the back-end, a set of middle-tier MTS components supporting our
business framework, and a standard Delphi application for the user interface.
So far, we’ve used only a user ID and password to provide user access to the
application and business services. MTS offers a much more sophisticated envi-
ronment for security than this, upon which we can capitalize.
In this third and final part of this series, and Transactions) has its own copy of a data
we’re going to look at the more advanced module descended from a common data
aspects of MTS. The first thing we’ll do is module named TDmDelphiBankCommon.
implement better security. The other limi- The methods to create and validate a client
tation has been that the client application key are located here. The Logon method calls
must be run on the same machine as the CreateClientKey, which allocates a new key
server components. We’ll fix this by allow- and associates the current system time with
ing the client to run remotely over a it. All the other objects’ methods make a call
DCOM connection. Finally, we’ll discuss to IsClientKeyValid — with the client key
client-side transactions, callbacks, refer- that the client passed in — before they per-
ences, and activities. form any actions. In this method, the current
system time is checked against the known
The Client Key client keys and their times. For the client key
In Part II, we developed a client application to be valid, it has to be known, and the dif-
that accessed four objects (Customers, Accounts, ference of the current system time and the
AccountTypes, and Transactions) in our client-key time can’t be more than a preset
DelphiBankServer2 component. Almost every amount (five minutes by default).
method had a ClientKey parameter obtained
from the Logon method of the Customers object. This is all straightforward, but where do we
This key is meant to provide timed-out access store a list of client keys in a bunch of state-
to the server objects. If the client application less MTS objects? We could store them in the
doesn’t access one of our MTS objects for a cer- database, but let’s think about the problem a
tain amount of time after logon, the client key little. We could be dealing with hundreds of
is invalidated. Without a valid client key, meth- calls a minute to the IsClientKeyValid
ods in the objects won’t proceed, and activity is method, because it’s called at the start of
disallowed. This is a simple, but effective, form nearly all methods in all objects. We also
of security that wasn’t fully implemented in need to consider the amount of database
Part II, so let’s see how it’s done. access on a potentially very active table that
would store these values. A table like this
As I’ve already mentioned, client keys are cre- would be a hot spot of activity, and present a
ated by the Logon method of the Customers bottleneck as the components scale upward.
object. Remember that each of the four MTS provides the solution to this problem:
objects (Customers, Accounts, AccountTypes, the MTS Shared Property Manager (SPM).
10 February 1999 Delphi Informant
Informant Spotlight
function TdmDelphiBankCommon.IsClientKeyValid(
function TdmDelphiBankCommon.CreateClientKey: Integer;
ClientKey: Integer; var strDebug: WideString): Boolean;
var
var
spgClientKeys : ISharedPropertyGroup;
spgClientKeys : ISharedPropertyGroup;
spClientKey : ISharedProperty;
spClientKey : ISharedProperty;
wsPropertyName : WideString;
nTickCountNow : Integer;
bAlreadyExists : WordBool;
begin
begin
Result := False;
try
strDebug :=
{ Create property group. }
'Connection has timed out. Please logon again.';
spgClientKeys :=
try
CreateSharedPropertyGroup(cDelphiBankClientKeys);
{ Create property group. }
spgClientKeys :=
if not Assigned(spgClientKeys) then
CreateSharedPropertyGroup(cDelphiBankClientKeys);
raise Exception.Create(
if not Assigned(spgClientKeys) then
'Could not create client key property group');
raise Exception.Create(
{ Create the property. }
'Could not create client key property group');
bAlreadyExists := False;
{ Look for the property. }
while not bAlreadyExists do begin
spClientKey := spgClientKeys.PropertyByName[
Result := GetTickCount;
'CK' + IntToStr(ClientKey)];
wsPropertyName := 'CK' + IntToStr(Result);
if not Assigned(spClientKey) then
spClientKey := spgClientKeys.CreateProperty(
Exit;
wsPropertyName, bAlreadyExists);
{ Check value. }
end;
nTickCountNow := GetTickCount;
if (Abs(nTickCountNow) - Abs(spClientKey.Value)) <=
if not Assigned(spClientKey) then
(cClientKeyTimeout * 1000) then
raise Exception.Create(
begin
'Could not create client key');
Result := True;
{ Set the value. }
strDebug := '';
spClientKey.Value := Result;
end;
except
except
on E: Exception do
on E: Exception do begin
raise Exception.Create(
strDebug := 'TdmDelphiBankCommon.IsClientKeyValid() -
'TdmDelphiBankCommon.CreateClientKey() - ' +
Client Key of "' + 'CK' + IntToStr(ClientKey) +
E.Message);
'" -> ' + E.Message;
end;
raise Exception.Create(strDebug);
end;
end;
end;
Figure 1: The CreateClientKey method in action.
end;
Figure 2: The IsClientKeyValid method.
Sharing State
The SPM is a resource dispenser you can use to share state variant value. Delphi provides two ways into the SPM: the
among multiple objects within a server process. This means CreateSharedPropertyGroup function, which creates a group
that any objects in the same package can share state with with a specified name and default features; or, for maxi-
each other. This is incredibly useful for stateless objects. The mum control, the CreateSharedPropertyGroupManager
SPM provides shared property groups, which establish method, which returns an ISharedPropertyGroupManager
unique name spaces for the shared properties they contain. interface you can use to create a group with extra features.
You categorize the properties you want to store into groups, We’ll use the former to create a group where we can store
then store properties in the groups as name/value associa- our client keys.
tions. Concurrency is taken care of because the SPM also
implements locks and semaphores to protect your properties Figure 1 shows the CreateClientKey method in action (all
from simultaneous access (which could result in lost source discussed in this article is available for download;
updates, and could leave the properties in an unknown see end of article for details). The first line creates the
state). Shared properties are held in memory and, therefore, shared property group with the name of the constant
have a very fast access time. The downside is that they dis- cDelphiBankClientKeys (DelphiBankServer3ClientKeys). If
appear when MTS is shut down and restarted, so don’t rely the group already exists (which it will most of the time),
on them for persistent data storage; they’re geared toward then an interface is returned on the existing group; other-
providing run-time shared state only. wise, a new group will be created and an interface returned
on that. We then generate a unique client key with the
The Mtx module of Delphi provides the definition of, and help of the Win32 function GetTickCount, which returns
access to, the SPM interfaces. The top level in the hierar- the number of milliseconds since the system started. Once
chy is the ISharedPropertyGroupManager interface, which we have our shared property, we set its value to the current
allows you to create and find property groups. Once you tick count and exit. Not hard at all, is it?
have a group, you access it with the ISharedPropertyGroup
interface. This allows you to create and find properties in Figure 2 shows the IsClientKeyValid method, which is called
the group. Finally, a property itself is accessed with the to check if the client key passed in by a client is still opera-
ISharedProperty interface, which you use to set or get its tional. You can see that it starts by creating the same prop-
11 February 1999 Delphi Informant
Informant Spotlight
erty group as before, but then it looks for the shared proper-
ty in the group by name. If it finds it, it checks the value if (TrnAmount > 2000) or (TrnAmount < 2000) then
begin
against the current system tick count and the timeout limit, if not (IsSecurityEnabled and
returning appropriately. IsCallerInRole('Managers')) then
raise Exception.Create('Transactions of more than ' +
'2000 dollars only allowed by Managers');
Shared properties can be used for many things: They could end;
help store singleton values; they could be used to hold the
current location and status of players in an online multi- Figure 3: An example of enforcing business rules.
player game; and they could even be used to provide the
next sequential number for a serial key in a database to tomers. We don’t want to allow customers to add new bank
save the delay of looking it up. The important thing is to accounts or delete transactions, for instance, so we have to
recognize their importance in MTS, and use them when write code to prevent this. Tellers work in the bank, but
you can. They can literally save you a lot of time. aren’t Managers. Adding accounts or account types is
acceptable for Tellers, but authorizing a transfer of more
Role-based Security than $2,000 is only allowed by Managers.
Every organization has specific duties assigned to its
employees. For a small company, this is easy to monitor. Figure 3 shows the sort of code you would use to imple-
As the organization grows, it’s forced to segregate its work- ment this. In our example, this code would be added to the
force into more general categories, such as Payroll, Sales, TAccounts2.AddToBalance method, an internal method
and Development, to abstract and categorize itself and its called by other methods in this object, such as Transfer. You
staff. The underlying IT technology it uses tends to mimic can see what it does quite easily. If the amount to add or
this structure for similar reasons. With the Windows NT deduct from the balance is more than $2,000, and security
operating system, domains are used to create areas of users, is not enabled, or the caller isn’t a Manager, the call fails.
such as Payroll, that share resources. By doing this, the sys- The IsSecurityEnabled method normally returns True, but
tems administrators can more efficiently perform their returns False if the MTS package is running in the client’s
jobs, making network security easier to enforce. A set of process space, and not in an MTS process space (more on
MTS business objects may have the requirement to be this shortly).
used by one or more domains, or none. MTS security
hooks into the Windows NT domain security model, but The definition of the caller used in IsCallerInRole varies
domains can contain users that have a broad spectrum of depending on the complexity of your setup. It’s defined as the
duties to perform. We need something more fine-grained process calling into the current server process in which the
with which to pigeon-hole our users and make decisions at object is executing, and is normally the base client. However,
run time about what functionality we allow them to per- if an MTS object calls another MTS object on a separate
form. The MTS solution to this is to further categorize machine, or in another package (each package runs in a dif-
users with roles. ferent process), it won’t be the base client. Rather, the caller
will be the ID of the calling MTS process running the calling
A role is a symbolic name that defines a logical group of object. This can be a tricky subject, and more information is
users for a package of components. Roles are defined dur- available in the MTS online documentation, but I’ll try to
ing application development, and Windows NT users or give you a brief overview.
groups are given the necessary roles at deployment time.
There are two ways of using role-based security: Each package runs under the guise of a user account, as we
Declarative and Programmatic. Declarative security is shall see shortly. Any objects in that package run in the
designed to instruct MTS to block access to packages, same process, and take on the identity of the package’s user
components, and interfaces. If you design your compo- account. If these objects access resources, such as a data-
nents well, and split functionality into related groups, then base, then they would do so with the rights and privileges
you will be able to take advantage of declarative security of the package’s user account, and not of the base client
more effectively. We’ll see how to implement declarative (the user), as you might expect. Also, security credentials
security when we look at the MTS Explorer shortly. On are only checked when you cross a process space, so an
the other hand, programmatic security requires program- object calling another object in the same package doesn’t
ming to implement, but offers a totally flexible way for have its security checked. MTS security is a complex topic,
you to make run-time decisions inside your objects. and you need to read up on this in more detail if you plan
on developing a system using MTS.
Programmatic Security
At run time, objects can check if a user is assigned to a spe- The MTS Explorer
cific role. By doing so, actions can be blocked, and are The MTS Explorer lets you install and configure — locally
therefore used to enforce business rules. In The Delphi or remotely — packages, components, security, and other
Bank example, we could have roles of Customers, Tellers, aspects of MTS running on a computer. We’ll start by
and Managers. Customers would represent typical cus- implementing declarative security on our objects. With
12 February 1999 Delphi Informant
Informant Spotlight
declarative security, you can control access to packages, when a user attempts to modify packages in the MTS
components, and interfaces by assigning roles to them with Explorer. By default, the System package has an
the MTS Explorer. Because declarative security uses Administrator role and a Reader role. Users mapped to the
Windows NT accounts for authentication, you won’t be Administrator role of the System package can use any MTS
able to use declarative security for a package if MTS is run- Explorer function. Users that are mapped to the Reader role
ning on Windows 95. can view all objects in the MTS Explorer hierarchy, but
can’t install, create, change, or delete objects, shut down
When you first install MTS, there is no security on the server processes, or export packages.
System package, because no users have been mapped to the
Administrator role. Therefore, security on the System pack- In the MTS Explorer, you’ll see the System package under My
age is disabled, and any user can use the MTS Explorer to Computer | Packages. If you open this, you’ll see Components
modify package configuration on the computer. If you map and Roles. Open Roles and you’ll see the Administrator and
users to the System package’s roles, MTS will check roles Reader roles as discussed. Add your account and any other
accounts that will be allowed to administrate MTS to the
Administrator role before enabling security on the System
package. Do this now by opening Administrator. Right-click on
Users and select New | User. You’ll see a dialog box like that
shown in Figure 4. You can now add your groups and users
(which can be from other domains) and select OK. On my sys-
tem, the System package now looks like that shown in Figure 5.
You can clearly see the groups and users assigned to each role.
Once you’ve done this, right-click on the System package and
select Properties. From the Properties dialog box, select the
Security page, and select Enable authorization checking. It’s
important that you only do this after you’ve added your
account to the Administrator role; otherwise, you’ll no longer
be able to administer MTS, and you will be forced to rein-
stall. If you now shut down all server processes (by right-
clicking on My Computer and selecting Shut Down Server
Processes), System security will be enabled.
Declarative Security
Figure 4: Adding users and groups to a role. So much for the System package, but we also want declara-
tive security on The Delphi Bank package and its objects.
To facilitate this, move administration-related
methods from the four main interfaces
(IAccountTypes3, IAccounts2, ICustomers2, and
ITransactions2) into an IxxxAdmin interface. For
instance, move the Delete and ListAll from
ITransactions2 to ITransactionsAdmin, because
these functions should only be used by Tellers and
Managers, not Customers. This means we can
assign the IxxxAdmin interface to the Tellers’ and
Managers’ roles. Any user not in one of these roles
trying to access one of the IxxxAdmin interfaces
will be automatically blocked out by MTS. The
original four interfaces will have all roles mapped
to them, and thus be accessible by all users.
Open The Delphi Bank package, then the Roles
directory. You should see that no roles are
defined. Right-click on Roles and select New, then
Role. Type in the name of the role and press OK.
Do this three times, entering Managers, Tellers,
and Customers, respectively. You need to add
users and groups to the roles in the same way you
Figure 5: The System package with roles defined. did for the System package. It’s important to
13 February 1999 Delphi Informant
Informant Spotlight
understand that these are Windows NT users and not the need to do the above for the remaining three objects and
customers we’ve defined on The Delphi Bank database. You each of their two interfaces. When you have done this,
must add the customers you intend to use to Windows NT, enable authorization checking for the package in the same
as well as The Delphi Bank database Customers table. way you did for the System package. Declarative security is
now fully enabled for The Delphi Bank.
Note that whichever user account you use to log on to
Windows with when you run the client application will Packaging Properties
be the one used by MTS to authorize access to the Before discussing remote access with DCOM, let’s quickly go
objects. You could log on to the application as three through the property sheets for a package. When you look at
different users, and transfer money and pay bills on their this dialog box for The Delphi Bank package, you’ll see that
accounts, but MTS will use your Windows logon user there are five information tabs from which to choose.
name to validate access to the objects and interfaces
because that is what is transported by DCOM. To make The General page contains the name and description of the
this work for you then, add your Windows logon user package. The Security page allows you to enable authoriza-
name to the Customers table, and give yourself at least one tion checking, and set the authentication level (usually set to
bank account in the Accounts table. You could use Paradox packet). The Advanced page is interesting. Here you can
to accomplish this. Then, for each role you just added, specify what happens once all components in the package
open it, right-click on Users, select New, then User, and have been deactivated. Depending on the expected level of
add your logon user name. use of the package, you may want to leave it running, or
shut it down after a number of minutes to conserve
All that remains is to assign the roles to our components resources. The default is to shut down after three minutes. If
and their interfaces. Open the Components folder, and note you’re currently developing the components in the package,
the four components in our package. Each of them has two it’s a good idea to set the package to shut down after zero
interfaces: a main one and an admin one. Figure 6 shows minutes, because this frees up the lock on the DLL to which
the setup for the Accounts2 object. Notice that for the you want to compile. Also on this page is the ability to dis-
IAccounts2 interface I’ve added all three roles because able deletions and changes to the package. This can be used
everyone can access this interface. For the IAccountsAdmin to avoid accidental changes on a live system. These flags
interface, I added only the Managers and Tellers roles. For would have to be manually turned off by an administrator
the object itself, Accounts2, I added all three roles because before the package could be deleted or changed.
we want all users to be able to access the component. You
The Identity page allows you to specify the Windows user
account that components in the package will use. If the
components need to access resources, such as a database or
MTS components on another machine, maybe even to read
and write files to a server somewhere, then this is the
account they will use to do it. Finally, the Activation page
specifies how the components in the package are activated.
The choices are Library package and Server package. A
Library package runs in the process of the client that cre-
ates it. This option is only available for clients on the com-
puter on which the package is being installed and config-
ured. A Server package runs in its own process on the com-
puter. Server packages support role-based security. Library
packages have no security; all of its components are avail-
able to the client. The BDE-MTS package installed by
Delphi 4 is an example of such a package.
Remote Clients via DCOM
You may be wondering by now how clients from across the
network call MTS components? How does a client application
know where to find them, and what extra work do we have to
do to the client to make it run across a network? Thanks to
DCOM and an MTS wizard, the answer is “very little.”
DCOM is an extension to COM that allows calls to COM
objects to take place transparently over a network.
Transparent means that the client application is blissfully
Figure 6: The Delphi Bank package with roles and users defined. unaware of whether the COM objects are running on the
14 February 1999 Delphi Informant
Informant Spotlight
local machine, or halfway around the world. By using stan- EnableCommit or SetComplete, the transaction will also be
dard OLE-compatible parameters, a method call is auto- aborted. Any error that causes Microsoft Distributed
matically packaged and routed over a network to the server Transaction Coordinator (MSDTC) to abort a transaction
machine, where it’s then un-packaged and executed. will also abort an MTS transaction.
DCOM is already installed on Windows NT 4.0, but on a
Windows 95 system, you’ll have to download and install Callbacks and References
DCOM for Windows 95 (from the Microsoft Web site) It’s a common programming technique to give an object a refer-
before you can call the MTS components across a network. ence to another object for it to work with. For example, you call
a search routine and pass in a reference to a search object that
Assuming you’ve installed DCOM, there’s a quick way to implements ISearch. This would allow you to use a different
enable a client computer to run our application and use search method depending on the data being searched. Similarly,
objects on our server. If you right-click on The Delphi Bank you may want to pass a reference to an MTS object to another
package and select Export, you’ll be presented with a dialog MTS object, or back to the client. If you’re an MTS object, the
box asking for the name and location of the exported client may even want to give you a reference to itself for you to
package. Three things are exported: call back when you’ve finished doing your work. MTS doesn’t
1) the DLL containing your components, prevent you from doing any of the above, but there are issues
2) a .PAK file that can be used to import the package and you must be aware of, or you may come “unstuck.”
component(s) into another MTS server installation, and
3) a client sub-directory containing an executable that you You can pass object references, for example, to use as a call-
run on a client computer. This executable performs all back, in only three ways:
the necessary registry entries to register the COM objects 1) through return from an object creation interface, such as
for the client application to use, but with additional CoCreateInstance, ITransactionContext.CreateInstance, or
information about their location on the network. IObjectContext.CreateInstance,
2) through a call to QueryInterface, or
The client we’ve been developing can now be run on this 3) through a method that has called SafeRef to obtain the
computer with no changes. It will instantiate and use the ser- object reference.
vices of the objects without realizing they’re running on your
server. You can examine what has been registered and adjust SafeRef is an MTS method an object can use to obtain a refer-
your DCOM settings by running the DCOM Configuration ence to itself that is safe to pass outside of its context. An
Manager (dcomcnfg.exe). See the “References” section at the object reference obtained in one of the above ways is called a
end of this article for books pertaining to DCOM and other safe reference. MTS ensures that methods invoked using safe
subjects relevant to MTS. references execute within the correct context. Never pass a self
pointer or a self reference obtained through an internal call to
Client-side Transactions QueryInterface to a client or another object.
It’s often necessary — or easier — for the client application
to control transactions. Suppose you have two independent Objects can make callbacks to clients and other MTS
systems running MTS: an Inventory and a Payments system. objects, but there are problems in doing so. First, calling
Neither have access to each other, nor do they need it. For an
procedure MakeOrder(CustomerId, StockId: Integer;
order to take place, though, inventory has to be deducted and StockAmount: Currency);
an entry in the Payments server made. The order has to be var
completely transactional, otherwise money or inventory could TransactionContextEx : ITransactionContextEx;
InventoryIntf : IInventory;
be lost. The order-processing application is being used by a PaymentIntf : IPayment;
user that has access to both systems, so it’s best suited to con- begin
trol the transaction. TransactionContextEx := CreateTransactionContextEx;
try
Client transactions are easily accomplished with the OleCheck(TransactionContextEx.CreateInstance(
ITransactionContextEx interface. Figure 7 shows an example CLASS_Inventory, IInventory, InventoryIntf));
OleCheck(TransactionContextEx.CreateInstance(
piece of code that could be used for such a scenario. We sim- CLASS_Payment, IPayment, PaymentIntf));
ply create an ITransactionContextEx interface, use it to create InventoryIntf.RemoveStock(StockId, StockAmount);
a Payments and Inventory object (which would be registered PaymentIntf.CreateStockPayment(
CustomerId, StockId, StockAmount);
COM objects running on different servers), call the appro- except
priate methods, and call either Commit or Abort. TransactionContextEx.Abort;
raise;
end;
Calling Commit doesn’t guarantee a transaction will be com-
mitted. If any MTS object that was part of the transaction TransactionContextEx.Commit;
has returned from a method after calling SetAbort, the trans- end;
action will be aborted. If any object that was part of the Figure 7: Client transactions are accomplished with the
transaction has called DisableCommit, and hasn’t yet called ITransactionContextEx interface.
15 February 1999 Delphi Informant
Informant Spotlight
back to a base client or another package requires access-level Define your components and objects, security requirements,
security on the client. The client would also have to be a interfaces, and roles before developing any code. Give your
DCOM server. Second, there may be firewalls blocking the interface type libraries to the front-end guys as soon as you’ve
path back to the client. Finally, any work done on the call- developed them. They can then start work given what is
back executes in the context of the object being called. It effectively your documentation blueprint. Also, by defining
may be part of the same transaction, a different transaction, role and security requirements up front, you can better code
or none at all. If you’re controlling the deployment arena for the functionality of your objects.
your application, then you can work around these aspects,
and callbacks become a viable operation. Finally, keep your components fine-grained. A lot of objects
implementing small, closely related interfaces are much easier
Activities to document, debug, reuse, and maintain. They’re also easier
There is one final piece of terminology that you may come for MTS to scale.
across when learning about MTS: the activity. An activity is a
set of objects executing on behalf of a base client application. Conclusion
Every MTS object belongs to one activity, which is recorded Hopefully, you will now be a lot more familiar and confi-
in the object’s context. For a given object, both the object dent with the MTS environment. You know what it is,
that instantiated it and all the descendant objects it has what it’s for, what it can do, and how to develop for it. I’ve
instantiated will be part of the same activity. The objects can tried to give you an overall picture of MTS throughout this
be distributed across one or more processes, executing on one series, so you can make judgements about whether to use
or more computers. MTS in parts of your development. As MTS moves into
the core of Windows NT, you’ll see more system software
The reason for an activity is simple: for all the objects running (particularly enterprise material) use MTS. Microsoft is
in an activity, MTS tracks the flow of execution and enforces a pushing Windows NT into the enterprise arena. MTS is
single logical thread of execution to prevent any inadvertent going to be the foundation that helps make this happen, so
parallelism that could cause application state corruption or you’re bound to come across it at some point.
deadlock. Activities aren’t something you have to worry about;
simply make sure you use the MTS Object Context to create Hopefully, I’ve proved the point I made when I stated that
instances of objects you want to use, and all work will belong Delphi makes developing for MTS a snap. It’s a pity that at
to the same activity and transaction. the time of writing this article, no one has yet written a book
covering MTS with the Delphi language. Most books on the
Tips for Development subject are geared, unsurprisingly, toward using Microsoft
To wrap up this series, I’d like to give you some pointers on development languages. With any luck, this will change.
your road to development. They’re not hard and fast, but
they may help smooth your progress into multi-tier devel- I’d be pleased to hear any feedback you have about this
opment with MTS. series, and to hear your success stories with Delphi and
MTS. If you have any questions about what you’ve read, or
Normal classes in Delphi have a set of properties, methods, something you don’t understand, please drop me a line. ∆
and events defined for them. Most people I see developing
MTS objects head straight down a similar path. An MTS References
object is a different animal, however, so you have to learn to Inside Distributed COM, Guy Eddon and Henry Eddon
think differently when you design one. If you write your [Microsoft Press, 1998], ISBN: 1-57231-849-X.
object with a set of properties on it, then every time a remote Inside COM, Dale Rogerson [Microsoft Press, 1997],
client sets a property on the object, there’s a DCOM remote ISBN: 1-57231-349-8.
procedure call done over the network. This is inefficient and Roger Jennings’ Database Workshop: Microsoft Transaction
will slow down your application considerably. Also, properties Server 2.0, Steven D. Gray, Rick A. Lievano, and Roger
imply statefulness. The object must be retaining state between Jennings [SAMS Publishing, 1997], ISBN: 0-672-31130-5.
your calls to set properties so it’s not deactivated or scalable.
Use methods that encapsulate functionality having parameters The files referenced in this article are available on the Delphi
that pass in all the information the method needs to perform Informant Works CD located in INFORM\99\FEB\DI9902PF.
its work. That way, the object is deactivated on return from
the method, and the parameters are efficiently packaged in
one bundle for a single network call.
Paul M. Fairhurst is a First Class Computer Science graduate of Sheffield
Stateful objects do have their place in MTS, but they’re University and freelance consultant/programmer specializing in client/server
not singletons. The best way to implement a singleton is and multi-tier database development. He is currently developing information
to have an MTS object that stores its state in the property systems for BBC Television and Radio in London. You can contact him at
manager. This way, multiple instances of the same object
[email protected].
will have the same properties, but will still be stateless.
16 February 1999 Delphi Informant
Algorithms
Hash Tables / Probing
By Rod Stephens
Hash It Out
Using Hash Tables to Manipulate Key-based Data
D atabases typically use trees to store index information. This lets the data-
base locate items very quickly. For databases stored on a hard disk or
other slow-storage device, speed is critical. Reading data from a disk takes a
relatively large amount of time, so the database must find the data as quickly
as possible. Trees make it easy to find ordered data, but, under some circum-
stances, you can locate items even faster using a hash table. A hash table is a
data structure that allows you to store and retrieve items based on a key. You
can add items to a hash table specifying a key, and later use the keys to locate
particular items.
The Basics you want to locate employees using only their
Suppose you want to store and quickly Social Security numbers. There are roughly
locate a few dozen items that have unique one billion possible Social Security numbers
integer keys between 0 and 99. You could of the form 123-45-6789. To use the previous
build an array with index bounds from 0 to hashing strategy, you would need to allocate a
99. Then you could store an item with key one-billion-entry array. If each employee’s
K in position K in the array. To locate an information occupied 1KB of storage, the
item with key K, you would look in posi- array would take up 1 terabyte (one million
tion K. The following code fragment shows megabytes) of memory. Chances are good
this simple scheme: that you don’t have that much memory or
disk space available on your computer. Even if
var you had that much storage, more than 99
values : array [0..99] of Variant; percent of it would always be unused, unless
begin
// Insert an item with key 13 in the array. you had more than 10 million employees.
values[13] := 'This is a really simple
hashing strategy'; In cases like this, where the number of
// See if a value with key 27 is present. possible key values is huge, you need to
if (VarIsEmpty(values[27])) then map the key values into a relatively small
ShowMessage('Key 27 is not present') hash table. For example, if you have
else
ShowMessage(String('values[27] = ') + around 80 employees, you might create an
values[27]); array with 100 entries indexed from 0 to
99. Then you could calculate an employee’s
This method is very simple. It’s also Social Security number modulus 100 and
extremely fast. With only a single array map the employee’s information to that
access, you can tell if the item is present entry. For example, an employee with
and find its value. Social Security number 1234-56-7890
would map to position 90 in the array.
Unfortunately, in the real world, key values
don’t always map into such a conveniently Using this scheme, you would only need
small range of values. For example, suppose 100 employee entries taking up a total of
17 February 1999 Delphi Informant
Algorithms
100KB of memory. If you have 80 employees, only 20
percent of that space is unused, so you’re not wasting a
lot of space.
Note that this array contains only 100 entries, but there are
one billion possible key values. In that case, 10 million possi-
ble key values will map to each array entry. For example, all
Social Security numbers that end with 99 will map to posi-
tion 99 in the array.
That means that sometimes you may try to put a record in Figure 2: The quadratic probe sequence will jump through an
array, skipping entries and inserting the new item in a position
the table, and find it’s already occupied by another record.
not adjacent to the cluster, thus reducing primary clustering.
This is called a collision. To resolve this problem, you need a
collision-resolution policy. This is a rule that tells where an
entry goes when the place it belongs is already occupied. The following code shows constants and a record type
Usually, the policy is to re-map the record to another posi- used to manage simple hash tables. The hash tables
tion in the table. If that position is also in use, the record is described here use a resizable array of THashType records
continuously remapped until an empty position is found. to store data items:
The sequence of positions that are examined searching for
an empty position is called the item’s probe sequence. type
// Values for hash table operations.
THashInsertValues = (hashInsertOk, hashInsertTableFull,
To summarize, a hashing scheme requires three things: hashInsertDuplicateKey);
1) A data structure called a hash table that holds the entries.
2) A hashing function that maps keys to entries in the // Define a record for hashing examples.
THashType = record
hash table. Value : Variant;
3) A collision-resolution policy that tells where to place an item Key : Longint;
when its natural position in the table is already occupied. end;
THashTypeArray = array [0..1000000] of THashType;
Linear Probing PHashTypeArray = ^THashTypeArray;
One of the most popular types of hashing is called open
addressing. Here, the value of the key is used to calculate an Listing One (beginning on page 20) shows the Delphi
offset in memory where the data should be placed. The previ- source code for a hash table class that uses open addressing
ous examples used open addressing to map a key value to a (available for download; see end of article for details). The
position in an array. main program creates a TLinearHashTable object, passing
the constructor a parameter that indicates the number of
There are several varieties of open addressing schemes that entries the hash table should use. It can then use the
differ in their collision-resolution policies. The simplest is AddItem function to insert items into the table and the
called linear probing. If an item’s position is occupied, the FindItem function to locate items in the hash table. This
program simply looks at the next position. If that position function searches the hash table and returns the value of the
is also occupied, the program looks at the next position. item if the key is present in the table. If the key isn’t pre-
The program continues looking through the array until it sent, the function returns the Variant value Empty.
finds an empty position, or it has searched the entire array.
If the program finds an empty position, it inserts the item Program Linear, shown in Figure 1, demonstrates open
there. Otherwise, the hash table is full, and there is no addressing with linear probing (available for download; see
room for another item. end of article for details). Enter a value in the # Values text
box and click the Make Values button to
make the program insert random values in
the hash table. To keep the program simple,
each item’s value and key are the same and
are numbers between 0 and 999.
Enter a value in the Value text box and click
the Get button to make the program locate
the item in the hash table. In Figure 1, the
program has just located the item 565. Enter
a value and click the Set button to make the
Figure 1: Open addressing with linear probing, as demonstrated by the program insert the item in the table.
Linear program.
18 February 1999 Delphi Informant
Algorithms
Quadratic Probing
Linear probing is fast and simple, but it suf-
fers from an annoying primary clustering
effect. When you add many items to the
hash table, they tend to cluster together. The
hash table shown in Figure 1 is only half
full, but many of the items lie next to oth-
ers. That makes inserting and finding items
slower than it would be if the items were
more evenly spaced. For example, if you try
to insert the value 161 in this table, its Figure 3: The Quad program demonstrates quadratic probing.
probe sequence will collide with the items
361, 662, 561, 764, 65, 964, 565, 668, and
469 before it finds an empty position. AddItem and FindItem functions, so only these are shown in
Listing Two (on page 21).
If the values were spaced exactly evenly, there would be an empty
spot next to every used position. Then the program could find The Quad program is similar to the previous example,
an empty position for any new item in at most two tries. except it uses quadratic probing instead of linear probing
(see Figure 3). Even though the same values were inserted
The reason clusters form is that there is a slightly higher proba- in Figures 1 and 3, the clusters in Figure 3 are smaller.
bility that a new item will be inserted next to an existing item
than it will land somewhere else. For example, suppose a hash Pseudo-random Probing
table with N entries contains one item. When you insert a new Quadratic probing reduces primary clustering, but it still has
item into the table, there is a 1/N chance that it will land in some problems. Items that initially map to the same position
any particular position. However, if the item maps to the same in the array follow the same probe sequence. For example, if
position as the item that is already in the table, it will be insert- the hash table contains 100 entries, the values 100, 200, 300,
ed next to that item, and it will form a small cluster. If the and so on all follow the same probe sequence. If the program
item maps to one of the positions next to the previous item, inserts many items that map to the same position, they will
it will also start a small cluster. There is a 3/N chance that the form a secondary cluster spread throughout the array. The
new item will land next to the previous item. As the table fills, effect is not as noticeable as that of primary clustering, but
the chances are good that large clusters will form. still reduces performance.
The reason clusters form is that any item that maps to part One way to eliminate secondary clustering is to use a pseudo-
of a cluster gets added to the end of the cluster. You can random probe sequence. With this technique, the locations
prevent that behavior using quadratic probing. In this in an item’s probe sequence are given by (K + Rand(P)) Mod
method, the indexes in an item’s probe sequence are given N, where N is the size of the table, K is the key, P = 0, 1, 2,
by the equation (K + P2) Mod N where N is the size of the ..., and Rand(P) is the P th number in a sequence of random
table, K is the key, and P = 0, 1, 2, ... The program follows numbers. The sequence of numbers depends on the key’s
this probe sequence until it finds an empty position, or value, so different values will have different probe sequences,
until it has checked N positions. At that point, the program even if they initially map to the same position.
gives up and refuses to insert the new item into the table.
In Delphi, you can use the Random function to produce
Figure 2 shows two probe sequences for an item being insert- sequences of pseudo-random numbers. To initialize
ed into the first position in a hash table. The linear probe Random, set the RandSeed system value equal to the new
sequence examines a few adjacent positions, then inserts the key’s value. For example, the program uses the following
new item next to the others, extending the cluster. The qua- code to initialize the random number generator for the
dratic probe sequence, on the other hand, jumps through the value new_key:
array (skipping entries), and inserts the new item in a posi-
tion that isn’t adjacent to the cluster. RandSeed := new_key;
The way quadratic probing skips through the hash table When the program later needs to locate this item in the
makes clusters less likely to form and makes them grow more hash table, it sets RandSeed to the key value again. Then
slowly. That means a program that uses quadratic probing Random will produce the same sequence of numbers it pro-
can insert and find items more quickly (on average) than one duced when the item was added to the table. This is impor-
that uses linear probing. tant. If it produced a new sequence of random numbers, the
program would not be able to follow the same probe
The code for the TQuadraticHashTable class is very similar to sequence it used to insert the item, so it would be unable to
the TLinearHashTable class. The only differences are in the find the item.
19 February 1999 Delphi Informant
Algorithms
The TRandomHashTable class is similar to the previous hash
table classes. The only differences are in the AddItem and Begin Listing One — The TLinearHashTable Class
FindItem routines shown in Listing Three,beginning on page 21. type
// A hash table with open addressing and linear probing.
TLinearHashTable = class
Conclusion private
While quadratic and pseudo-random probing often give better HashTable : PHashTypeArray;
NumItems : Longint;
performance than linear probing, they also have some draw- NumUsed : Longint;
backs. Neither of them is guaranteed to visit every item in the public
hash table. For example, suppose you have a hash table with constructor Create(size : Longint);
destructor Destroy; override;
only eight entries. To insert the item 4 into the table using qua- function AddItem(new_value: Variant;
dratic probing, the program would follow this probe sequence: new_key: Longint): THashInsertValues;
function FindItem(target_key: Longint): Variant;
function FindItemAndIndex(target_key: Longint;
4 + 02 = 4 var index: Longint): Variant;
4 + 12 = 5 function ValueByIndex(index: Longint): Variant;
4 + 22 = 8 = 0 mod 8 end;
4 + 32 = 13 = 5 mod 8 // Allocate the hash table.
4 + 42 = 20 = 4 mod 8 constructor TLinearHashTable.Create(size: Longint);
4 + 52 = 29 = 5 mod 8 var
i : Longint;
4 + 62 = 40 = 0 mod 8 begin
etc. inherited Create;
// Allocate the hash table entries.
After eight probes, the sequence has only visited positions 0, NumUsed := 0;
4, and 5. If all the table’s entries are full except for entries six NumItems := size;
and seven, the program cannot insert the item in the table GetMem(HashTable, NumItems * SizeOf(THashType));
even though there is space available. Similarly, there is no way // Initialize the hash table entries to Unassigned.
to know if or when a pseudo-random probe sequence will for i := 0 to NumItems - 1 do
visit all the entries in the table. HashTable^[i].Value := Unassigned;
end;
Even so, quadratic and pseudo-random probing usually pro- // Free the hash table.
vide good performance when the table isn’t too full. Of destructor TLinearHashTable.Destroy;
begin
course, when the table holds a lot of empty entries, linear if (HashTable <> nil) then
probing does quite well, too. FreeMem(HashTable);
inherited Destroy;
end;
Using these hash-table classes, you can store items and
search for keys extremely quickly. If you size your hash // Add an item into the hash table. Return hashInsertOk,
table properly, you can get excellent performance — some- // hashInsertTableFull, or hashInsertDuplicateKey to
// indicate our success or failure.
times better than the performance provided by a database function TLinearHashTable.AddItem(new_value: Variant;
doing indexed retrieval. new_key: Longint): THashInsertValues;
var
probe : Longint;
There are many other types of hash tables that are useful in begin
different circumstances. For example, some work well when // Make sure there is room.
the data must be stored on a hard disk. You can learn more if (NumUsed >= NumItems) then
begin
about hash tables and other algorithms in Rod’s book Ready- // The table is full.
to-Run Delphi 3.0 Algorithms [John Wiley & Sons, 1998]. AddItem := hashInsertTableFull;
The algorithms run in Delphi 3 or later. ∆ end
else
begin
The files referenced in this article are available on the Delphi // Find the first probe sequence value.
Informant Works CD located in INFORM\99\FEB\DI9902RS. probe := new_key mod NumItems;
// While that position is occupied, try the next one.
while
(not VarIsEmpty(HashTable^[probe].Value)) do begin
// Make sure this key value is not already in use.
if (HashTable^[probe].Key = new_key) then
Break;
Rod’s book Ready-to-Run Delphi 3.0 Algorithms [John Wiley & Sons, 1998] has
// Check the next position in the probe sequence.
lots more to say about hash tables and other algorithms. For more information, probe := (probe + 1) mod NumItems;
visit http://www.delphi-helper.com/da.htm. You can contact Rod via e-mail at end;
[email protected].
// See if we found an empty position.
if (VarIsEmpty(HashTable^[probe].Value)) then
20 February 1999 Delphi Informant
Algorithms
begin end;
// Insert the item.
HashTable^[probe].Value := new_value; // See if this is the target key.
HashTable^[probe].Key := new_key; if (HashTable^[probe].Key = new_key) then
Result := hashInsertOk; begin
// Keep track of the number of entries used. // It is. The key is already here.
NumUsed := NumUsed + 1; Result := hashInsertDuplicateKey;
end Exit;
else end;
begin end;
// The key is already used.
Result := hashInsertDuplicateKey; // If we get here, we failed to find room.
end; Result := hashInsertTableFull;
end; // End (NumUsed >= NumItems) ... else ... end; // End function AddItem.
end; // End function AddItem.
// Find an item in the hash table. Return the item's
// Find an item in the hash table. Return the item's // value or Unassigned if it is not here.
// value or Unassigned if it is not here. function TQuadraticHashTable.FindItem(target_key: Longint):
function TLinearHashTable.FindItem(target_key: Longint): Variant;
Variant; var
var trial, probe : Longint;
trial, probe: Longint; begin
begin // Search positions until we find the target, an entry
// Find the first probe sequence value. // that is unused, or we have tried NumItems times.
probe := target_key mod NumItems; for trial := 1 to NumItems do begin
// Calculate the next probe sequence value.
// Search positions until we find the target, an entry probe := (target_key + trial) mod NumItems;
// that is unused, or we have checked the entire table.
for trial := 1 to NumItems do begin // See if this entry is unused.
// See if this entry is unused. if (VarIsEmpty(HashTable^[probe].Value)) then
if (VarIsEmpty(HashTable^[probe].Value)) then begin
Break; // The value is not here. Return Unassigned.
// See if this entry is the target. Result := Unassigned;
if (HashTable^[probe].Key = target_key) then Exit;
Break; end;
// Consider the next item in the probe sequence.
probe := (probe + 1) mod NumItems; // See if this entry is the target.
end; if (HashTable^[probe].Key = target_key) then
begin
// See if we found the entry. // We found it. Return the value.
if ((not VarIsEmpty(HashTable^[probe].Value)) and Result := HashTable^[probe].Value;
(HashTable^[probe].Key = target_key)) then Exit;
// We found it. Return the target value. end;
Result := HashTable^[probe].Value end;
else
// The value is not here. Return Unassigned. // If we get here, we failed to find the item.
Result := Unassigned; Result := Unassigned;
end; // End of function FindItem.
end; // End of function FindItemAndIndex.
End Listing Two
End Listing One
Begin Listing Three — The TRandomHashTable Class
Begin Listing Two — The TQuadraticHashTable Class // Add an item into the hash table. Return hashInsertOk,
// Add an item into the hash table. Return hashInsertOk, // hashInsertTableFull, or hashInsertDuplicateKey to
// hashInsertTableFull, or hashInsertDuplicateKey to // indicate our success or failure.
// indicate our success or failure. function TRandomHashTable.AddItem(new_value: Variant;
function TQuadraticHashTable.AddItem(new_value: Variant; new_key: Longint): THashInsertValues;
new_key: Longint): THashInsertValues; var
var trial, probe: Longint;
trial, probe: Longint; begin
begin // Initialize the random number generator for the key.
// Try up to NumItems times to find an open position RandSeed := new_key;
// or this key.
for trial := 1 to NumItems do begin // Try up to NumItems times to find an open position
// Find the next probe sequence value. // or this key.
probe := (new_key + trial) mod NumItems; for trial := 1 to NumItems do begin
// Find the next probe sequence value.
// See if the position is unoccupied. probe := (new_key + Random(NumItems)) mod NumItems;
if (VarIsEmpty(HashTable^[probe].Value)) then
begin // See if the position is unoccupied.
// It is. Take this position. if (VarIsEmpty(HashTable^[probe].Value)) then
HashTable^[probe].Value := new_value; begin
HashTable^[probe].Key := new_key; // It is. Take this position.
Result := hashInsertOk; HashTable^[probe].Value := new_value;
Exit; HashTable^[probe].Key := new_key;
21 February 1999 Delphi Informant
Algorithms
Result := hashInsertOk;
Exit;
end;
// See if this is the target key.
if (HashTable^[probe].Key = new_key) then
begin
// It is. The key is already here.
Result := hashInsertDuplicateKey;
Exit;
end;
end;
// If we get here, we failed to find room.
Result := hashInsertTableFull;
end; // End function AddItem.
// Find an item in the hash table. Return the item's
// value or Unassigned if it is not here.
function TRandomHashTable.FindItem(target_key: Longint):
Variant;
var
trial, probe : Longint;
begin
// Initialize the random number generator for the key.
RandSeed := target_key;
// Search positions until we find the target, an entry
// that is unused, or we have tried NumItems times.
for trial := 1 to NumItems do begin
// Calculate the next probe sequence value.
probe := (target_key + Random(NumItems)) mod NumItems;
// See if this entry is unused.
if (VarIsEmpty(HashTable^[probe].Value)) then
begin
// The value is not here. Return Unassigned.
Result := Unassigned;
Exit;
end;
// See if this entry is the target.
if (HashTable^[probe].Key = target_key) then
begin
// We found it. Return the value.
Result := HashTable^[probe].Value;
Exit;
end;
end;
// If we get here, we failed to find the item.
Result := Unassigned;
end; // End of function FindItem.
End Listing Three
22 February 1999 Delphi Informant
Columns & Rows
Delphi 3 Client/Server / Multi-tier Database Development
By Thomas J. Theobald
Multi-tier Database Applications
Part II: Getting to the Code
L ast month we defined an application with four partitions: user interface,
business logic, data access, and data storage. We explored the rationale for
the various partitions, and described the steps for planning and building such an
application. This month we turn to the implementation, i.e. the code. (All source
referenced in this article is available for download; see end of article for details).
When I first had the idea for this series, I although it sounds like it might be interesting
wanted to demonstrate a couple of ways of to play around with.
building the application. I’ve tried three,
but I’m only going to demonstrate the easi- Model Two: Inheritance
est of them (since we’re talking about get- This model created an ancestor Data Access
ting stuff done on time, and on budget, this data module, from which descended the
is probably for the best). I will, however, Business Logic remote data module (see
provide a brief description of the problems Figure 2). With this model, I had hoped to
I encountered with the other two. apply a three-tier physical model, while main-
taining the DA and BL layers as separate pro-
Model One: Four-tier “Straight-pipe” Model jects, producing a conceptual four-tier model
This model attempted to take a “normal” that would still be easy to manage.
three-tier model built in Delphi, and insert an
additional physical tier into the stream (see I found that, although it was simple to gener-
Figure 1). In keeping with the principles ate the ancestor DA element, I was forced to
described last month, I had hoped this would manually write the type library and all its
allow me to manage development as four sep- attendant functions in the descendant class. If
arate projects, thus allowing me to redeploy I were into writing interfaces, this could be
whichever one needed changing. fun, but I’m assuming that most developers
have neither the time nor the inclination for
Unfortunately, this meant that the road the this. It’s entirely too messy for me to deal
information took — database-dataset/- with as an application developer — perhaps
provider-client dataset — needed an additional as a tool developer, but not otherwise.
layer similar to the “client dataset” type to be
created. Because TClientDataSet doesn’t have Model Three: Peer-level Middle Partitions
its own provider, and a TProvider wouldn’t So, instead of a straight-through approach, I
accept a TClientDataSet as its dataset, I decided to make my middle partitions peers
assumed that meant I would have to design a instead of hierarchical (see Figure 3). Each one
whole new component that would be a new became a remote data module unto itself, with
variety of TProvider to accept the the DA partition drawing rules and validation
TClientDataSet. I don’t have that kind of time, from the BL partition as needed. This, I hoped,
would allow me to maintain physical and con-
ceptual separation. Not only that, but I could
also shamelessly pirate the EmpEdit demonstra-
tion from Delphi’s included MIDAS demonstra-
Figure 1: The four-tier “straight-pipe” model. tions for this exercise.
23 February 1999 Delphi Informant
Columns & Rows
However, if you ever want to write a commit or rollback condi-
tion, you’ll be applying that condition to every attached user.
Include a TDatabase on your remote data module to give each
user separate transaction control. Someone will probably point
out that this will do away with the license-cost benefit of having
an n-tier system on servers that license at a per-connection rate;
they may have been hoping that they only need to pay for a
number of clients corresponding to the number of middle-tier
Figure 2: The inheritance model. attachments. If it means that much to you, don’t use explicit
transaction control, or buy a server that charges a per-client rate
instead of per-connection, or perhaps one that charges on a per-
connected-machine rate. If the savings on client
licenses is an issue, development with a smaller model
may need to be considered.
Figure 3: This model has peer-level middle partitions. Let’s look at a hypothetical case. If we have a corpo-
ration that can potentially buy licenses for 30,000
users at a ballpark of US$300 each (don’t quote me
I’m going to direct the validation of a few fields at the DA parti- on that pricing; contact your database vendor), we’re looking at
tion to some functions provided from the BL partition. It’ll be roughly US$9 million for client licenses (I’m sure bulk pricing
simple at first. After that, I’ll change the rules a little, and actual- brings it down a lot). If we put 100 users on each middle tier,
ly stream a rules component over to the DA partition from the we’re down to 300 client licenses at a cost of US$90,000, the
BL partition to accomplish the same goal (using a technique cost difference being US$8.1 million. Considerable. The serv-
published in David Body’s article “DCOM Streaming” in the er(s) software and hardware will probably be another few mil-
March, 1998 Delphi Informant). lion. I would like to think that if one could demonstrate a dif-
ference of US$8.1 million to a CEO, CFO, and CIO in the
How Do We Do This? same room, the CEO would probably go along with the CFO
Using Delphi, we have a few components that are specific to and tell the CIO to buy the database server that provides those
this n-tier stuff. I’ll run through the layers and describe the savings. Of course, each case will be different, but when given
components we use to set it up. hard numbers and argued logically, pitching the case the devel-
opment team wants shouldn’t be that difficult.
On the DS side, there are no differences between two-tier,
three-tier, or n-tier levels. Our data side doesn’t care what gets It would also probably be of little use to generate anything
access, as long as it fits its own criteria of who is allowed. more than a rudimentary UI for this partition because it
won’t be viewed by the user community. If your client wants
On the DA side, we build this application as if we were one, great. Budget time for it. Just be aware it isn’t necessary.
building a standard two-tier model. We use TDataSet descen-
dants (TStoredProc, TQuery, and TTable — although proba- Now we have DS and DA defined. Next is the client side.
bly not TTable if we’re working with a SQL server) to get the
data out of the data server. The difference here is that instead At the client, instead of standard TDataSets (I define stan-
of a standard TDataModule, we use a remote data module. dard as TQuery, TStoredProc, and TTable), we’ll use
TClientDataSet as our connection. We’ll also use the
If you select File | New, you’ll see a Remote Data Module avail- TRemoteServer instead of a TDatabase here. The
able. Choosing that will produce a data module that looks TRemoteServer will connect to the middle tier, and will
incredibly like the standard one, but includes an interface defin- give access to all the published IProviders on that middle
ition as part of its class, as well as a type library. As you add tier. (Neat little factoid: If you don’t know the providers
datasets to the remote, you’ll publish them to outside applica- coming to you, you can get a full list of them from the
tions by right-clicking on them and choosing export <dataset TRemoteServer once it establishes a connection; it’s avail-
name> from data module. This will add a stub function to the able through a procedure called GetProviderNames.) Step
type library, and the data module that returns a provider for by step, this would be:
that specific dataset. Note that you don’t have to do this; there 1) Drop a TRemoteServer in your UI application.
may be some datasets that exist solely for internal use, such as 2) Choose or give it a computer and server name.
providing lookup fields. 3) Drop a TClientDataSet in your application.
4) Wire its RemoteServer property to the TRemoteServer you
This is where you drop the TDatabase control. There might be a just added.
temptation to make a single TDatabase on a separate data mod- 5) Choose or enter a provider in the ProviderName property
ule and have every instance of your remote data module pass corresponding to one provided by the application server.
through it. Not bad, if you’re using implicit transaction control. 6) Set it active to see if you get data back.
24 February 1999 Delphi Informant
Columns & Rows
Any of these steps could be done
at run time, as well as design time.
It might be a bit more difficult,
but it’s possible.
Packets
These interactions revolve around
“packets,” or “deltas,” which are
simply small packages of infor-
mation passed between the
TClientDataSet and its applica-
tion server.
What happens, in short, is as fol-
lows: When the client dataset
opens, it calls the provider at the
middle tier. The provider goes to
its dataset, opens it (if it isn’t
open already), and retrieves a
Figure 4: A Paradox-based DA element. Figure 5: An InterBase version.
dataset. The provider then pack-
ages the dataset into what is called
a Data Packet, and sends that back downstream to the client end up generating two DA elements that fulfill the same role.
dataset. The client dataset then supplies that packet to the I’ll start with a Paradox-based one (see Figure 4), and when
system as a representation of the data. The user then makes I’ve finished the initial application, I’ll create an InterBase
changes, deletions, or insertions, and tells the UI to version (see Figure 5) and install a switch at the UI.
ApplyUpdates (those of you familiar with cached updates will
see many similarities here). The DA partition will end up linking to the BL partition
using a TRemoteServer, and will pass validation requests on to
The client dataset acts on that command, assembles a Delta the BL partition using that component’s AppServer property.
Packet consisting of any differences between the current state Operationally, this will be similar to a three-tier application;
of the data at the UI and the original set it received from the the difference will be in avoiding the work of recoding the
provider, and ships this delta back to the provider. The business logic when we create our second DA partition.
provider then automatically applies this delta to the dataset as
a change to stored data, row-by-row. If any updates fail, the This multi-tier stuff does deserve a note on how it seems to
provider stores the row with its old value (which it originally know what is going on across multiple machines. The idea
supplied to the client dataset), the current value (what the of DCOM is that component objects will share a format.
provider just found at the storage side), and the new value This makes it easier for one application to use the objects of
(which the user just tried to input) in a resulting data packet another, and so forth. The means by which component
to be sent back to the client. The client dataset can then cycle objects are identified under COM/DCOM are Globally
through these erroneous records in its ReconcileError event. Unique Identifiers, or GUIDs. So far, GUIDs have proved
to be just that — completely unique identification numbers
The Elements for application components. If you look into a bare-bones
As stated earlier, I’ve pirated a copy of the EmpEdit demon- type library (like the ones included with my source code,
stration included with Delphi and C++Builder C/S versions. which are available for download; see end of article for
I’ve taken that application and enhanced it to some degree details), you’ll see three or four GUIDs.
for this example. I suggest you take a copy for yourself and
try adding the functionality I describe here, simply to get the When we tell a TRemoteServer about an application we
hang of n-tier thinking. want it to access, we first give it a machine name (or leave
that blank if we intend to run off the same box) to identi-
DS. The first partition will be the database side. I’ll start with fy under Windows where we are going to find the applica-
one that everyone has access to and comes supplied for tion server. We then specify a ServerName to identify the
Paradox: DBDEMOS. I’ve already used DataPump to pro- application we want to access; we could specify a bare
duce an InterBase version, and the same could be done for an GUID if we were certain of the one we need. When we
Oracle, Sybase, etc. data side. Fortunately for this example, make a connection, the TRemoteServer goes to Win32 and
it’s already done. says: “Please find this object on this server and give me a
handle to it.” If the local OS finds the machine in ques-
DA. The DA partition will be two partitions; because I’m tion, it asks it for a handle to the specific object, and
trying to be independent of database vendors, I’m going to returns the result. This is why the application server needs
25 February 1999 Delphi Informant
Columns & Rows
I’d like to point out that the example I’ve supplied uses a
remote data module inside an application for a rules
source. Given the nature of the application, it would prob-
ably be best run as a single-instance server on the same
box as the DA partition, and every instance of the DA
server’s module could reference the same BL module.
Some instances could require a multi-instanced version of
the BL partition on a separate machine, but the conditions
for that would be rare. I could just as easily have supplied
a library file to do the same job as the module I’ve given
here. In fact, this is the difference between an in-process
and out-of-process solution. In-process means something
tied directly into the process, while out-of-process refers to
getting something from an external source. Generally, in-
process servers are faster, but use the same machine — and
therefore resources — as the application using them, while
out-of-process servers take a little more time to operate,
Figure 6: The BL partition supplies a simple rule for salary but operate in a resource pool that is isolated from the
validation.
application using them.
to be registered before any other applications try to get to
it (if Win32 doesn’t know it’s there, it can’t find it). I need to mention a few things about the code sample. The
BL partition, and the two DA partitions, each have a cou-
Our DA partition will be an enhancement to the server ple of labels on them referring to “business objects.” The
application of the EmpEdit demonstration. The server appli- BL partition has the label of B.L. Objects Outstanding and
cation will have a few changes made: the DA partitions refer to “rule objects.” These are mislead-
A display of how many rule objects it has invoked will be ing titles that I had intended to use as representations of
added, with modifications to the data module’s Create the BL partition serving a complete binary object to each
and Destroy event handlers. DA partition from which to access rules, using DCOM
The EmpQuery will have its fields instantiated at design time. streaming. Unfortunately, I didn’t have time to incorporate
The EmpQuerySalary field will have a validation event added. this before completing this article.
BL. The BL partition of this system (see Figure 6) will UI. This will be a very simple front-end, with the standard
supply a simple rule for salary validation (denying the form from the original EmpEdit application and the error
change of a salary to more than US$100,000 from this reconciliation routine from RECERROR.PAS (see Figure 7).
application), which will be referenced by the UI partition I’ve added one change: a drop-down listbox with the names
passing through DA. It will also surface a property called of both servers and the GUIDs they use to identify them-
SalaryBreak, which will contain the value of the maximum selves. This allows the user to determine which server they
salary allowed to be assigned by a non-CFO employee. attach to. (Developers would generally use this functionality
Any changed data that passes through the DA partition themselves, but it makes a nice touch for the demonstration.)
will have to qualify as valid under that restriction, or be
denied at the UI. Conclusion
We’ve seen the components in Delphi/C++Builder that com-
Additionally, I will install a function that will serve up a bine to make a functional n-tier application: TClientDataSet,
“live” object for the DA partition to invoke, which will
contain much the same information as DA already
retrieves from BL. The key point to note in this practice is
that, when employing the “briefcase model,” most users
won’t have access to the BL layer, even though they might
have local copies of the data. By downloading a “rules
object” to the UI, a briefcase-style user could then stream
that set of rules to disk at shutdown, and re-load it every
time the system runs. When the user finally reconnects,
any new version of the rules server can be streamed down
to replace the existing local one. I won’t demonstrate this
full briefcase technique, but I will have an object stream
Figure 7: The UI uses a standard form from the EmpEdit appli-
across to the DA side for use at that level. (The briefcase cation, and the error reconciliation routine from RECERROR.PAS.
model will be the subject of an article by Bill Todd in next I’ve added a drop-down listbox with the names of both servers
month’s Delphi Informant.) and the GUIDs they use to identify themselves.
26 February 1999 Delphi Informant
Columns & Rows
TRemoteServer, TProvider, and remote data modules. Each
has its role to play, and each fits in well with the DCOM
platform this particular set was built to handle.
We also talked about planning. I want to stress again that
this stuff isn’t easy. Don’t let anyone try to convince you
this doesn’t take a lot of sweat to produce. The application
I’ve used as an example here is trivial (not to make light
of the developer who built the demonstration I used as
a foundation). A real n-tier application will take blood,
sweat, and tears and, without proper planning, will never
get done. I guarantee that if you don’t plan properly,
you’ll not only have a bad application, you’ll also have a
high turnover rate from all the disgusted developers who
won’t be able to believe the architecture they’re being
asked to pursue.
I’d like to sign off with a note on the potential Internet use of
this strategy: Deploying a thin client via the Internet will allow
the developer to keep the business logic and storage inside the
IS department’s “ivory tower,” but will allow the users to roam
freely around the world. Deploy an ActiveX client application,
and they’ll be able to browse it from its cache freely. If you can
pull that off, your users will love you. ∆
The files referenced in this article are available on the Delphi
Informant Works CD located in INFORM\99FEB\DI9902TT.
Tom Theobald is a senior software developer with Response Networks, Inc. of
Alexandria, VA. He began his career with computers as a NetWare engineer,
moving later to include NT and Lotus Notes among his acquired skill set.
Currently he is developing network response-time diagnostic tools used in the
identification of network brown-outs and the prevention of down time. He can
be reached at
[email protected] with any questions or com-
ments. Death threats and other matters of a personal nature can be forwarded
to
[email protected].
27 February 1999 Delphi Informant
The API Calls
Cryptography / Internet / Communications
By Mujahid Beg
For Your Eyes Only
Working with the Microsoft CryptoAPI
W ith the advent of the Internet as a critical business tool, data security has
been pushed into the limelight. “Secure Commerce” and “Secure E-Mail”
are becoming mainstream buzzwords. Understandably, in this age of Internet
data, application support of security features has become extremely important.
Historically, from a developer’s perspective, security has always been difficult to
implement. Bruce Schneier, noted cryptographer and author of Applied
Cryptography: Protocols, Algorithms, and Source Code in C [John Wiley & Sons,
1995], says it well: “Building a secure cryptographic system is easy to do badly
and very difficult to do well. Unfortunately, most people can’t tell the difference.”
The average cryptography developer has many some basic CryptoAPI concepts and develop a
obstacles to overcome. Besides the buzzwords tCryptography class to make its use from Delphi
and the new concepts, there are also complex easier. Finally, the developed class will be used to
algorithms and protocols to master. Then build a utility program to encrypt/decrypt files.
there are those nasty royalty and patent issues
for the most popular and proven algorithms. As with any relatively new Win32 API, using
Export restrictions on products using cryptog- CryptoAPI with Delphi is painful because the
raphy are also a major headache. On top of all API header file from Microsoft (WinCrypt.H)
this, just using secure and proven cryptograph- is written in C, and a translated Delphi unit
ic algorithms in an application doesn’t guaran- from Inprise isn’t available yet. For this article’s
tee it’s cryptographically secure. A weak imple- demonstration utility, Crypton, the relevant
mentation could turn a locked safe into a glass parts of the WinCrypt.H header file have been
window, and, worse, the security holes may translated (this and other files referenced in
not be discovered until an important client this article are available for download; see end
loses a million transactions. of article for details). This will allow for the
encryption/decryption of data in any applica-
Enter the Microsoft Cryptographic Application tion, but all the signature and certificate-related
Programming Interface, or CryptoAPI for stuff is left out. There are at least two freeware
short. Version 1.0 of CryptoAPI was shipped header file translators for Delphi, but neither
with Internet Explorer 3.0, and Version 2.0 was of them were quite up to the task of automati-
shipped with Internet Explorer 4.0 and cally translating the complete header file.
Windows 95 OSR2. This article will explore
Before delving into the details of CryptoAPI, a
few cautions are appropriate. By virtue of its
inclusion as part of the Windows operating
system, many developers will be encouraged to
include security functionality within their
applications. This means any security hole in
CryptoAPI will potentially affect a huge num-
ber of secured systems. At first glance, the
chance of this happening seems extremely
Figure 1: General architecture of the Win32 cryptographic system. remote. But, although CryptoAPI was devel-
28 February 1999 Delphi Informant
The API Calls
The CSPs are responsible for providing all the crypto-
Algorithm Base Provider Enhanced
graphic services, including creation and storage of keys,
Key Length Provider Key
encryption/decryption of data, storage and management of
Length (Salt)
certificates, and hashing and signing functionality.
RSA public-key 512 bits 1,024 bits (No) Different CSPs can implement different sets of algorithms
signature algorithm and key storage techniques — some store encrypted keys
RSA public-key 512 bits 1,024 bits (No) in the registry, while others may store them in a smart
exchange algorithm card or use a fingerprint as a persistent pseudo-key.
RC2 block 40 bits 128 bits (Yes) Currently, there are CSPs available from Microsoft and
encryption algorithm several other vendors (check Microsoft’s Web site,
RC4 stream 40 bits 128 bits (Yes) http://www.microsoft.com, for a listing).
encryption algorithm
DES Not supported 56 bits (No) Microsoft Base Cryptographic Provider is a general-purpose
Triple DES (2-key) Not supported 112 bits (No) CSP that supports digital signatures and data encryption. This
Triple DES (3-key) Not supported 168 bits (No) provider is included with Internet Explorer 3.0, or later, and
Figure 2: Base CSP versus Enhanced CSP. will become part of future Windows operating systems. An
enhanced provider, which is backward-compatible to the base
oped by a group of talented designers and programmers at a provider but offers better security, is also available. Because of
multibillion-dollar company, it doesn’t guarantee the security of export restrictions, this CSP is only available to North
the implementation. Consider the fact that most application American users. The difference between the two CSPs is sum-
developers will probably look to Microsoft’s CryptoAPI docu- marized in Figure 2.
mentation for examples and code snippets. John Boyer of UWI
Unisoft Wares, Inc. published an article in the June, 1998 issue In an application, the first requirement is to initialize the
of Dr. Dobb’s Journal, wherein he points out how one of the desired CSP (see Listing Four beginning on page 31).
sample programs that uses digital signatures with certificates has Information about the desired cryptographic provider is
a severe security weakness. passed to the CryptAcquireContext function, which returns a
handle to the provider in the form of an HCRYPTPROV
Another factor that CryptoAPI brings to light has nothing to type value. For security reasons, information between the
do with CryptoAPI itself. Because of the ease-of-use of this API, CSP and the application is passed in the form of opaque han-
many developers will incorporate security features in their appli- dles. The value returned by this function is one such handle;
cations using this API. A fair percentage of these developers will there are others for keys, hash objects, etc.
have only a vague understanding of the vast and complex topic
of cryptography. This creates a potential for a great deal of risk; Keys to the Safe
it’s far too easy for a developer to make seemingly clever design Every CSP maintains its own set of keys for each user on a
decisions that compromise an otherwise secure system. machine. The keys are stored in a persistent secure storage
known as the key container. The most common place to save
CryptoAPI Basics a key container is in the system registry, but it’s not a require-
An analogy might help in understanding the architecture of ment. A CSP could choose to store the keys on a smart card,
CryptoAPI. The Win32 Graphics Device Interface (GDI) or whatever medium is appropriate.
layer provides an API layer between the application and the
graphics card driver, and thus protects the developer from the If there is no previously created key container available,
painful experience of talking to each type of graphics card the CryptAcquireContext call will fail. For the Crypton pro-
separately. In a similar fashion, the CryptoAPI provides gram, a key container isn’t really necessary, because a user-
driver-independent access to cryptography services. In other supplied password will be used to generate the keys. But,
words, access to all cryptography services is done through a none of the other CryptoAPI calls can be made without
standard API layer that uses the implementation-layer DLL acquiring a cryptographic context first. So the
to do the real work. In the world of CryptoAPI, this driver is CryptAcquireContext function must be called again with
a Cryptography Service Provider, or CSP. the CRYPT_NEWKEYSET flag set to create a default-key
container for the current user.
CSPs from multiple vendors may be installed on a single
machine at any time. An application can explicitly choose a The SetPassword procedure takes a user-supplied password and
CSP to use, or let the system select the default CSP for a generates a key from it (see Figure 3). To do this, a hash object is
particular user. Figure 1 shows the general architecture of the needed, which is created with a call to the CryptCreateHash func-
Win32 cryptographic system. Applications talk to the oper- tion. The password string is hashed within this hash object with
ating system through the CryptoAPI, and the operating sys- the CryptHashData function. Finally, a call to CryptDeriveKey is
tem converts the calls to CryptoSPI (Service Provider used to generate the key from the current hash object, after which
Interface) calls. For security and other reasons, the applica- the hash object and the old key are freed. The newly created key
tion never directly talks to the CSP. handle, an HCRYPTKEY type value, is stored within the object,
29 February 1999 Delphi Informant
The API Calls
procedure tCryptography.SetPassword(PassCode: string); // Encrypt or Decrypt a file depending on the passed in
const // OpMode parameter. The input file name and the output
// Mix up the password a bit. // file name may be the same. Since the entire file is read
cPassText = 'This (%s) is the thing'; // into a buffer before processing, there are no conflicts.
var procedure tCryptography.DoOperation(OpMode: tOpMode;
hOldPassKey: HCRYPTKEY; InFileName, OutFileName: string);
hHash: HCRYPTHASH; var
msg: string; Buff: pByte;
begin BuffSize, CryptBuffSize, ClearTextSize: dWord;
if Length(PassCode) < cMinPassLen then ft: tFileTime;
RaiseErr(Format( begin
'Password must be at least %d characters', // Load the input file contents.
[cMinPassLen])); AllocAndLoadBuffer(opMode, InFileName, buff, BuffSize);
// Create a hash object. if Buff = nil then
if CryptCreateHash(hprov, cHASH_ALGID, Exit;
0, 0, hHash) = False then ClearTextSize := BuffSize;
RaiseErr('Unable to create has object'); CryptBuffSize := BuffSize;
try try
msg := Format(cPassText, [PassCode]); if opMode = omENCRYPT then
// Hash the Formatted password string. begin // Encryption.
if CryptHashdata(hHash, PChar(msg), // First we need to know the buffer size needed to
Length(msg), 0) = False then // store the cyphertext.
RaiseErr('Unable to hash password data'); if CryptEncrypt(hPassKey, 0, True, 0, nil,
// Create a key using this hash object. CryptBuffSize, ClearTextSize) = False then
hOldPassKey := hPassKey; // Save old key handle. if GetLastError <> ERROR_MORE_DATA then
if CryptDeriveKey(hProv, cCRYPT_ALGID, hHash, RaiseErr('Unable to get encrypted buffer size');
cKEY_FLAGS, hPassKey) = False then // Allocate the required buffer.
RaiseErr('Unable to derive password hash key'); ReallocMem(buff, CryptBuffSize);
if hOldPassKey <> 0 then // Free old key handle. BuffSize := CryptBuffSize;
if CryptDestroyKey(hOldPassKey) = False then // Now encrypt the data buffer.
RaiseErr('Unable to destroy old password key'); if CryptEncrypt(hPassKey, 0, True, 0, buff,
finally ClearTextSize, BuffSize) = False then
CryptDestroyHash(hHash); // Destroy the hash object. RaiseErr('Unable to encrypt data buffer');
end; end
end; else // Decryption.
if opMode = omDECRYPT then
Figure 3: The SetPassword procedure. if CryptDecrypt(hPassKey, 0, True, 0, Buff,
ClearTextSize, BuffSize) = False then
RaiseErr('Unable to decrypt data buffer.' +
but the password string is not. Note that this provides for better #13 + 'Possibly incorrect password');
security; the password string isn’t available anymore, and the han- // Save buffer to output file.
dle to the key is only meaningful to the CSP module within the SaveAndFreeBuffer(opMode, OutFileName, buff,
ClearTextSize, BuffSize);
current cryptographic context. This is an advantage of the opaque except
handle approach. An application might have a handle to some if buff <> nil then
data, but the actual data (a key in this case) remains unavailable. FreeMem(buff, BuffSize);
raise;
end;
The user password string can be passed in to the tCryptography end;
object constructor as a parameter. This way, the object can be Figure 4: The DoOperation procedure.
created with the password, and then the password string may
be destroyed or erased. The object with the saved key handle
remains usable throughout the application. Although this than, the original data ( plaintext) buffer. On the other
scheme provides better security, in some applications you will hand, in the decryption phase, the plaintext buffer
need to change the password. The write-only password prop- required is the same size, or smaller, than the cyphertext
erty lets you do just that. buffer. Buffer sizes are adjusted accordingly in the
DoOperation procedure (see Figure 4).
The tCryptography object constructor automatically con-
verts passwords to upper case before passing them on to the The two functions, AllocAndLoadBuffer and
SetPassword function. In any significant application, this is SaveAndFreeBuffer, handle file input/output for the object (see
a definite cryptographic no-no, because doing so reduces Listing Five on page 30). These two functions also manage the
the key space significantly. For the Crypton utility, howev- “Magic” text, a constant file header that’s written at the begin-
er, usability and ease-of-use were given more importance. ning of every encrypted file. This enables the object to detect
multiple-encryption attempts on an already-encrypted file, or
Passing the Data decryption attempts of a plain-text (non-encrypted) file. Any
To encrypt a file, complete file contents are read into a string of characters unlikely to appear by chance in a file can
buffer, encrypted, and then written back to the file. be used as magic text. The tCryptography object uses “elifdet-
Because an encryption operation usually doesn’t perform pyrcnenasisiht”. In encryption mode, if the file header match-
any compression of the data, the buffer needed to store es the magic text, it assumes the file is already encrypted and
the encrypted data (cyphertext) is the same size, or larger exits. After all, how often does a file start with the phrase “this
30 February 1999 Delphi Informant
The API Calls
method. All other methods are some form of compro-
mise. The idea is to have a system that takes so much
effort to break that it becomes unfeasible in terms of
time and/or money required.
The CryptoAPI gives the developer a clean, uni-
form way to implement proven algorithms and
protocols, with minimal effort. But, best of all,
because it will be part of the operating system, all
kinds of applications will be able to use it seam-
lessly. At least, that’s the idea. ∆
The files referenced in this article are available on the
Delphi Informant Works CD located in
INFORM\99\FEB\DI9902MB.
Figure 5: The customized “Open” dialog box.
procedure TMainForm.OpenDialogShow(Sender: TObject);
begin Mujahid Beg has been programming in various languages for the past 10 years.
// Change the window caption. His primary development environments are Delphi and C/C++. He is the Director
SetWindowText(GetParent(OpenDialog.handle), of Systems Development for MediNet-EDI Solutions — a medical EDI services com-
PChar('Select files to ' + cOpNameStr[CurrOpMode]));
// Change the open button caption.
pany located in Houston, TX. He can be reached at
[email protected].
SendMessage(GetParent(OpenDialog.handle),
CDM_SETCONTROLTEXT, 1, Integer(PChar(
'&'+ cOpNameStr[CurrOpMode])));
end;
Begin Listing Four — Initializing the desired CSP
Figure 6: The OpenDialogShow event handler.
constructor tCryptography.Create(Container, Provider,
Password: string; CanCreate: Boolean);
is an encrypted file” written backwards? The opposite happens var
r: bool;
in decryption mode; if the magic text doesn’t match, decryp- buff: array [0..1023] of Char;
tion is canceled. size: dWord;
l, h: Byte;
A Non-standard Dialog Box begin
inherited Create;
During development of the utility program, a file selection hProv := 0;
dialog box, capable of allowing the user to select multiple hPassKey := 0;
fProviderName := '';
files, was needed. The standard Windows file open dialog fContainerName := '';
box would work, as long as the multiple file selection prop- fCryptVer := '0.0';
erty was enabled. However, this standard dialog box is just a
if CryptAcquireContext(hProv, PChar(Container),
little too standard; the caption always reads “Open,” as does PChar(Provider), PROV_RSA_FULL, 0) = False then
the default button caption. It’s preferable to customize this // Unable to acquire context, check if failure was due
dialog box to reflect the actual operation (see Figure 5). // to absence of key container.
if (CanCreate) and (GetLastError = NTE_BAD_KEYSET) then
// Create a new key set for the current user.
Changing the caption is easy. In the TMainForm.OpenDialogShow if CryptAcquireContext(hProv, PChar(Container),
event handler, the SetWindowText function is called to perform PChar(Provider), PROV_RSA_FULL,
CRYPT_NEWKEYSET) = False then
this task (see Figure 6). Changing the Open button caption RaiseErr('Unable to aquire context');
is a little trickier. The dialog resource for this dialog box is
stored in a standard Windows system DLL, namely // Get some info about the cryptography module.
// Name of the CSP.
ComDlg32.DLL. The control ID of the Open button can be size := sizeof(buff);
looked up by opening this DLL, and loading the dialog if CryptGetProvParam(hProv, PP_NAME,
resource in a resource editor. In this case, the Open button @Buff, size, 0) = False then
fProviderName := 'Unknown'
control ID is 1 and the Cancel button ID is 2. Now it’s sim- else
ply a matter of sending a CDM_SETCONTROLTEXT fProviderName := StrPas(Buff);
message (defined in CommDlg unit) to the appropriate dia-
// Name of the key container.
log control. This is done in the same OpenDialogShow event size := sizeof(buff);
handler with a standard SendMessage function call. if CryptGetProvParam(hProv, PP_CONTAINER,
@Buff, size, 0) = False then
Secure Data fContainerName := 'Unknown'
else
Data security requires a lot of effort. There is no algorithm that’s fContainerName := StrPas(Buff);
completely unbreakable, except perhaps the One-Time-Pad
31 February 1999 Delphi Informant
The API Calls
// Version number. end;
size := sizeof(buff);
if CryptGetProvParam(hProv, PP_VERSION, procedure tCryptography.SaveAndFreeBuffer(OpMode: tOpMode;
@Buff, size, 0) = False then FileName: string; var buff: pByte;
fCryptVer := '*.00' SaveSize, BuffSize: dWord);
else // This function is responsible for opening a file for
fCryptVer := // writing and saving the contents of the buffer.
Format('%d.%d',[Integer(buff[1]),Integer(buff[0])]); var
F: THandle;
// Set password key. SizeWritten: DWORD;
if PassWord <> '' then begin
SetPassWord(UpperCase(PassWord)); // Open file in write mode.
end; F := CreateFile(PChar(FileName), GENERIC_WRITE, 0, nil,
CREATE_ALWAYS, 0, 0);
if F = INVALID_HANDLE_VALUE then
End Listing Four RaiseErr('Unable to open input file');
Begin Listing Five — AllocAndLoadBuffer and //
//
In Encryption mode — write a fixed file header before
the actual encrypted data. This protects against
SaveAndFreeBuffer // multiple encryption attempts against the same file.
if opMode = omENCRYPT then
// This function is responsible for opening a file for
WriteFile(F, cMagicText, cMagicTextLen,
// reading, and reading the contents in a buffer.
SizeWritten, nil);
procedure tCryptography.AllocAndLoadBuffer(OpMode: tOpMode;
WriteFile(F, buff^, SaveSize, SizeWritten, nil);
FileName: string; var buff: pByte; var BuffSize: dWord);
FileClose(F);
var
F: THandle;
FreeMem(buff, BuffSize);
SizeRead: DWORD;
Buff := nil;
TempBuff: array[0..cMagicTextLen] of Char;
pTempBuff: PChar;
if SaveSize <> SizeWritten then
begin
RaiseErr('Unable to write output file.');
buff := nil;
end;
// Open and read in the file.
F := CreateFile(PChar(FileName), GENERIC_READ, 0, nil,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); End Listing Five
if F = INVALID_HANDLE_VALUE then
RaiseErr('Unable to open input file');
try
// Assuming filesize < 2 GB.
BuffSize := GetFileSize(F, nil);
// Check file header to verify file is encrypted.
if opMode = omDECRYPT then
begin
pTempBuff := @TempBuff;
// Read in the header.
ReadFile(F, pTempBuff^, cMagicTextLen,
SizeRead, nil);
if StrLComp(cMagicText,
pTempBuff, cMagicTextLen) <> 0 then
RaiseErr('Not an encrypted file: ' + FileName);
// Data size = File Size - Header Size.
Dec(BuffSize, cMagicTextLen);
end;
// Allocate buffer to hold entire contents of the file.
GetMem(buff, BuffSize);
try
ReadFile(F, buff^, BuffSize, SizeRead, nil);
if BuffSize <> SizeRead then
RaiseErr('Unable to read input file');
// Make sure we are not attempting to encrypt an
// already encrypted file.
if opMode = omENCRYPT then
if StrLComp(cMagicText, PChar(buff),
cMagicTextLen) = 0 then
RaiseErr('File is already encrypted: ' + FileName)
except
FreeMem(buff, BuffSize);
Buff := nil;
raise;
end;
finally
FileClose(F);
end;
32 February 1999 Delphi Informant
Greater Delphi
BDE / Networking
By Bill Todd
The BDE Made Easy
Sharing the BDE on a Network
I nstalling and maintaining Delphi applications that use the BDE (Borland
Database Engine) in a network environment is too costly if you install the BDE
and the application on every PC. It’s easy to solve half the problem by putting
your application’s EXE on the file server so that all users share a single copy.
When you need to install an update, you only have to do it once, and the new
version is immediately available to everyone. But what about the BDE?
Inprise recommends the BDE be installed on Using the techniques described in this article,
each PC to improve performance. However, you can take a new PC out of its box and
the only performance gain is that the BDE connect it to the network, and the only thing
DLLs can probably be read into memory you have to do to run a BDE application is
faster from a local hard drive than from the create a shortcut to the EXE. There are other
file server. Because the load time of the DLLs benefits, as well. You can have different appli-
is generally a minor component of overall cations use different BDE configuration files,
application performance — unless the net- or even different versions of the BDE. The
work is very slow — little is gained by interesting thing is that there are no tricks
installing the BDE on each PC. involved; the BDE was designed from the
beginning to allow a single, central copy to be
The alternative is to install the BDE on the shared by multiple users.
file server. Installing a single copy of the BDE
on the file server offers several major benefits: There are two phases to the process of get-
1) You only have to install the BDE one ting applications to automatically configure
time in one location. and use a central copy of the BDE. The
2) When you need to upgrade to a new ver- first is getting the BDE installed on the file
sion of the BDE, you only need to install server. The second is adding code to your
the update one time in one location. program so it will automatically create the
3) You can be certain that all users are BDE registry entries when it starts.
running the same version of the BDE
at all times. Phase One
4) Having all users share a single copy of the Before you can use a file-server BDE installa-
BDE configuration file guarantees that all tion, you must install the BDE on the file serv-
users are using the same BDE settings. er. Neither the version of InstallShield Express
5) If you need to add an alias, or change any that comes with Delphi, nor the commercial
other BDE configuration setting, you version of InstallShield Express allow you to
only need to make the change once, and install the BDE anywhere but the user’s local
it immediately affects all users. hard drive. One solution is to install the BDE
6) A centralized BDE installation makes it on one workstation, then copy the BDE direc-
easy to have your BDE applications con- tory to the file server. The disadvantage of this
figure the PCs on which they run to use technique is that it leaves the BDE registry
the BDE the first time they run. entries on the workstation pointing to the BDE
33 February 1999 Delphi Informant
Greater Delphi
All your applications must be able to deter-
mine the location of the BDE and its config-
uration file, and
you must be able to change the location of the
BDE easily.
This requires two .INI files. The first has the
same name as your application’s EXE — with an
.INI extension — and is located in the same
directory as the EXE. This file can contain any
of the following entries in its [BDE] section:
[BDE]
ConfigPath=f:\foo\bde\idapi.cfg
BDEPath=f:\foo\bde
Overwrite=True
IniPath=f:\foo\bde.ini
The ConfigPath entry contains the full path to,
and name of, the BDE configuration file. The
BDEPath entry supplies the path to the BDE
Figure 1: The BDE Administrator Options dialog box. directory. By default, the code discussed later in
this article will only create the BDE registry entries
on the local hard drive. This can be easily overcome by letting if they don’t exist. This prevents your program from changing
your application reset the BDE registry entries when it runs. existing entries. But what if you need to move the BDE to a
new directory when a new file server is installed, or for some
While you can change the registry entries to point to the server, other reason? The Overwrite entry solves this problem by
you can also avoid the problem by creating a set of installation telling your program to create the BDE registry entries using
diskettes that install the BDE files as though they were an the information in the .INI file, even if they already exist. By
application. This lets you put the BDE where you want it, and setting Overwrite to True, you could have several versions of
doesn’t create the undesired registry entries. If your develop- the BDE installed on your server, and have different applica-
ment machine is connected to the network to which you’re tions use different versions. You could also have all the appli-
deploying, you can also copy the BDE directory from your cations share the same BDE, but have different applications
machine to the file server. If you use one of the Wise installa- use different BDE configuration files.
tion programs, you’ll have no problem because Wise lets you
install the BDE in any directory you wish. There is one disadvantage to this system: If you have 20
different Delphi applications, and you need to make a
Once the BDE is installed on the file server, you’ll need to set change, you’ll have to change 20 .INI files. The IniPath
the BDE configuration options in the BDE configuration file entry addresses this problem. If the IniPath entry is pre-
on the file server. Choose Object | Open Configuration to sent, all other entries in the BDE section of the applica-
open the configuration file in the network BDE folder. Next, tion .INI file are ignored, and the BDE settings are read
choose Object | Options to display the BDE Administrator from the .INI file in the IniPath entry. This allows you to
Options dialog box shown in Figure 1. have a single .INI file that controls the BDE settings for
many applications. This shared .INI file also contains a
Make sure the Windows 3.1 and Windows 95/NT radio button in [BDE] section, and can contain all the previously shown
the Save for use with group box is selected. If you don’t set this values, except IniPath.
option, some of the configuration parameters will be stored
locally in the Windows registry instead of in the configuration Listing Six (beginning on page 35) shows the code for the
file. If settings are stored in the registry, they must be changed TApplicationIniFile component that automatically config-
on every workstation. However, setting the Windows 3.1 and ures the BDE each time your application runs (this is
Windows 95/NT option causes all settings to be stored in the available for download; see end of article for details).
configuration file, and causes the configuration file settings to Although this is a component, and can be installed on the
be used even if conflicting settings exist in the registry. You can Component palette, you probably won’t want to do so.
also create aliases, or change any other settings at this time. Instead, copy the unit file into your project directory, and
add it to your project. Because it’s likely you’ll have other
Phase Two information unique to each application to store in the
The next step is to get your application to automatically cre- application’s .INI file, adding this unit to each project lets
ate the BDE registry entries each time it runs. This system you easily customize the component to handle application-
must meet the following requirements: specific settings.
34 February 1999 Delphi Informant
Greater Delphi
The overridden constructor of TApplicationIniFile calls its initialization
OpenAppIniFile method. OpenAppIniFile gets the path to AppIni := TApplicationIniFile.Create(Application);
try
the application’s .INI file by using the Application.ExeName AppIni.CreateBDEKeys;
property and changing the file extension to .INI. It then finally
checks for an IniPath entry in the [RemoteIniFile] section. AppIni.Free;
end;
If it finds this entry, it opens the file to which it’s pointing. end.
This allows you to have a family of related applications
share a common .INI file.
Conclusion
The heart of TApplicationIniFile is the CreateBDEKeys method. The advantages of installing the BDE on the file server and
This method begins by setting the Boolean variable sharing it across all users are so great, it’s surprising that Inprise
BDEInstalled to True, and calling OpenBDEIniFile, a method doesn’t recommend or, at least, document this option. It reduces
almost identical to OpenAppIniFile, except that it looks for the the cost of installing the BDE and every application that uses the
IniPath entry in the [BDE] section of the application .INI file. BDE initially, as well as the cost of installing BDE updates in the
The method then retrieves and saves the value of the Overwrite future. Having your applications automatically create the BDE
entry. The registry is accessed and updated using an instance of registry entries offers similar benefits; you’ll never get another call
TRegistry. After the TRegistry object is created, its LazyWrite from a user who just got a new PC and none of the BDE appli-
property is set to False to ensure that changes to the registry are cations run, even though the support techs copied all the files
saved immediately. from the old hard drive. With self-configuring applications and a
central BDE installation, anyone who can create a shortcut can
The next step is to determine if the BDE is already install a BDE application on a new workstation.
installed on this machine. The BDE is properly installed if
the ConfigFile01 and DLLPath entries exist under The only trick to making this system work is to make sure
KEY_LOCAL_MACHINE\Software\Borland\Database the Windows 3.1 and Windows 95/NT compatibility option is
Engine, and the BLAPIPath entry exists under set in the BDE Administrator Options dialog box; this is
KEY_LOCAL_MACHINE\Software\BLW32. If any of unfortunate. The Windows 95/NT only option offers no bene-
these keys are missing, the BDEInstalled variable is set to fits and, in my opinion, should be removed so that all BDE
False. First, Reg.OpenKey is called and passed to the configuration information is always stored in the BDE con-
Software\Borland\Database Engine key. Next, Reg.ReadString is figuration file. This would ensure that any BDE configura-
used to read the ConfigFile01 and DLLPath keys. Reg.OpenKey tion file could be safely shared. ∆
is called a second time to move to the Software\BLW32 key, and
Reg.ReadString is used to read the BLAPIPath value. The files referenced in this article are available on the Delphi
Informant Works CD located in INFORM\99\FEB\DI9902BT.
The final step is to create the BDE registry entries if the BDE
isn’t installed, or if the .INI file contains the Overwrite=True
entry. If you’ve looked at the Software\BLW32 key (using Bill Todd is president of The Database Group, Inc., a database consulting and
Regedit, for example), you’ve seen that, in addition to the development firm based near Phoenix. He is a Contributing Editor of Delphi
BLAPIPath value, there are entries for each of the BDE Informant, co-author of four database-programming books, author of over 60
Language drivers. You don’t have to create these keys when you articles, and a member of Team Borland, providing technical support on the
install the BDE. However, if they’re present, you need to change Inprise Internet newsgroups. He is a frequent speaker at Inprise conferences in
the path in each entry. The code creates a TStringList object the US and Europe. Bill is also a nationally known trainer and has taught
named LanguageKeys, and loads all the value names under the Paradox and Delphi programming classes across the country and overseas. He
was an instructor on the 1995, 1996, and 1997 Borland/Softbite Delphi World
Software\BLW32 key into the StringList by calling the TRegistry
Tours. He can be reached at
[email protected] or (602) 802-0178.
object’s GetValueNames method. It then searches the StringList
by calling its IndexOf method to determine if the BLAPIPath
entry is present. If not, it’s added to the list. Finally, a for loop
iterates through the StringList, and changes the path for each key Begin Listing Six — AppIniFl.pas
to the BDE path that was read from the .INI file earlier. Another
unit AppIniFl;
call to Reg.OpenKey opens the Software\Borland\Database
Engine key. Two calls to Reg.WriteString create the ConfigFile01 interface
and DLLPath entries.
uses
Windows, Messages, SysUtils, Classes, Graphics,
For CreateBDEKeys to work, you must call it before the Controls,
Forms, Dialogs, IniFiles;
program’s default BDE session is created. Therefore, the
only place you can put this call is in the initialization sec- type
tion of one of your program’s unit files. The most natural TApplicationIniFile = class(TComponent)
private
location is the initialization section of the main form’s
AppIni: TIniFile;
unit. Here’s an example:
35 February 1999 Delphi Informant
Greater Delphi
BDEIni: TIniFile; [IniFile]
AppIniPath: string; IniPath=f:\foo
protected
procedure OpenAppIniFile; If the Overwrite=True entry is present any existing BDE
procedure OpenBDEIniFile; entries will be overwritten with the entries from the INI
public file. If the IniPath entry is present the BDE entries
constructor Create(AOwner: TComponent); override; will be read from the file at that location with the same
destructor Destroy; override; name as the EXE and the extension .INI. This allows a
procedure CreateBDEKeys; central INI file to be used even if the app is installed
end; on each workstation. }
const
procedure Register; cIniTrue = 'TRUE';
cOverwriteBDETag = 'Overwrite';
implementation cIniConfigPathTag = 'ConfigPath';
cIniBDEPathTag = 'BDEPath';
uses Registry; cDatabaseEngineKey = '\Software\Borland\Database Engine';
cLanguageDriverKey = '\Software\Borland\BLW32';
const cLanguageDriverTag = 'BLAPIPATH';
cRemoteIniPathTag = 'IniPath'; cConfigPathTag = 'configfile01';
cRemoteIniSection = 'RemoteIniFile'; cDllPathTag = 'dllpath';
cBDESection = 'BDE'; var
Reg: TRegistry;
constructor TApplicationIniFile.Create(AOwner: TComponent); LanguageKeys: TStringList;
begin ConfigPath: string;
inherited; BDEPath: string;
OpenAppIniFile; OverwriteBDEStr: string;
end; S: string;
OverwriteBDE: Boolean;
destructor TApplicationIniFile.Destroy; BDEInstalled: Boolean;
begin I: Integer;
AppIni.Free; begin
inherited; BDEInstalled := True;
end; { Open the INI file that contains the BDE information. }
OpenBDEIniFile;
procedure TApplicationIniFile.OpenAppIniFile; { Read the BDE DLL and Config file paths from the INI
begin file. }
AppIniPath := ConfigPath := BDEIni.ReadString(cBDESection,
ChangeFileExt(Application.ExeName, '') + '.ini'; cIniConfigPathTag, '');
AppIni := TIniFile.Create(AppIniPath); BDEPath := BDEIni.ReadString(cBDESection,
{ If there is a remote INI file path in the INI file, cIniBDEPathTag, '');
open the remote INI file. } if Copy(BDEPath, Length(BDEPath), 1) = '\' then
AppIniPath := AppIni.ReadString(cRemoteIniSection, BDEPath := Copy(BDEPath, 1, Length(BDEPath) - 1);
cRemoteIniPathTag, ''); { Determine if BDE section contains an Overwrite
if AppIniPath <> '' then parameter. If so, and if the value is True, the BDE
begin registry settings in the INI file will overwrite any
AppIni.Free; existing settings on the user's computer. }
AppIni := TIniFile.Create(AppIniPath); OverwriteBDEStr := BDEIni.ReadString(
end; cBDESection, cOverwriteBDETag, '');
end; if UpperCase(OverwriteBDEStr) = cIniTrue then
OverwriteBDE := True;
procedure TApplicationIniFile.OpenBDEIniFile; Reg := TRegistry.Create;
var Reg.LazyWrite := False;
BDEIniPath: string; try
begin Reg.RootKey := HKEY_LOCAL_MACHINE;
{ If there is a remote INI file path in the BDE section, Reg.OpenKey(cDatabaseEngineKey, True);
open that file, else set BDEIni to point to AppIni. } { See if any of the BDE registry keys are missing. If
BDEIniPath := AppIni.ReadString(cBDESection, so, all the keys will be created from the values in
cRemoteIniPathTag, ''); the INI file. }
if BDEIniPath <> '' then if Reg.ReadString(cConfigPathTag) = '' then
BDEIni := TIniFile.Create(BDEIniPath) BDEInstalled := False;
else if Reg.ReadString(cDllPathTag) = '' then
BDEIni := AppIni; BDEInstalled := False;
end; Reg.OpenKey(cLanguageDriverKey, True);
if Reg.ReadString(cLanguageDriverTag) = '' then
procedure TApplicationIniFile.CreateBDEKeys; BDEInstalled := False;
{ Creates the BDE Registry entries using values found in an { If the BDE is not installed, or if the INI file's BDE
INI file with the same name as the application's EXE file section contains the Overwrite=True entry create the
and located in the same directory. The INI file can BDE keys. }
contain the following sections and entries. if (not BDEInstalled) or (OverwriteBDE) then begin
{ Write the BLW32 language driver subkeys. }
[BDE] if BDEPath <> '' then begin
ConfigPath=f:\foo\bde\idapi.cfg LanguageKeys := TStringList.Create;
BDEPath=f:\foo\bde { If there are no language driver keys, add the
Overwrite=True BLAPIPATH key to the list so it will be created.}
IniPath=f:\foo try
Reg.GetValueNames(LanguageKeys);
36 February 1999 Delphi Informant
Greater Delphi
if LanguageKeys.IndexOf(
cLanguageDriverTag) = 0 then
LanguageKeys.Add(cLanguageDriverTag);
for I := 0 to Pred(LanguageKeys.Count) do begin
{ If this entry is the BLAPIPATH entry, just
write the BDEPath. If it is the entry for one
of the language driver files, extract the
file name from the existing entry and add it
to the end of the BDE path. }
if UpperCase(LanguageKeys[I]) =
cLanguageDriverTag then
begin
S := BDEPath;
end
else
begin
S := Reg.ReadString(LanguageKeys[I]);
S := BDEPath + '\' + ExtractFileName(S);
end;
Reg.WriteString(LanguageKeys[I], S);
end; // for
finally
LanguageKeys.Free;
end; // try
end; // if
{ Write the DatabaseEngine subkeys. }
Reg.OpenKey(cDatabaseEngineKey, True);
if ConfigPath <> '' then
Reg.WriteString(cConfigPathTag, ConfigPath);
if BDEPath <> '' then
Reg.WriteString(cDllPathTag, BDEPath);
end; // if
finally
Reg.Free;
end; // try
end;
procedure Register;
begin
RegisterComponents('DGI', [TApplicationIniFile]);
end;
end.
End Listing Six
37 February 1999 Delphi Informant
At Your Fingertips
Delphi / Tips
By Robert Vivrette
They Were There All Along
Fun with SysUtils
T hose handy functions we need are often right under our noses — and we
aren’t even aware of it. Occasionally, I dig through the VCL source to see
what interesting functions are there that I hadn’t seen before. This month’s
“At Your Fingertips” is devoted to often-overlooked functions inside Delphi’s
SysUtils unit.
Finding the Switch When the Lights Go Out characters that define a switch (usually a
Many applications support command-line hyphen or a slash) and a Boolean value to
parameters. They’re often used to control the indicate if you want the test to be per-
initial state of the application, or to provide formed with case sensitivity.
information about a configuration or docu-
ment file to open. Figure 1 shows a simple example of how this
function works. The demonstration shows
One of the more awkward things a program- the command line present when the applica-
mer must do is determine whether com- tion was launched (available through the
mand-line switches have been passed into an global variable CmdLine). It then allows you
application. This is often complicated by the to type in a switch value to test for. When
fact that the switches can be in any order, you click on the Test! button, it tells you if
might be a different case than expected, or the switch exists on the command line.
even worse, might be a string value with
embedded spaces. In the past, we had to There are a wide variety of things for
write our own routines to determine this which you can test. For example, the rou-
information. Generally, it involved scanning tine correctly finds strings with embedded
the command line, examining every character spaces (as long as you put them in quotes
to see if it fit what we expected. on the command line). It can also spot
numbers, or switches with trailing on/off
Help has arrived! FindCmdLineSwitch is an characters like the plus (+) or minus (-)
extremely handy routine that does all this signs. Here’s all the code necessary to run
for you. It’s surprisingly powerful and flexi- this demonstration:
ble. All you need to do is pass in the switch
for which you’re looking, followed by the procedure TForm1.FormCreate(Sender: TObject);
begin
Label2.Caption := CmdLine;
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
if FindCmdLineSwitch(Edit1.Text,[
'-','/'],True) then
Label4.Caption := 'Switch Exists!'
else
Label4.Caption := 'Switch Not Present';
Figure 1: A simple example of how FindCmdLineSwitch works. end;
38 February 1999 Delphi Informant
At Your Fingertips
The Hunt Is On
Finding a file on a disk drive is a fairly common pro-
gramming chore. Often, you’ll use the FileExists func-
tion to determine if a particular file exists. The prob-
lem with FileExists is that it only looks at a single
location on a disk drive. If the file name passed into
FileExists includes path information, it will look
there. If it has no path information, it will look at the
current directory for the file.
But what if the file could potentially be in one of a Figure 2: A demonstration of StringReplace.
number of locations? You could do something like this:
if not FileExists('C:\MyApp\string') then takes a string and replaces in it all occurrences of one sub-
if not FileExists('C:\MyApp\Dir1\string') then string with another substring. It then returns the new
if not FileExists('C:\MyApp\Dir2\string') then
... string as its result.
The first parameter used is the string through which you’ll
But that starts getting to be a real maintenance nightmare! be scanning. Next comes the string to search for and
delete, followed by the string to put in its place. Last
Enter FileSearch. This function accepts two parameters: The comes a parameter that allows you to specify a set of flags.
first is the name of the file for which you are looking, and the Currently, these flags can be rfReplaceAll, which tells the
second is a list of directories (separated by semicolons) in function to replace all occurrences of the string rather than
which to perform the search. For example, the code: just the first one, and rfIgnoreCase, which tells the func-
tion to be case insensitive.
TheFile := FileSearch(
'string', 'C:\MyApp;C:\MyApp\Dir1;C:\MyApp\Dir2'); Figure 2 shows a demonstration program on the use of
StringReplace. Clicking the Do It! button replaces all occur-
would perform a search similar to the example shown previously. rences of text in the Search For edit box with the text in the
Replace With edit box. The results are shown in the Result edit
If FileSearch finds the file in any of the directories, it will box. Here is the code that accomplishes this:
return a fully qualified path and file name to that file. If it
doesn’t find it, it returns an empty string. This comes in par- procedure TForm1.BitBtn1Click(Sender: TObject);
ticularly handy when you’re looking for a string that might begin
Edit4.Text:= StringReplace(
be in more than one location. Edit1.Text,Edit2.Text,Edit3.Text,
[rfReplaceAll,rfIgnoreCase]);
FileSearch simulates the behavior present in the Path com- end;
mand used by DOS and Windows. In fact, if you want to
search the path currently defined on the machine, you can There is one added capability worth noting: StringReplace
also use the Windows API function SearchPath. The Win32 allows for the possibility of multi-byte characters.
API Help discusses this function in more depth.
Conclusion
A String in Need of Replacing One of the best training tools an aspiring Delphi developer
Delphi includes a number of interesting string-handling rou- has at his or her disposal is to look through the functions
tines for inserting and deleting strings from within another available in the source code provided with Delphi
string, two of which are Insert and Delete. (Professional and Client/Server packages). Sometimes you
find little gems, such as those outlined here, that can save
However, these functions will only perform a single insertion many hours of programming effort. ∆
or deletion at a time. What happens if you want to replace all
occurrences of a string with another string? In the past, pro- The files referenced in this article are available on the Delphi
grammers often had to build a repeat or while loop, where the Informant Works CD located in INFORM\99\FEB\DI9902RV.
string would be examined for a string using the Pos command.
When the string was found, you could delete it with Delete,
and insert the new string using Insert. The loop would contin- Robert Vivrette is a contract programmer for Pacific Gas & Electric, and Technical
ue until no more occurrences of the search string existed. Editor for Delphi Informant. He has worked as a game designer and computer
consultant, and has experience in a number of programming languages. He can
This problem is solved by means of one of the more over- be reached via e-mail at
[email protected].
looked functions in SysUtils: StringReplace. This function
39 February 1999 Delphi Informant
New & Used
By Warren Rachele
LEADTOOLS Imaging 10
A Sharp and Focused Imaging Toolkit
U sers have come to expect the ability to use graphic and document images
in their work. Such workaday applications as product databases now rou-
tinely offer an image of the product in addition to the text description.
Paperless document systems require the ability to acquire and annotate their
collected documents. All this is complex enough, but the number of formats
available for storing graphic images makes the task of including image pro-
cessing even more difficult. LEAD Technologies, Inc. has taken all this into con-
sideration, and provides a one-stop solution.
LEADTOOLS Imaging is an imaging toolkit Microsoft, and Hewlett Packard. Now in ver-
of considerable depth that should be consid- sion 10, the LEADTOOLS Imaging package
ered when you need to integrate full-featured is a collection of more than 600 functions,
image processing into an application. The properties, and methods that provide high-
toolkit, which you can purchase off the shelf, and low-level control for image processing
is the same LEADTOOLS technology that’s through a single ActiveX control.
been licensed by such heavy hitters as Corel,
The toolkit, implemented through
Color and Grayscale LEADTOOLS, offers imaging technology
JPEG and LEAD compressed (.JPG and .CMP) in 15 broad categories: scanning, color
GIF and TIFF with LZW compression conversion, display, multimedia effects,
TIFF formats annotation, image processing, compression,
BMP formats image format import/export filters, imag-
Icons and cursors(.ICO and .CUR) ing common dialog boxes, database imag-
PCX formats (.PCX and .DCX) ing, Internet imaging, optical character
Kodak formats (.PCD and .FPX) recognition (OCR), screen capture, multi-
DICOM format (.DIC) media, and the FlashPix extension. It works
Exif formats (.TIFF and .JPG) with any development tool that supports
Windows metafile format (.WMF) ActiveX components and, as expected, it
Photoshop 3.0 format (.PSD) worked flawlessly with Delphi 4.
Portable network graphics format (.PNG)
TrueVision TARGA format (.TGA) Installation
Encapsulate PostScript (.EPS) Plan to devote some time for installation.
SUN raster format (.RAS) An installation program creates the appro-
WordPerfect format (.WPG) priate directory structures and registers the
Macintosh picture format (.PCT) ActiveX controls with Windows; the rest is
Windows AVI (.AVI) up to you. The documentation for the
Bi-tonal (1-Bit) installation process is brief, so you’ll need to
TIFF CCITT and other FAX formats be familiar with Delphi’s Import ActiveX
LEAD 1-bit format (.CMP) Control process to successfully integrate the
Miscellaneous 1-bit formats (.MAC, .IMG, and .MSP) LEADTOOLS controls into your IDE.
Figure 1: Some of the over 60 file formats supported by Each component must be individually
LEADTOOLS. imported from the list of registered controls.
40 February 1999 Delphi Informant
New & Used
Displaying an image in the sizable control is simply
a matter of passing the image’s file name as a para-
meter to the LEAD control’s Load function. Once
the image is defined, a wide array of manipulation
is possible. The Main control is also fully database-
aware, and can load images directly from a BLOb
field.
The LEAD Main image control can be left at its
default size and automatically sized to the client
window during the display process. If the size of
the image exceeds the client size, scroll bars can be
automatically added. It isn’t necessary to predefine
the image type before loading it; image files carry
information in their headers that define the storage
format, and LEADTOOLS reads this to decide
which filter is appropriate to use. LEADTOOLS
supports more than 60 file formats (see Figure 1).
The image displayed in Figure 2 is a sample stored
Figure 2: This image is stored in LEAD’s CMP format. in LEAD’s proprietary CMP format.
The greatest strength of LEADTOOLS Imaging is
its image-processing capabilities; much more can
be done with images beyond simply displaying
them. Image manipulation is the core of the
LEADTOOLS Imaging product, and version 10
expands its stable to support over 2,000 effects,
more than 80 shapes, and over 30 gradients. The
ViewMaster project has implemented a few of
these features through menu options. The image
in Figure 3 has had the Oilify effect applied to it,
rendering the image in the form of an oil paint-
ing. Nearly all the artistic effects available to the
user can be interactively adjusted to meet the users
needs. For example, the Slider control is used to
determine the depth of the Neighborhood setting,
modifying the sharpness of the brush strokes used
in the “painting.”
LEAD Technologies, through all its iterations, has
learned from its customers and adapted to their
Figure 3: The Oilify effect renders the image as an oil painting.
common uses for the controls. To this end, many
of the commonly used methods and properties
have been encapsulated in dialog boxes for the
Once the proper paths are set, each of the visual and non- convenience of the developer. The Imaging Common Dialog
visual components takes its place on the ActiveX page of the libraries provide a set of common dialog boxes that combine
Delphi Component palette. LEAD includes all the controls Windows dialog-box functionality with imaging features
they make on the CD, and all of them are registered with from LEADTOOLS. Placing the non-visual LEADDlg con-
Windows; thus, they appear during the import process. trol on a form incorporates the libraries into your project,
Depending on the package you purchase, you may not be and gives you access to extended dialog boxes for FileOpen,
licensed to use all of them, and the controls will not function FileSave, ColorResolution, Image Processing/Filtering, and
until a license number unlocks them. Don’t clutter up your Effects. Based on a programmer-determined set of user inter-
palette with non-functional controls. face flags, the FileOpen dialog box adds or removes features
such as the preview window and the File Information button.
Using the LEAD Control
Putting the toolkit to work is as simple as placing the LEAD A feature new to version 10 is the Pan Window. The Pan
Main control onto a form in your project. This control encap- Window is a scaled view of the bitmap being displayed in the
sulates the majority of the imaging functionality of the toolkit. Main control. It becomes an active control that allows the user
41 February 1999 Delphi Informant
New & Used
panel that were internally manipulated. Internally
controlled acquisition bypasses the scanner soft-
ware, and relies on the application settings to man-
age the process.
Acquiring data from the screen is another area in
which LEADTOOLS shines. Simple, rectangular
regions of the screen can be captured directly using
the Main control alone. To extend the options and
control over the screen capture function,
LEADTOOLS includes a separate, non-visual con-
trol that encapsulates a greater range of methods and
properties. The LEADTOOLS Screen Capture con-
trol can capture a wide variety of regions from the
screen: FullScreen, ActiveWindow, ActiveClientArea,
MenuUnderCursor, WindowUnderCursor,
SelectedObject, MouseCursor, and Wallpaper. An
area can also be selected using the freehand tool or a
number of capture containment shapes, including
circle, square, ellipse, and rounded-corner rectangles.
In addition to screen capturing, LEADTOOLS can
extract icon, bitmap, and cursor resources from 16-
Figure 4: The ScanMaster demonstration program. or 32-bit Windows EXE and DLL files.
to display different regions of the image without using the Internet Imaging
scroll bars. For example, if an image is zoomed in the Main LEADTOOLS offers two alternatives for supporting Internet
control, the Pan Window displays the original image. The user images. The first is a Netscape plug-in that works with
can use the cursor that appears over the Pan Window to shift Navigator or Internet Explorer. The plug-in DLL enables the
the displayed area of the main image to a specific point. browser to display any of the LEADTOOLS-supported
image formats. This control is designed for use by HTML
Image Acquisition programmers to provide support for image data contained in
In addition to displaying and manipulating image data, your desktop application, or received through an Internet
LEADTOOLS is adept at acquiring images. The ActiveX con- connection. Among other server requirements, the control
trol supports TWAIN devices in its native form, and optional- requires that Height and Width values be embedded into the
ly supports ISIS input devices. Implemented directly through page. When the image is displayed on the page, a right
the Main control, the TWAIN interface provides all the neces- mouse-click activates a menu offering options to copy to the
sary functionality to add image acquisition: device selection, Clipboard, zoom in and out, return the image to normal size,
acquisition, and error management. The Main control’s file- and save as a supported file format.
save methods and properties are used to determine the final
file-type characteristics. The TWAIN interface allows the pro- Purchase What You Need
grammer to choose from two development paths, depending LEAD Technologies has extensively reorganized their product
on the target device. One choice that can be used with a scan- line. In previous incarnations, LEADTOOLS users could
ner, such as the HP ScanJet that was used for testing in this select the modules to include in their toolkit. With the excep-
article, is to let the scanner’s own software handle the settings tion of FlashPix support, the purchase of individual classes
for the scan. In the ScanMaster demonstration program, this has disappeared; the package includes all formats, and you
method was implemented through the Get It button. Few set- pick the ones you want.
tings need to be programmatically managed using this
method. A pair of calls to the TwainSelect and TwainAcquire The software reviewed here is the LEADTOOLS Imaging
methods will handle the job of acquiring the image for your package. All the other toolkits build upon it. LEADTOOLS
program. Figure 4 shows the demonstration program after Imaging Pro includes the 16- and 32-bit APIs, which allow
acquiring an image and placing it in the Main control. programmers to choose between a high-level component
interface, or working at a lower level through direct interac-
The programmer can also exercise complete control over tion with the DLLs.
nearly all aspects of the image acquisition process. The
TWAIN device, and all the applicable settings, can be con- LEADTOOLS Multimedia opens access to audio and video
trolled from within the program, or through the user inter- tools. Programmers have access to three new capabilities:
face. The second button, The Hard Way, uses this develop- audio and video, including AVI and MPEG; capture from
ment path, then displays some of the settings on the status any Window’s Video Capture Device, such as VCR or
42 February 1999 Delphi Informant
New & Used
Camcorder; and Internet streaming video. LEADTOOLS If the process were that simple,
Multimedia Pro includes the APIs needed for low-level access this would be acceptable, but
to the DLLs. there are other steps involved in
using the control. To locate
LEADTOOLS Document Express includes all the technol- these, you must find the section
ogy included in the Multimedia toolkit, and expands it to in the documentation that refers LEADTOOLS Imaging 10 is an out-
meet the specialized demands of the document imaging to your chosen development standing package, providing a one-
stop solution for Delphi developers
market. The package includes the tools necessary to incor- tool. The majority of the docu- who wish to answer the demand for
porate annotation of images through text, graphics, and mentation for Delphi users applications with full-featured image
processing capabilities. This off-the-
sticky notes, and numerous other document processing focuses on version 2, with a new, shelf toolkit provides high- and low-
features. These come in the form of new filters that allow very brief chapter for version 4. level control for image processing
through a single ActiveX control. The
the user to perform operations on images, such as despeck- Code snippets with some com- installation process is a bump in an
le, deskew, and other clean-up functions, as well as ultra- ments are provided to teach you otherwise smooth implementation.
fast rotation of the image. The LEADTOOLS Document how to use the control’s features. LEAD Technologies, Inc.
Express Suite includes the Xerox TextBridge, a full- A property and method reference 900 Baxter St.
Charlotte, NC 28204
featured OCR class. is included in the manual, but
for the most part, it refers back Phone: (800) 637-4699 or
(704) 332-5532
LEAD also offers LEADTOOLS Medical Express for special- to the tool-specific examples for Fax: (704) 372-8116
ized, medical image processing. Images from MR and CT expansion on the topic. E-Mail: [email protected]
Web Site: http://www.leadtools.com
scanners are in a high-resolution format named DICOM. Price: US$495
The Medical Express toolkit includes filters for this format The Windows Help files are for-
that allow multiple-level gray-scale mapping and separation matted much the same as the manual, but are available at the
through a bit-range. It also includes all the capabilities push of 1. The code snippets, when provided, can be
included in the Multimedia packages. quickly copied into your program to speed the development
process. Plan on a long, steep learning curve to fully use the
LEAD has also modified their license agreement. Previous LEADTOOLS technology.
versions of the license required royalty payments based on
the number of copies of your application that were sold. Conclusion
With the exception of the Document Express, Document If you need any form of image processing in your develop-
Express Suite, and Medical Express packages, and the ment efforts, LEADTOOLS Imaging is an outstanding pack-
FlashPix module, the imaging technology is now royalty- age to include in your toolbox. The depth and quality of the
free. These packages require a written royalty agreement on classes are outstanding, and the amount of functionality you
file with LEAD before their distribution. Another licensing can quickly add to an application is simply amazing. As you
issue that arises with the Imaging packages is support for work with the controls and discover the capabilities available,
GIF and TIFF LZW formats. This technology is copyright- you’ll find yourself implementing functionality in your appli-
and patent-protected by the Unisys Corporation, so you cation that probably wasn’t a part of the original specification.
must license it from them before unlocking support for it in
LEADTOOLS. The documentation and installation provide bumps in an
otherwise smooth implementation. Although the majority
Documentation of the documentation is written for Delphi 2, the controls
The quality of the documentation is the biggest negative worked flawlessly with Delphi 3 and 4. Once located, the
issue that I identified with the LEADTOOLS products. Help files also worked without fail. Current users of
The documentation for LEADTOOLS, with the exception LEADTOOLS should consider this a must-have upgrade.
of a slim, 61-page summary of features, is provided in The new features and increased speed make it worth the
PDF format and through Windows Help files. With a upgrade costs, although the integration of the new control
product this extensive, online documentation makes learn- into an existing project will take some work. ∆
ing an onerous process; the back-and-forth of locating
topics, pages, and references is better suited to the printed
page. Printed manuals for all the products are available at
an additional cost. Warren Rachele is Chief Architect of The Hunter Group, an Evergreen, CO software
development company specializing in database-management software. The com-
Learning to implement the ActiveX control is a test of your pany has served its customers since 1987. Warren also teaches programming,
abilities to decipher and extract meaning from the terse state- hardware architecture, and database management at the college level. He can be
reached by e-mail at [email protected].
ments contained in the 1,364 pages of documentation for the
Main control alone. The introductory sections refer to the
product in the most general of terms. For example, to load an
image into the control, you are instructed to use the follow-
ing method: LOAD Method.
43 February 1999 Delphi Informant
TextFile
Delphi 4 Bible
Tom Swan, author of Delphi 4 Bible, end of Part II, the reader will have
has had a long and prolific publishing explored areas that include menus, but-
career, documenting numerous pro- tons and check boxes, toolbars, lists, text
gramming languages and environments. of all types, files and directories, and dia-
The respect his books receive lends an log boxes. Because each of the elements of
additional note of credibility to Delphi the user interface was explored in discrete
as a development tool. Although the fashion, the reader will not have seen all
reader level is categorized as “beginning of these tasks coalesce into a complete
to advanced,” this work is light on application. Don’t worry, Swan has a plan.
beginner topics and heavily weighted The application as a whole is the sub-
toward the advanced programmer look- ject of Part III. Having covered the user
ing for task-specific information. interface components common to most
Swan begins by reviewing Delphi 1, and applications in the previous section,
reiterates the key features of each version Swan is free to concentrate on the
since before introducing the new features “guts” of an application. These chapters
of Delphi 4. Features such as code com- are not in-depth references; rather, Pascal for a quick DOS utility? Not after
pletion, tool-tip expression evaluation, there’s enough information in each to you see how easily a CRT application can
and the Module Explorer are reviewed in give the reader a taste of how Delphi be created in Delphi.
greater detail. Chapter 1 concludes with a supports the development of different The book includes a CD-ROM con-
feature that is often the best in each chap- types of applications. The experienced taining all code from the book. It’s
ter, the Expert-User Tips. At the end of programmer will realize that the “appli- unlikely you’ll use it, however, given the
each chapter, Swan provides a list of quick cations” discussed are often found size of the sample programs. I find that
shots and insights into Delphi program- together in a single application; still, the concepts are reinforced by creating the
ming. This mixture of hints, ideas, and single-topic chapters work well. sample projects and typing the code.
fixes is an extremely valuable resource — Swan covers graphics applications, print- Tom Swan is an excellent writer; his
one that readers will return to repeatedly. er applications, and working with the prose is clear, and is not disrupted by poor
The remaining chapters in Part I expand Clipboard, DDE, and OLE. Chapters attempts at humor. Should you buy
on this quick survey of the Delphi envi- dealing with database applications and the Delphi 4 Bible? Not if your programming
ronment. Forms and components are development of charts and reports are too skills are at the beginning stage. There is
briefly discussed, and some basic projects brief; these topics often fill entire books. little here that will help the novice pro-
pull the beginning programmer into the However, Swan succeeds in providing the grammer advance. Advanced users may
RAD world. The programmer learns to essence of the task. All the information is find much of the information in the book
place components on forms, and set the well presented and, again, the Expert-User repetitive, though the Expert-User Tips
appropriate properties and code methods Tips that conclude each chapter are wor- are worthy of two or three reads. This
to build the simple projects. To support thy of careful examination. book is aimed squarely at intermediate
the beginning programmer, a minimal The final chapter is a grab bag of programmers looking to broaden their
introduction to the Pascal language is advanced techniques and topics. Ranging Delphi programming skills.
unfortunately missing from this guide. from the creation of a console application,
Part II explores the development of the to creating a DLL, to creating Internet — Warren Rachele
user interface. Delphi 4 Bible takes a task- applications, the chapter is fun to read
oriented approach to developing the user and explore. However, the depth of some Delphi 4 Bible by Tom Swan, IDG
interface, showing the reader how to com- of this material could have been expanded Books Worldwide, 919 E. Hillsdale
plete each step of the process. The first to prevent the reader from wondering Blvd., Suite 400, Foster City, CA
task explored is programming the key- how to fully implement some of the ideas. 94404, http://www.idgbooks.com.
board and mouse. As with each of the What makes the chapter work are the
task chapters, Swan introduces the com- broad strokes Swan uses to paint the ISBN: 0-7645-3237-5
ponents and methods used to include many areas in which Delphi can serve the Price: US$49.99
these services in an application. By the programmer. Tempted to fire up Turbo (953 Pages, CD-ROM)
44 February 1999 Delphi Informant
File | New
Directions / Commentary
Toward a Stronger Delphi Community
In the “Developer Ethics” column I wrote last September, I explored the darker side of our community: cer-
tain sleazy, shameless “developers” — despicable rip-off artists who undermine the hard work of talented
developers by stealing their software. Fortunately, they’re a tiny minority.
In stark contrast, our Delphi community is Groups of developers also establish infor- are just four unique features in the com-
rich with developers who make positive con- mal relationships with each other, sharing mercial product and value it accordingly.
tributions, freely sharing the fruits of their discoveries, tools, and code freely, while You don’t have to be a Wall Street guru
work with the rest of us. I’ll discuss some critically evaluating each other’s work as to see the business implications inherent
notable examples. Interestingly, I’ll also beta testers. Project Jedi, its ups and downs in that scenario.
touch on a gray area of situations that can notwithstanding, has been a resilient and
be interpreted positively, or negatively, impressive endeavor. Even during times of Further, the freeware tool may have more
depending upon your perspective. little or no activity on the discussion bugs, may not be as well designed as the
threads, developers have been quietly work- commercial product, or may come up
Sharing the wealth. We all enjoy getting ing on various projects. short on documentation and customer
something for free, right? The developer support. But a buyer who’s not familiar
who distributes his or her tools freely also Another major strength of the Delphi with the products may not be aware of
benefits, making a name in the industry. community is the impressive collection of these possible problems. Conceivably, he
With the Internet, such distribution has tool creators and third-party companies. or she might focus solely on the feature
become almost trivial in its ease. We can What generally sets them apart from the set of the two products, and base the per-
find numerous examples of developers who average shareware developer is the quality ceived value of the commercial product
have become very well known using this of the tools they provide, including sup- on the sum of its unique feature set. In
strategy. A recent one is Gerald Nunn. port and documentation. While you may the previous example, the potential cus-
Because of his popular freeware library, pay much less — or nothing — for a tomer would place the value of the com-
GExperts (which I hope to write about in shareware or freeware tool, you cannot mercial product (at $5 a feature) at just
an upcoming issue), many of you already expect to get the same quality. $20. If the company were to reduce its
know his name. There are many more selling price to a figure close to this, it
examples of these innovative writers who David and Goliath. The gray area I might risk bankruptcy.
have made great tools freely available — alluded to is the heated battles that can
Marco Cantù, Ray Lischner, and Bob occur when a freeware/shareware author Clearly this is one gray area, and there may
Swart, to name a few. decides to compete with a commercial be others. However, even those developers
product. While the up-and-coming who decide to play the role of David
The abundance of freely available Delphi developer may have nothing to lose, this against Goliath are seldom satisfied with
tools has greatly enhanced this develop- is hardly the case with the established simply cloning the giant’s product; they
ment environment. If you doubt the pop- vendor. Commercial companies must usually want to add something new and
ularity of freeware/shareware in the continue to make a profit. Otherwise, distinctive. Do you agree with this assess-
Delphi community, consider the popular- they cannot provide new and improved ment? On the whole, with our superior
ity of the major Web sites like the Delphi tools. If they find themselves under- development tool, with our strong lines of
Super Page. Such sites have enhanced the mined by other developers who essential- communication, and with our many tal-
Delphi community by making ly clone their components or tools, their ented developers, we indeed have a strong
freeware/shareware components and tools profits could very well shrink. Delphi community. ∆
easily available. And there are some real
gems out there. Many come with source One could argue, “If a full-time compa- — Alan C. Moore, Ph.D.
code, so we can learn new techniques ny can’t compete with some guy working
while acquiring useful tools. This has 10 hours a week, then that company Alan Moore is a Professor of Music at
definitely made our community stronger. shouldn’t be in business.” But consider Kentucky State University, specializing in
the counter argument: “A free product music composition and music theory. He has
The Internet has strengthened our that is similar in functionality to a sell- been developing education-related applica-
community in other ways. In the past ing product has the effect of decreasing tions with the Borland languages for more
couple of years, the Delphi Internet the perceived value of that commercial than 10 years. He has published a number of
venues have expanded exponentially. product.” How so? If the commercial articles in various technical journals. Using
Once just a handful of CompuServe product sells for $100 and has 20 fea- Delphi, he specializes in writing custom com-
forums and Usenet newsgroups, Delphi tures, while the competing freeware ponents and implementing multimedia capa-
communication venues now include list product duplicates 16 of them, how will bilities in applications, particularly sound
servers, search engines, and sophisticated the potential customer compare them? and music. You can reach Alan on the
newsgroups. That customer may conclude that there Internet at [email protected].
45 February 1999 Delphi Informant