0% found this document useful (0 votes)
65 views44 pages

Delphi Informant 95 2001

This document is the June 1996 issue of the Delphi Informant magazine. It includes articles about implementing multithreading in Delphi 2 programs, comparing Paradox tables and Local InterBase for database applications in Delphi, using the TDBNavigator component in Delphi 2, and creating custom Delphi components. It also reviews the Memory Monitor for Delphi tool and the book "Developing Custom Delphi Components".

Uploaded by

reader-647470
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
65 views44 pages

Delphi Informant 95 2001

This document is the June 1996 issue of the Delphi Informant magazine. It includes articles about implementing multithreading in Delphi 2 programs, comparing Paradox tables and Local InterBase for database applications in Delphi, using the TDBNavigator component in Delphi 2, and creating custom Delphi components. It also reviews the Memory Monitor for Delphi tool and the book "Developing Custom Delphi Components".

Uploaded by

reader-647470
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 44

June 1996, Volume 2, Number 6

Do the Strand
Delphi 2 Multithreading

Cover Art By: Tom McKeith

ON THE COVER
9 Do the Strand — Joseph C. Fung
35 Dynamic Delphi — Andrew Wozniewicz
The 32-bit version of Windows features true multitasking through
It’s the final installment of a four-part series on DLLs. Mr Wozniewicz
multithreading, a technology that can enhance the performance of
concludes by discussing: dynamically loading and releasing a DLL, using
your Windows 95 and Windows NT applications. Mr Fung explains
dynamically loaded DLLs, and accessing data in a library. He even pro-
how to implement multithreading capabilities into your Delphi 2
vides a DLL summary for your quick reference.
programs using clear, workable examples.

FEATURES REVIEWS
15 Informant Spotlight — Kevin Bluck
40 Memory Monitor for Delphi
Most programmers use Paradox tables for their Delphi database
Product review by Robert Vivrette
applications. Depending on the application, however, Local InterBase
Resource leaks are the bane of Windows programming.
may be the better tool. To help you decide which product best suits
Fortunately, there’s now a tool to help Delphi developers
your needs, Mr Bluck provides a comprehensive analysis and
stop the bleeding. It’s named MemMonD and our own
comparison of Paradox and InterBase.
Mr Vivrette puts it through its paces.
20 DBNavigator — Cary Jensen, Ph.D.
43 Developing Custom Delphi Components
In addition to ranges, DataSets, and SQL queries, Delphi 2 provides
Book review by Richard Wagner
filtering capabilities. Dr Jensen introduces the cast of characters —
TDBDataSet’s TTable, TQuery, and TStoredProc —you’ll need to
become familiar with to help your users “drill down” into their data DEPARTMENTS
with Delphi 2. 2 Editorial by Jerry Coffey
23 From the Palette — Ray Konopka 3 Delphi Tools
If you’re interested in creating Delphi components, Mr Konopka’s article 6 Newsline
is a great place to start. His TRzAddress component appears simple. Peel 44 File | New by Richard Wagner
back its layers, however, and you’ll see that this well-built control
demonstrates the effective encapsulation of other components.

28 OP Tech — Keith Wood


There’s good recursion and there’s bad recursion. Mr Wood examines the
positive side of this phenomenon in Object Pascal by exploring a number
of interesting implementations, including: calculating factorials, building
binary trees, and creating mind-bending fractals.

Delphi Informant June 1996 1


Symposium

Why does The Unabomber love Visual Basic?


Because he hates technology.
— Anonymous

love that one. It has all the elements of a great belly laugh. It’s short, topical, and has that cruel
I edge a joke needs to really draw blood. Cruel because it rings true — as any VB-to-Delphi convert
can attest.

The tone was only slightly more seri- The important thing is that there is a
ous at the First Annual Delphi happy, healthy, and still burgeoning
Informant Readers Choice awards din- third-party market for Delphi. This is
ner, where representatives of the best especially remarkable given the fact
third-party Delphi tools were present- that Delphi is just over a year old.
ed with lovely tokens of your affec- Product of the Year was a runaway
tion. However, there is no affectionate with Woll2Woll Software’s InfoPower
term for the award itself — a hand- taking top honors. Other winners
some Lucite tower encapsulating the included Pacheco and Teixeira’s Delphi
DI “Big D” and its happy electron Developer’s Guide for Best Delphi
satellites (a dire warning of a radia- Book, and Successware International’s
tion leak or the first image in an “Our Apollo Rock-E-T for Best Delphi
Friend the Atom” slideshow, depend- Add-In (see the April DI for all the
ing on your mood). No candidate winners). Zack Urlocker, Director of Delphi
jumps to the forefront. For example, a Product Management, and Delphi cre-
“Dimmy” (from Delphi Informant And despite the fact the DI Readers ator, Anders Hejlsberg arriving at the
Delphi Informant Readers Choice
Magazine) doesn’t sound like some- Choice Awards weren’t for them, the
Awards. They’d just left the Jolt Cola
thing you’d really like to win. three top members of the Delphi devel- awards where Delphi won for
Obviously there’s work to be done in opment team — Delphi Chief Technical Excellence.
this area. Or perhaps I should heed Architect, Anders Hejlsberg; Director
today’s chic admonition and simply of Delphi Product Management, Zack
“not go there.” Urlocker; and Director of Delphi home an industry award for them-
Development, Gary Whizin — were selves, and then looked on as the
kind enough to attend the festivities. community they created was honored
for its achievements. ’Course the faji-
Actually, our little affair must have tas and margaritas didn’t hurt.
been a bit of a let down for the three-
some, since earlier that evening they’d Thanks for reading,
accepted the Jolt Cola award for
Technical Excellence, beating an
impressive array of competitors
including white-hot Java, Symantec
C++, and Visual Basic (okay, so most
of the competitors were impressive). Jerry Coffey, Editor-in-Chief

But if it was anticlimactic for the Internet: [email protected]


Borland crew, they never let on. CompuServe: 70304,3633
Woll2Woll Software’s Roy Woll graciously Come to think of it, it was probably Fax: 916-686-8497
accepts the Delphi Informant Readers just the thing after receiving the Jolt Snail: 10519 E. Stockton Blvd., Ste.
Choice Product of the Year award. Cola award. The Delphi team took 142, Elk Grove, CA 95624

Delphi Informant June 1996 2


Delphi TurboPower Announces Async Professional 2.0 for Delphi
TurboPower Software
T O O L S Co., of Colorado Springs,
CO, has announced Async
New Products Professional 2.0 for Delphi
and Solutions (APD), a serial communi-
cations library of native
VCL components for
Delphi.
Version 2.0 offers 32-bit
support and fax compo-
nents for converting files,
sending and receiving fax
files with fax modems,
viewing and printing fax
files, and converting print protocols, debugging and Price: US$199, includes full source
output to fax format. APD tracing tools, modem data- code, printed documentation and online
supports both 16- and 32- base, and an event-driven help file, free technical support by
New Delphi Book
bit applications. dialing engine. File transfers phone, e-mail, and fax, and a 60-day
Teach Yourself Delphi 2 APD now includes TAPI and other operations run in money-back guarantee.
in 21 days
Dan Osier, Steve Grobman, devices, eliminating many the background. Contact: TurboPower Software Co.,
and Steve Batson modem configuration prob- A free trial version of ADP PO Box 49009, Colorado Springs,
SAMS Publishing
lems. is available on TurboPower’s CO 80949-9009
APD also features protocol Web site and BBS. Phone: (800) 333-4160 or
status, modem selection, The demonstration ver- (719) 260-9136
and dialing dialog boxes. It sion has the full functional- Fax: (719) 260-7151
includes ANSI and VT100 ity of APD, but only runs BBS: (719) 260-9726
emulators, ZModem, when Delphi is operating. CIS Forum: GO PCVENB
YModem, and XModem Web Site: http://www.tpower.com

New Delphi Components for Internet Programming


ISBN: 0-672-30863-0
Price: US$35 (706 pages) Software Avenue, Inc. of The InternetClient com- The kit also contains a
Phone: (800) 428-5331
Gilbert, AZ has announced ponent can be used to cre- series of documents collect-
the release of The Internet ate various applications, ed from various Internet
Developer’s Kit for Delphi. from a Finger client to a sites. This information
The kit contains two new World Wide Web Browser. describes the standards and
Delphi components, a docu- The InternetServer com- protocols for common
ment viewer, and 16- and ponent can manage as Internet services. The
32-bit versions of the com- many as 100 simultaneous printed documentation
ponents. The two Delphi client connections. provides installation
components, InternetClient With this component, instructions, a detailed
and InternetServer, provide and an Internet SLIP description of both compo-
access to the Windows account, Delphi developers nents, and programming
Socket library (WINSOCK). can create Internet servers. examples. Context-sensitive
online help is also available
from within Delphi.

Price: US$249.95, plus shipping and


handling.
Contact: Software Avenue, Inc.,
PO Box 1324, Gilbert, AZ 85234
Phone: (800) 813-0876 or
(602) 813-0876
E-Mail: CIS: 76455,3236
Web Site: http://ourworld.compu-
serve.com:80/homepages/Software-
Avenue/

Delphi Informant June 1996 3


Delphi Game Tools for Delphi 2
Mobius Ltd. of Springfield,
T O O L S MA announced the availabili-
ty of Mobius Fast Sprites.
New Products These native Delphi 2 com-
and Solutions ponents are designed to pro-
vide animation through the
Windows 95 Games SDK
and DirectDraw DLLs.
Fast Sprites enables Delphi 2
game developers to control
video hardware using COM
architecture. They allow devel-
opers to set screen resolutions,
grab the CPU, and command
the video card to create sprites
and animation without code. Xpression video card with a component. Registered users receive
Fast Sprites also handles DX4 66 CPU. Fast Sprites updates free for one year.
New Delphi Book
screen (buffer) flipping and are the first components of Contact: Mobius Ltd., 75 Dwight Rd.,
Delphi 2 Unleashed
updating in sync with the a complete suite; additional Springfield, MA 01108
Charles Calvert
SAMS Publishing vertical blank, giving components are currently Phone: (413) 827-9747
smooth tear-free graphics. planned. Fax: (413) 827-9747
Refresh rates have been Internet: CIS: 73563,533
clocked at over 70 FPS on Price: Promotional offer US$149, Web Site: http://www.xmission.com/-
an ATI Mach 64 Graphic includes online help file and joystick ~imagicom/mobius/mobius.html

Shoreline Releases New Version of VisualPROS for Delphi 2


Shoreline Software of reducing the amount of user-drawn help clouds.
Vernon, CT has released ver- code redundancy often VisualPROS supports any
sion 2.0 of its VisualPROS found in VBXes and other type of application created
for Delphi, an add-on com- components. with Delphi 2. The 32-bit
ISBN: 0-672-30858-4 ponent set for enhancing In this version, Shoreline versions are not simple
Price: US$59.99
(1,400 pages, CD-ROM) graphical user interfaces. The has provided optimized com- ports of the 16-bit con-
Phone: (800) 428-5331 new version adds 16- and ponents for bitmap manage- trols, but are optimized for
32-bit versions of controls, ment, drop-in management 32-bit execution.
optimizations, and full of both Windows 3.x .INI VisualPROS’ online help
source code in Object Pascal. files and Registry elements of has been upgraded, and is
Written in Delphi 2, Windows 95, Windows NT, now integrated with Delphi
VisualPROS controls typi- and online help. 2. Printed documentation is
cally add less than 20K- They also enhanced the also available.
30K to an application. Tileback and HelpCloud Currently VisualPROS is
They use the existing components. The Tileback available from Breakthrough
Delphi 2 framework, component now includes Technologies. For informa-
texture mapping for dialog tion call (800) 813-7909,
box backgrounds with 25 (800) 323-1809, (602) 258-
texture choices, simultane- 2715, or fax (602) 258-2805.
ous display management of An OCX version of the
both gradient fills and product is expected to ship in
bitmaps on form back- the second quarter of 1996.
grounds, one component
support for both MDI and Price: US$149.95
regular form backgrounds Contact: Shoreline Software,
with automatic detection, 35-31 Talcottville Rd. #123, Vernon,
and nine directional gradi- CT 06066-4030
ent fill types. Phone: (860) 870-5707
The HelpCloud component Fax: (860) 870-5727
now offers display, placement, E-Mail: Internet: [email protected].
and transparent support for Web Site: http://www.shoresoft.com

Delphi Informant June 1996 4


RoboHELP 95 HTML Edition Released by Blue Sky Software
Blue Sky Software Corp., of
Delphi
La Jolla, CA, has released T O O L S
RoboHELP 95 HTML
Edition. This version allows New Products
users to create HTML and and Solutions
Windows Help files from the
same source code. RoboHELP
95 HTML Edition turns
Microsoft Word 7 for
Windows 95 into an author-
ing tool capable of creating
HTML files and Windows
Help systems simultaneously.
The RoboHELP 95
HTML Edition recognizes
the similarities between HTML, making it available basics of HTML authoring.
HTML and Windows on the Internet. In addi- New Delphi Book
Help, and provides a way tion, the RoboHELP 95 Price: RoboHELP 95 HTML Edition,
Borland’s Official
to create the HTML Home HTML Edition is currently special promotional price, US$699 No-Nonsense Guide to Delphi 2
page (including all HTML, the only authoring tool (list price is US$897). Michelle Manning
SAMS Publishing
GIF, and MAP files, and providing simultaneous Contact: Blue Sky Software Corp., 7777
hyperlinks for a Web site HTML and Help creation Fay Ave., Suite 201, La Jolla, CA 92037
and/or intranet). from single source code. Phone: (800) 459-2356 or
The RoboHELP 95 The RoboHELP 95 (619) 459-6365
HTML Edition can also be HTML Edition includes Fax: (619) 459-6366
used to move information Mastering HTML for Help E-Mail: Internet: [email protected]
from Windows Help to Authors, a guide to the Web Site: http://www.blue-sky.com

Wintertree Software Releases Updated Thesaurus Software


Wintertree Software Inc. has according to parts of speech: Price: Win16 SDK, US$399
ISBN: 0-672-30871-1
announced the release of the adjectives, adverbs, nouns, (CAN$499); Win32 SDK, US$399 Price: US$25 (386 pages)
ThesDB Thesaurus Engine, and verbs. In addition, users (CAN$499); and Source SDK, Phone: (800) 428-5331

version 3.1, a database of syn- can add custom word cate- US$1,499 (CAN$1,899). The Win16
onyms and a software library gories and synonyms. and Win32 SDKs are available bundled
to access the database. ThesDB is available in for US$699 (CAN$879).
Developers can use ThesDB three forms: the Win16 Contact: Wintertree Software Inc.,
to add thesaurus and syn- SDK for 16-bit Windows 69 Beddington Ave., Nepean, Ontario,
onym-finding capabilities to applications; the Win32 Canada, K2J 3N4
their 16- and 32- bit applica- SDK for 32-bit develop- Phone: (800) 340-8803 or
tions. The thesaurus dialog ment; and the Source SDK (613) 825-6271
box permits keyword searches, which includes the ANSI-C Fax: (613) 825-5521
user-thesaurus maintenance, source code to the ThesDB CIS Forum: GO WINSDK
and synonym selection. The engine and related software. Web Site: http://fox.nstn.ca/~wsi/
initial keyword and selected The Source SDK is suitable
synonym can be set and for applications developed
retrieved by the application, on non-Windows plat-
replacing the selected word forms, and includes Win16
with a synonym. ThesDB also and Win32 SDKs. All SDK
suggests replacements for mis- types include a 50-page
spelled or unknown words. programmer’s guide and a
The new version includes license to distribute applica-
50,000 synonyms in over tions royalty-free.
3,100 word categories in the A demonstration version of
American English main the- ThesDB is available from
saurus. Word categories in the Wintertree’s CompuServe
main thesaurus are classified forum and Web page.

Delphi Informant June 1996 5


News Delphi 2 Takes First Place in NSTL Comparative Ratings Report
Scotts Valley, CA — In a
product evaluation of client/-
Gupta’s SQLWindows,
Delphi Client/Server Suite 2
Fortune 1000 companies
and government agencies.
L I N E server development tools con- was cited as “the fastest, most “Delphi’s performance is
ducted by National Software versatile, and easiest to use of head and shoulders above all
June 1996 Testing Laboratories (NSTL), the evaluated products.” its competitors,” reported
Delphi Client/Server Suite 2 NSTL’s Software Digest NSTL in the March
received the highest overall and PC Digest Ratings Software Digest Ratings
rating out of four develop- Reports are used by many Report on Client/Server
ment packages. industry analysts and editors Development Tools (Volume
Outperforming Powersoft’s as a source of objective 13, Number 3). “(Delphi)
PowerBuilder Enterprise for analysis of personal comput- offers performance superiori-
Windows, Microsoft’s Visual er hardware and software, ty across the board: database
Basic Enterprise Edition, and and also as buying guides for and non-database operations,
browsing entire database
User-Based Pricing for Borland’s InterBase tables, executing queries, or
Scotts Valley, CA — Borland additional users to an existing searching for data.”
International has outlined its server. This license is not plat- For a copy of the NSTL
new user-based pricing form specific. Media and doc- report, contact the NSTL
scheme for InterBase. Under umentation are not included, Testing and Distribution
Delphi 2 Help File this program, the initial server but client libraries may be Center at (610) 941-9600.
Updated purchase includes five user copied from the original serv-
Scotts Valley, CA —
Borland has released an
licenses. Pricing for additional er bundle diskettes. InterBase Borland Announces
updated VCL.HLP file for users is identical across Intel 4.0 Client/Server Access Java-Enabled
Delphi 2. The file,
platforms, including NT, License Bundle for a single
VCL.ZIP, is available on
Borland Online NetWare, and SCO. For user is priced at US$170.
InterBase InterClient
(http://www.borland.com)
and Borland’s new Delphi
applications requiring more The 10-user pack for Scotts Valley, CA —
2 CompuServe forum than five users, additional InterBase 4.0 Client/Server Borland International Inc.
(GO BDELPHI32).
An improved, but interim
user license packs are available Access License Bundle is the has announced the InterBase
version, this version fixes in single, 10, and 20 packs. same as the single user bun- InterClient. Written in Java,
most of the broken jumps
reported by the help com-
The InterBase 4.0, 5 User dle, except it includes 10 InterBase InterClient uses
piler. It also amends the Starter Bundle includes a sin- client and 10 server access InterBase as a SQL database
missing popup menus for
the properties, methods,
gle copy of the media and licenses. The 10-user pack is server to anonymously
and events of TTable, documentation, and both priced at US$1,350. access Web databases.
TQuery, TStoredProc, and
TSession.
client and server licenses for InterBase 4.0 Client/Server Based on Borland’s work
five users. This package is pur- Access License Bundle, 20- with JavaSoft and the JDBC
chased for every new server, user pack, has 20 client and standard, the InterBase
and is priced at US$850. It is 20 server access licenses. Its InterClient is designed to
available for Windows NT, features are identical to the deliver the benefits of Internet
NetWare, and SCO OS 3.0. single user bundle in all technologies into corporate
The InterBase 4.0 other respects, and it’s organizations. It will contain
Client/Server Access License priced at US$2,400. client and server components
Bundle, single user, contains For more information, visit for interfacing with the Web.
one client and one server Borland Online at The InterBase InterClient is
license so developers can add http://www.borland.com. expected to enable distributed
transaction processing by the
Software Development ’96 West Update Java client and the database
San Francisco, CA — Amid intranet development. server, eliminate updating
a sea of vendors, over Borland International, client database libraries,
12,000 attendees gathered at Informix, Microsoft, NeXT, reduce overall traffic on the
Moscone Center in San Powersoft, Silicon Graphics, network, speed access to serv-
Francisco, CA last March Sun Microsystems, and Sybase er data, provide automatic
for the ninth edition of were among the key vendors download of public Internet
Software Development ’96 adding Internet and intranet data to the client, and will not
West. Over 100 new prod- development tools to their require developers to install
ucts were previewed at the product groups. client database libraries.
three-day event, most
“SD ’96 West: Internet Tools Unveiled ” “Borland Announces InterClient”
addressing Internet and continued on page 7 continued on page 7

Delphi Informant June 1996 6


News Borland Ships ReportSmith 3.0 for Windows 95 and Windows NT
Scotts Valley, CA — Borland
International Inc. has begun
bases. ReportSmith 3.0 fea-
tures 32-bit functionality,
from other applications, and
integration with Delphi 2.
L I N E shipping ReportSmith 3.0, a updated native drivers for ReportSmith 3.0 is included
new release of its client/serv- accessing 32-bit client in Developer and Client/-
June 1996 er reporting and query tool libraries from Sybase, Server Suite versions of
for Windows 95 and Oracle, Informix, Microsoft Delphi 2. The stand-alone
Windows NT. SQL Server, and Gupta package of ReportSmith 3.0
ReportSmith 3.0 includes a SQLBase, as well as 32-bit is available for US$179.95.
set of tools for creating ODBC drivers. It also has Previous owners of
columnar, crosstab, and multitasking capabilities, ReportSmith may upgrade
form reports using live data support for PC and SQL for US$129.95.
while working with local databases, a new API layer For more information, call
tables or client/server data- for controlling ReportSmith Borland at (800) 233-2444.
SD ’96 West: Internet Tools Unveiled (cont.)
Borland demonstrated how support Microsoft technolo- several new Java-enabled
to develop Internet tech- gies are now available to cus- development tools, such as
nologies in client/server tomers electronically on Java Products Everywhere
applications with Delphi 2. Borland Online (http://www.- (JOE), Internet Workshop,
They also demonstrated how borland.com) and Borland’s Solstice FireWall-1 2.0, and
Delphi Wins Jolt Cola
Award for Best Delphi can use Microsoft’s CompuServe forums. Borland Solstice Messaging for Solaris
Development Tool Internet technologies, also announced plans to add (IMAP 4). Call Sun
At Software Development
’96 West, Delphi was pre- including the Internet server support for Netscape’s Microsystems at (415) 336-
sented with the “Jolt Cola” Control Pack, ActiveX, and NSAPI as well as other popu-
award for Technical
6483 for more information.
Excellence. It dominated WinInet and ISAPI applica- lar Web server APIs. Software Development ’96
the competition which
included Microsoft’s Visual tion programming interfaces In addition, Borland East is scheduled for October
C++ and Visual Basic, (APIs), to create a Delphi announced the release of their 29-31, 1996 at the Washing-
and SunSoft’s Java.
Web browser client applica- new C++ Development Suite ton Convention Center in
tion running on Windows 5.0 for Windows 95 and Washington, DC.
95, and a Delphi Web server Windows NT, and previewed
application running on Latte, a RAD development Borland Announces
Windows NT. tool for Java developers. InterClient (cont.)
Delphi components that Sun Microsystems unveiled The InterBase InterClient
using JDBC will allow Java
applets to be downloaded
and run in a Web browser,
bypassing traditional time-
access methods such as a
Common Gateway Interface
(CGI). The benefits to IS
managers include higher
throughput speeds, lower
traffic on the ’Net, and
enhanced modular applica-
tion design. InterBase
InterClient is also designed
to simplify intranet access by
presenting a unified interface
to all services and resources,
On its way. Borland’s Latte. without requiring additional
software or hardware.
Borland’s C++ Wins SD ’96 Superbowl InterBase InterClient has
San Francisco, CA — Development ’96 West. A been designed and written
Borland International Inc. team of five judges chose for the Windows and UNIX
announced its newly released 32-bit platforms. Beta copies
Borland C++ 5.0 won the “Borland’s C++ Wins SD ’96 Superbowl” are expected to be available
C++ Superbowl at Software continued on page 8 the second quarter of 1996.

Delphi Informant June 1996 7


News Borland Announces C++ 5.0 and ObjectScripting Contest
San Francisco, CA —
Borland International Inc.
Classes (MFC) compilation
support; and Visual
(http://www.borland.com).
The grand prize winner will
L I N E has begun shipping its C++ Database Tools (VDBT). win a laptop computer
Development Suite 5.0, that Borland C++ supports (estimated at US$4,000)
June 1996 combines five tools and namespaces, the standard and be offered a speaking
Borland C++ 5.0. C++ library, OCXes, inte- engagement at the 1996
Along with Borland C++, grated 32-bit resource edit- Borland Developers
the suite features ing, and integrated 32-bit Conference, scheduled for
CodeGuard 32/16, PVCS debugging. It also features July in Anaheim, CA.
Version Manager, and free Java-compatible devel- Scripts awarded an
InstallShield Express. It opment tools, (including Honorable Mention will be
also includes the new Sun’s Java Development placed on a CD (or
AppAccelerator for Java, a Kit), the Borland Debugger diskette). Visit Borland
just-in-time compiler. for Java, AppExpert, and Online for more details.
Available separately or as color syntax highlighting
part of the Suite, Borland for Java code. Fung Joins PCSI
C++ 5.0 includes a 32-bit Borland C++ Development Englewood, NJ —
hosted environment for tar- Suite 5.0 is priced at Professional Computer
geting multiple platforms, US$499.95, and Borland C++ Solutions, Inc. (PCSI)
including Windows 95, 5.0 is priced at US$349.95. announced Joseph C. Fung
Windows NT, Windows Upgrades are available on has been named Director of
3.1, and DOS. It also CD-ROM, and include Technology and Tools.
includes ObjectWindows online documentation; Previously Fung was a prin-
Library (OWL) 5.0, sup- diskettes and printed docu- cipal of Farpoint Systems
porting the Windows 95- mentation are available sepa- Corp., a New York/New
based common controls and rately at an additional charge. Jersey-based consulting firm
16-bit emulation of most For more information, call specializing in developing
Windows 95 common con- Borland at (800) 645-4559. database applications with
trols; Microsoft Foundation In addition, Borland has Delphi, ObjectPAL, and
announced a new Object- Visual Basic. The operations
Scripting contest, with the of Farpoint Systems are
grand prize winner taking being merged into the opera-
home a new laptop com- tions of PCSI.
puter. Included in Borland Fung currently writes for
C++ Development Suite 5.0 Delphi Informant, Paradox
and Borland C++ 5.0, Informant, and other publica-
ObjectScripting allows tions. He is the author of
developers to modify and Paradox for Windows Essential
configure Borland’s inte- Power Programming (Prima,
grated development envi- 1995) and a co-author of
ronment (IDE). Delphi In-Depth
Customers can submit (Osborne/McGraw-Hill,
scripts directly to Borland 1996). He is the architect of
via Borland Online ScriptView and AppExpert,
perennial winners of the
Borland’s C++ Wins SD ’96 Superbowl (cont.) Paradox Informant Reader’s
Borland C++ 5.0 as the overall Borland C++ Development Choice Award.
winner over Microsoft’s Visual Suite 5.0 and Borland C++ In addition, Fung has served
C++. The judges selected 5.0. For complete details, as a guest lecturer for the
Borland C++ as the winner visit Borland Online at Borland Paradox 5.0 World
based on the total number of http://www.borland.com. Tour, chaired an Advisory
development problems Software Development is Board for the Borland
Borland C++ programmers produced by Miller Developer Conference ’95,
solved in an hour. Freeman, Inc. For show and served as advisory
Borland recently information, visit Miller board member for the
announced two new ver- Freeman’s Web site at Borland International
sions of its C++ products: http://www.mfi.com. Conference ’94.
Delphi Informant June 1996 8
On the Cover
Delphi 2 / Object Pascal

By Joseph C. Fung

Do the Strand
An Introduction to Multithreading with Delphi 2
Do the Strand-o, when you feel low
It’s the new way, that’s why we say
Do the Strand
— Roxy Music, “Do the Strand”
For Your Pleasure ...

he Win32 API introduces the concept of multithreading to Windows 95


T and Windows NT programs. Using multithreading, you can improve
performance by partitioning an application into multiple paths of execu-
tion. Although multithreading is very powerful, it adds a whole new level
of complexity to an application.
Fortunately, Delphi 2 provides the Windows NT, each thread may even
TThread class so that you can work with have its own dedicated CPU.)
threads and the VCL (Visual Component
Library) in a thread-safe manner. Threads are useful because they let you
more efficiently use the computer’s
Spindles and Spools CPU(s) by partitioning an application
In Windows 95 and Windows NT across threads, and, also, perform work
(referred to here as Win32), an application or lengthy processing in the background
consists of a process and one or more while the user continues to interact with
threads. A process is an instance of the the application.
application and has its own virtual address
space, global variables, and operating sys- The Win32 API provides facilities for you
tem resources. By itself, a process does not to create and use additional threads in
execute. Instead, each process has a prima- your application. However, these facilities
ry thread of execution that obtains time alone do not permit you to use VCL com-
slices from the CPU. This primary thread ponents in a thread-safe manner without
executes the code in the application. When corrupting data. (“Thread safe” means
this thread terminates, the process ends. that two or more simultaneous processes
are designed in such a way that the code
Win32 allows you to create additional being executed in one thread does not
threads, or simultaneous paths of execu- interfere with the execution of the other.)
tion, within an application. Each of
these threads shares the address space of Fortunately, Delphi provides a TThread
the parent process so they have access to class that encapsulates the threading
the same global variables and resources. mechanism and works with the VCL.
Also, the operating system gives each of You can use the TThread class along
these additional threads time slices so with Win32 API functions to add pow-
they appear to execute concurrently. (In erful multithreading capabilities to your
a multiprocessor machine under application.

Delphi Informant June 1996 9


On the Cover
When to Use Additional Threads Unfortunately, this can leave Windows 95 applica-
Here are some situations where multithreading can be very tions unresponsive because many 32-bit API func-
useful: tions, including those that handle the user interface,
Often a user will initiate some work that requires a actually call the 16-bit code base. To minimize the
lengthy process. With Windows 3.x and single-threaded effects of this, you can create additional threads to
applications, the user interface is unresponsive while the perform the work in the background while the pri-
work is going on because the primary (and only) thread mary thread is waiting on the user interface.
cannot process window messages while it’s working on
some other task. This leaves the user in an unproductive Creating Threads Using the TThread Class
state while waiting for the task to complete. You can pre- The Delphi 2 TThread class encapsulates the multithread-
vent this by creating an additional thread to finish the ing mechanism so that you don’t have to rely solely on
task in the background. This keeps the main user inter- Win32 API function calls to create threads. More impor-
face very responsive and available to handle user interac- tantly, the TThread class provides a facility to work with
tion while the work is being completed. VCL components in a thread-safe manner, and also sup-
plies a simpler alternative to thread local storage, a way to
In an MDI (multiple document interface) application associate data with individual threads.
where you have multiple documents represented by
child forms, you can give each child form its own thread You can work entirely with threads using the Win32 API.
to perform any lengthy processing. If one of the child However, you will be working at a lower level and also
forms initiates a lengthy process, the user can move to must supply your own framework for synchronizing access
one of the other child forms and continue working. to the VCL. If you do not synchronize your thread’s access
to VCL properties and methods, you run the risk of cor-
Windows NT supports machines with one or more rupting shared data and generating access violations.
processors by distributing threads across the set of
CPUs. When you run a single-threaded application on The TThread class does not completely hide all the details
a machine with multiple processors, you may not be of working with the Win32 API. Instead, it’s a thin wrap-
efficiently using the additional processing power. By per that removes the drudgery of VCL synchronization and
partitioning the application using threads, you can bal- thread local storage. When you instantiate a TThread, the
ance the load and significantly increase the applica- class creates a new thread and stores its handle internally.
tion’s performance.
The TThread class is defined in the CLASSES unit, so be
With a Windows 3.x application, you frequently used sure to include this unit in your uses statement. Figure 1
a timer to perform work on a periodic basis by divid- lists the protected and public properties of the TThread
ing a repetitive or lengthy task into logical units and class, while Figure 2 lists the protected and public methods.
executing the code when the timer event occurred.
Any such code should be examined because it’s a nat- Using the TThread Class
ural candidate for multithreading. The steps required to add multithreading to your Delphi 2
applications are fairly straightforward. For each new thread,
Windows 95 supports both 16- and 32-bit Windows you first derive a new TThread class from the base TThread
applications. To provide compatibility with 16-bit class. Then you add code to instantiate the new object. You
Windows applications and to maintain a good level of can define the new thread class by adding the TThread type
performance, Windows 95 contains a significant portion declaration into an existing unit, or by creating a separate unit
of the old 16-bit Windows API system code. This code is to hold the TThread definition from the Object Repository.
called by all Windows 3.x applications and by Windows
95 applications that make certain Win32 API calls, Property Description
including those to the GDI and USER modules. This FreeOnTerminate Specifies whether the VCL automatically destroys
the thread upon termination. Default is False.
16-bit code was never designed to be used simultaneous-
Handle The thread handle.
ly by multiple threads, so access to it is protected.
Windows 95 internally uses Win16Mutex, a system-wide Priority Lets you set/get the thread’s relative priority.
mechanism for guaranteeing exclusive access to the code. OnTerminate An event property for the event handler that is exe-
cuted when the thread terminates.

Essentially, whenever a thread makes a call to a pro- ReturnValue The value returned by a thread.

tected 16-bit API function, any other thread Suspended A Boolean variable that lets you set/get the thread’s
suspended state.
attempting to call any other 16-bit API function
waits until the first thread yields to Windows or Terminated A read-only Boolean that indicates if the thread
should terminate.
releases control. If an application doesn’t yield, or
ThreadID The thread’s ID.
stops processing window messages for a long time,
all these other applications will be tied up. Figure 1: TThread class properties.

Delphi Informant June 1996 10


On the Cover
Method Description
Create Create is the thread constructor.

Destroy Destroy is the thread destructor.

DoTerminate DoTerminate calls the OnTerminate event handler, if


one exists. DoTerminate executes as part of the thread,
as opposed to OnTerminate, which executes as part of
the process. It is unusual to override this method.

Execute Execute is a virtual, abstract method that you over-


ride to specify the thread's behavior. Do not call this
method; it is automatically called by the constructor.

Resume Resumes execution of a suspended thread.

Suspend Suspends execution of a thread.

Synchronize Synchronizes access to VCL properties or methods.

Terminate Sets a flag that tells the thread to end.

WaitFor Suspends execution until a specified thread is signaled.

Figure 5: The default unit created for the new TThread type.

in your code. Finally, you should add the actual code to


create the new TThread and work with it. This code may
perform functions to suspend or resume execution of the
thread or even terminate it. These steps are described in
the following sections.

Creating a Thread
To create a new TThread, you declare a TThread class, a
variable or member variable of this class, and then call its
Create constructor. A constructor of the TThread type has
the following syntax:

Figure 2 (Top): TThread class methods. Create( Suspended : Boolean );


Figure 3 (Bottom): The New Items dialog box.
When you call the constructor you pass to it a single
To create a new thread unit for an existing project using Boolean parameter, Suspended, that indicates if the thread
the Object Repository, select File | New. Delphi displays should be created in a suspended state. Normally, you
the New Items dialog box, as shown in Figure 3. would pass a value of False so that the thread begins execu-
tion immediately.
When you select the
Thread Object icon from Assuming that you have declared an object named
this dialog box and click MyThread of the type TThread, the following code seg-
OK, the New Thread
ment would create and execute it:
Object dialog box appears,
prompting you to name the Figure 4: The New Thread Object MyThread.Create(False);
dialog box.
new class (see Figure 4).
Placing the Code for the Background Work
After entering the name, a new unit appears containing a The TThread class defines a virtual abstract method,
basic TThread class declaration. For example, if you have a Execute, that you override to implement the code for
new project, and then use the Object Repository to add a your background task. The constructor for TThread calls
second unit for a thread class named TMyThread, your Execute immediately after creating the thread so you
screen will resemble Figure 5. don’t need to run it explicitly. When Execute is finished
running, the thread terminates and sets a return value
After declaring the new class, you need to define the code that you can query using the ReturnValue property.
that goes into the TThread’s member functions. At a mini-
mum, you should place the thread’s main worker code into The code that you place into Execute may perform some
the TThread’s Execute method, the place reserved for this background task such as a lengthy calculation or query. If
code. For instance, if your code performs a background cal- the code resides in a loop, the loop should contain some pre-
culation, you should place this calculation in Execute. defined condition that breaks out when it’s True, such as
when a certain number of iterations have passed. You should
Next, you should add code to store the TThread variable also place a test inside the loop to see if the Terminated prop-
in a global or member variable so that you can reference it erty is True. If so, your code should immediately exit the
Delphi Informant June 1996 11
On the Cover
Execute method. (The reason why you need this additional
// This method accesses the caption of a Label component.
piece of code will become clearer later.) The following is an procedure TMyThread.UpdateDisplay
example of the implementation code for an Execute method: begin
MyForm.ProgressLabel.Caption :=
'Up to ' + IntToStr(Count);
procedure TMyThread.Execute;
end;
begin
while MoreWork do begin
procedure TMyThread.Execute;
CalculateSpreadsheet; { Perform some work }
begin
if Terminated then
while WorkToDo do begin
Exit;
DoSomeWorkMethod;
end;
end;
Synchronize(UpdateDisplay);

Using VCL Components from within a Thread if Terminated then


Exit;
There are special considerations to make if you plan to
end;
access any of the VCL components from within your end;
thread. This is because the VCL is not implicitly
thread-safe. In other words, any unprotected access of a Figure 6: The UpdateDisplay method for the TMyThread object.
component from within a thread may potentially cor-
rupt data or cause an access violation. This includes Within the Execute method for the Thread object, a while
calling any methods, and/or reading or writing from loop continues to execute so long as a variable named
any of a component’s properties. WorkToDo evaluates to True. Within this loop, a call is made
to a method named DoSomeWork. Next, this thread needs to
Access to the VCL must be protected because multiple update the display of the Label component. To do this, it
threads may try to work with the same component calls Synchronize and passes UpdateDisplay as an argument.
simultaneously, possibly leading to data corruption or
other unwanted side effects. Also, for performance and Suspending and Resuming Threads
efficiency, the VCL caches Windows GDI (Graphic The TThread class provides the Suspend and Resume
Device Interface) objects that handle screen painting. methods to suspend and resume execution of a thread.
When a thread is suspended, it’s idle and no CPU
Windows does not allow two or more threads to have simul- cycles are given to it.
taneous access to a GDI object. This may happen because
the VCL stores and reuses GDI objects — in this case, you To suspend a thread, just call the TThread object’s Suspend
can get an access violation with multithreaded access. method from another thread (primary or otherwise). A
thread can even suspend itself by calling its own Suspend
Using Synchronize with the VCL. To address the issue of method. You shouldn’t do this, however, unless you expect
thread-safe VCL access, the TThread class provides a another thread to reawaken the suspended thread. To
Synchronize method. If your thread needs to access a VCL resume execution, just call the TThread’s Resume method.
component, the thread should not do it directly. Instead,
you should put this code into a separate method, and then Thread Priority
execute this method by calling Synchronize. The TThread class defines the Priority property so that you
can dynamically change the priority of a thread. A thread’s
Synchronize has the following syntax: priority determines when and how often it’s scheduled for
execution by the operating system. By setting one thread’s
Synchronize( Method : TThreadMethod ); priority higher than another, you give it more CPU time. A
thread’s priority level ranges from 0 to 31, with 31 being
When you call Synchronize, you send a method to it by the highest level. By default, a newly created thread adopts
value. Synchronize then executes this method in a the same priority level as its parent process.
thread-safe manner. In most cases, you will want the
method that you pass to Synchronize to be a member of Setting a Thread’s Priority. A thread’s priority is measured
the same derived Thread object. Doing so provides the relative to the priority of its process. The VCL defines seven
method with access to the object’s private interface and priority levels that you can use to set the thread’s relative pri-
variables. ority. They are part of the TThreadPriority enumerated type.

The code fragment in Figure 6 shows how your code The five main priority levels are:
might appear, and demonstrates how you can update a 1) tpLowest
component’s property from within a thread. A method 2) tpLower
named UpdateDisplay is implemented for the TMyThread 3) tpNormal
class. Within this method, a Label component’s Caption 4) tpHigher
property is updated. 5) tpHighest

Delphi Informant June 1996 12


On the Cover
A thread with a relative priority level of tpNormal has a A Multithreading Example:
priority level equal to its process. With priority levels of Using Child Forms and Worker Threads
tpHigher and tpHighest, the thread’s relative priority is 1 In an MDI application, or in an application that displays
higher and 2 higher than its process, respectively. With a form for each task, multithreading may be appropriate.
priority levels of tpLowest and tpLower, the thread’s relative With multithreading, you can let each form have its own
priority is 2 lower and 1 lower than its process. thread. This way the user can initiate a background task in
one form, and then continue to interact with other forms
There are also two special priority levels: tpIdle and while the background work is progressing.
tpTimeCritical. If a thread’s priority is tpIdle, its priority is
always set to 1, unless its process is 24 or higher (real- In such a scenario, the primary thread handles all the
time). In this case, the thread’s priority level is set to 16. If work of managing the user interface and responding to
the thread’s priority is tpTimeCritical, its priority is always window messages. Each time the user initiates a new task,
set to 15, unless its process is 24 or higher. In this case, the primary thread creates a new form and gives it its own
the thread’s priority is set to 31. “worker” thread.

Terminating a Thread This next example illustrates how you can open new
There are two recommended ways to terminate a thread forms, each owning its own worker thread. The lengthy
when using the TThread class: you can call Exit from the task in this example is represented by iterating through a
TThread’s Execute method, or you can call the TThread’s long loop and updating a track bar to show the progress.
Terminate method.
When you run the example in project WORK.DPR, the
Calling Exit to Terminate a Thread. The bulk of the code Worker Thread Examples form appears. Each time you
responsible for performing your background task resides in press the New Thread button on this form, a new child
the Execute method. When this code finishes running, it form is created with its own thread, and immediately
should exit the Execute method. This can happen implicit- begins its work. Each new form has a unique caption
ly as the last action of the method or if you call Exit. that identifies the “worker” (Worker 1, Worker 2, etc.).
When Execute finishes, the thread terminates and the
TThread class takes care of any cleanup. Each form also contains a Suspend/Resume button that
allows you to suspend and resume the thread. Finally, if
Calling Terminate from Another Thread. A second way to you close the form before the thread is finished, the
terminate a thread is to call the thread’s Terminate method. form closes and the thread terminates.
Typically, you do this when you want to terminate a specif-
ic thread from the main thread or from another thread. Figure 7 depicts the main form surrounded by two
worker forms. Listing One (on page 14) shows the .PAS
Terminate does not actually terminate the thread, but file for this example.
sets the Terminated property to
True. It’s then up to your code to
check the value of Terminated
from inside the thread’s methods,
and to act appropriately to exit the
Execute method. If you do not
have any code that does this, call-
ing Terminate is ineffective.

The following code segment illus-


trates how this code might appear:
procedure TMyThread.Execute;
begin
while SomeCondition do begin
{ Do some work here }
DoSomeWork;
if Terminated then
Exit;
end;
end;

After performing a unit of work,


the if statement checks the value of
Terminated, and exits the method if
Figure 7: The demonstration project at work, showing the main form and two of its
it’s True.
worker forms.

Delphi Informant June 1996 13


On the Cover
Conclusion Begin Listing One — The WORKTHD.PAS file
Multithreading is a welcome feature to Win32 unit workthd;
(Windows 95 and Windows NT) applications, but can
add complexity and unique problems of its own. interface

The TThread class encapsulates the threading process, uses


providing a facility for working with the VCL in a Classes, Forms;
thread-safe manner, and offering a way to associate
type
local data with each thread. This lets you pursue multi-
TWorkerThread = class(TThread)
threading in earnest and add power and flexibility to private
your business applications. ∆ { Private declarations }
MoreWork: Boolean;
FOwnerForm: TForm;
Portions of this article abridged and reprinted by permis-
protected
sion from “Using Multithreading,” Delphi In-Depth by procedure Execute; override;
Cary Jensen, Loy Anderson, Joseph C. Fung, Ann public
Lynnworth, Mark Ostroff, Martin Rudy, and Robert property OwnerForm:
TForm read FOwnerForm write FOwnerForm;
Vivrette, published by Osborne/McGraw-Hill Companies,
procedure UpdateDisplay;
Inc. Copyright 1996 by Osborne/McGraw-Hill end;
Companies, Inc. ISBN: 0-07-882211-4.
implementation

The demonstration project referenced in this article is uses WorkForm, Windows, SysUtils;
available on the Delphi Informant Works CD located in
INFORM \JUNE \96 \DI9606JF. { TWorkerThread }

procedure TWorkerThread.UpdateDisplay;
begin
with (FOwnerForm as TWorkerForm) do begin
with WorkProgressBar do

Joseph C. Fung is Director of Technology and Tools at PCSI, a leading


client/server and Internet/Intranet consulting and development firm. He writes if Position < Max then
for Delphi Informant and Databased Advisor; and is the co-author of Delphi In- begin
Depth and the author of Paradox for Windows Essential Power Programming. Position := Position + 1;
Mr Fung is the architect of AppExpert and ScriptView, perennial winners of the WorkProgressLabel.Caption :=
Databased Advisor Reader’s Choice Award and Paradox Informant Reader’s IntToStr(Position) + ' %';
Choice Award. Recently, Mr Fung chaired an Advisory Board for the Borland if Position = Max then
Developer Conference. begin
MoreWork := False;
PCSI, Professional Computer Solutions, Inc., is a national leader in developing WorkerButton.Caption := 'Done';
SQL applications using Delphi, Visual Basic, and Access, and using Microsoft end;
SQL Server, Sybase, and Oracle server technologies. PCSI is a Borland end;
Connections Partner and Microsoft Solution Provider at the Partner level. The end;
end;
main number for PCSI is (201) 816-8002.
procedure TWorkerThread.Execute;
begin
MoreWork := True;
while MoreWork do begin
{ Do some work here }
// Simulate work by putting thread to sleep 100 ms.
// Actually, we're just putting the thread to sleep.
Sleep(100);
if not Terminated then
Synchronize(UpdateDisplay)
else
Exit;
end;
end;

end.

End Listing One

Delphi Informant June 1996 14


Informant Spotlight
InterBase / Paradox

By Kevin Bluck

InterBase vs. Paradox


Which Is Best for Your Application?

f you’re like me, when you first bought Delphi you didn’t pay much
I attention to the Local InterBase Server bundled in the package. Most
likely, even if you were learning to build database applications, you
amused yourself for weeks using nothing but Paradox tables. In fact, like
me, you may have came from a Paradox background.
The products are so strongly associated that ly impossible. Worst of all, some operations are
many people have had difficulty making the slower with InterBase than with Paradox. It
distinction. And rumors that Delphi would quickly becomes apparent that InterBase isn’t
replace Paradox compounded the problem. automatically better than Paradox.
Although it should be clear by now that this
isn’t the case, most developers still use Paradox The idea that InterBase isn’t better than
tables for their Delphi database development. Paradox is absolutely true. At least, it’s not
In short, I doubt many bought Delphi to get always better. The two products are significant-
their hands on the Local InterBase Server. ly different, and are intended to serve in differ-
ent situations. The only thing they really have
Piqued Curiosity in common is that they both store data in
But, programmers are a naturally curious lot, tables. They diverge rapidly from that point.
and many of them eventually began poking
at Local InterBase. At first glance, it doesn’t Each is a system with strengths and weak-
seem that different from Paradox. You access nesses. The trick is deciding which is appro-
it using an alias, just as Paradox, and priate for a particular application. And once
although they have different names, the field made, that decision fundamentally affects the
types are similar. You can even create subsequent development effort.
InterBase tables using the Database Desktop.
You use the same TTable and TQuery com- Paradox Is File Based
ponents that are used for Paradox tables. In Paradox is a file-based database system. The
short, the Borland Database Engine (BDE) data files contain data records that have a defi-
interface creates a convincing illusion that nite order. In other words, record number 106
InterBase tables behave like Paradox tables. will always be the same record until it’s physi-
cally moved within the file, perhaps as a result
Except InterBase is supposed to be better some- of a sorting operation. Even more importantly,
how. It’s an industrial-strength RDBMS, so it it will always follow record 105 and precede
must be bigger and faster that Paradox, right? record 107, until that order is explicitly
changed. This allows the records to be easily
For many developers, though, disillusionment navigated by a cursor, since it’s possible to iden-
soon sets in. After creating InterBase tables with tify a record by its position within a table with-
the Database Desktop, they discover they can’t out having to reference the data it contains.
casually change field definitions by simply
restructuring as they can with Paradox tables. This explicit physical ordering of records has
All searches and indexes are case-sensitive, some advantages. Moving back and forth
unlike Paradox. Defining primary and foreign through the data file is a simple matter, and
keys seems easy, but changing them seems near- the records are easily refreshed when the cur-

Delphi Informant June 1996 15


Informant Spotlight
sor arrives at them. The concept of browsing is convenient for a fundamental cornerstone of the relational model, and its
users and developers. It allows records to be handled one at a absence causes significant problems.
time, in a predictable order. This navigational behavior is one
of the major Paradox concepts that is difficult to transplant You will quickly discover how this rather incomprehensi-
into the InterBase world, and many Paradox developers have ble omission by the ANSI committee will cause you prob-
a difficult time making the transition. lems while using Delphi and the BDE to develop SQL
applications — unless you exercise the discipline to ensure
InterBase Is Set Based that your queries always return correct R-tables. (We’ll
InterBase is a true, set-based relational database system. comprehensively cover the full implications of this in a
Tables aren’t stored in individual files. More importantly, the future article.)
records are not ordered. Mathematically speaking, sets are
unordered. Order is “discovered” only when the set is physi- Physical Design and Its Impact on Speed
cally represented, such as when querying a database. You can’t Paradox is a client-based system where the data is completely
count on the same record being record number 105 twice in managed by the individual clients. Whenever data must be
a row, unless you explicitly impose a certain ordering on the read or manipulated in any way, it must be transported to the
query. Since you can’t positively identify a record by its posi- Paradox application. Each application handles all processing
tion within the table, you must refer to values within the itself. If multiple users are accessing data simultaneously on a
record. Therefore, to positively identify a record, at least one network, each user’s application transports the data it requires
field or combination of fields must contain unique values for back to the user’s machine.
each record. This is what’s known as a primary key.
Each instance of Paradox has no regard for the others. If an
It’s possible for more than one field or combination to pro- instance of Paradox needs to guarantee the stability of data
vide unique values, in which case they form a pool of candi- for any reason, it must forcibly prohibit other instances from
date keys. Since it’s important for a database management sys- changing the data through a locking scheme.
tem to identify individual records conclusively, the existence
of a primary key is crucial. A table that has a unique primary When a Paradox application needs to search a table, the
key is called an R-table, and all data sets must be R-tables for necessary raw data and indexes must be loaded into the
the relational model to work. client machine’s memory, the physical activity of the search
conducted, results produced, and the now-unneeded raw
The advantage of this set-based conceptualization of data is data discarded. This activity is repeated for every operation
that sets and the operations that can be performed on them on every client. The file server holding the data files does
have the property of closure. This means that when you per- absolutely nothing more than send the requested raw data
form a set operation on a set, it always produces another set, over the network to the client machines, making absolutely
which can then have another operation performed on it, no attempt to process the data. In essence, it acts as a
producing another set, ad infinitum. This is a powerful logi- remote hard disk.
cal concept, and if it’s properly implemented, will remove
the physical characteristics of storage from consideration in What this usually means is that Paradox is speedy on a local
the application’s design. This is the foundation of the rela- hard drive, but slows down dramatically over a network.
tional model. Network bandwidth can quickly become clogged with large
volumes of unprocessed data being shipped repeatedly to the
Cursor-based systems such as Paradox allow you to work only client machines. Even in a fairly small network environment,
on one record at a time, repeating an operation when you performance degrades rapidly as new users arrive.
want to process groups of records. Set-based systems allow you
to manipulate a set of data as if it were a single entity, all com- InterBase is a server-based system. Instead of different
ponents of which will share the same fate. This has the poten- processes physically manipulating the stored data, only one
tial of increasing the simplicity of application design and vast- central process running on the server machine has direct
ly improving data integrity. access to the data. All the client applications make polite
requests to the server process, which does the actual process-
Strangely enough, the ANSI SQL specification, which pur- ing entirely on the server machine while the client waits.
ports to be a relational standard, doesn’t require a primary
key to be defined for each table. This means records within When the server finishes, it passes only the result back to
the data sets that are produced by SQL queries don’t have to the client, which then goes on about its business. The
be uniquely identified by value. most direct impact of this scheme is that the network
doesn’t need to be clogged with large volumes of redun-
Even worse, the results of SQL queries, even if they come dant raw data being sent to the clients. Also, the often
from proper R-tables, don’t have to be R-tables themselves. complex data processing tasks can be delegated from usu-
Because identification by value is the only reliable method of ally less powerful client machines to the usually more
identifying individual records within a set, the primary key is powerful server machine.

Delphi Informant June 1996 16


Informant Spotlight
Notice that everything about InterBase’s design implies a cisive traveler can lock down a seat for a long time, causing
multi-user environment. InterBase was designed from the others to believe the seat is taken, only to decide not to take
ground up as a multi-user system. Paradox, on the other the seat after all.
hand, was designed primarily for a single user, with support
for multi-user capabilities added on. InterBase, on the other hand, handles all data manipulation
by itself using an optimistic concurrency scheme. It’s therefore
InterBase Isn’t for Browsing in a better position to manage contention among users with-
As mentioned earlier, despite the fact that InterBase is a true out resorting to Draconian locking tactics. The fact that one
RDBMS, it performs some operations more slowly than does user may be in the process of changing a record will not pre-
Paradox. For the most part, these are browsing operations — vent other users from also attempting to change it. Whenever
natural for Paradox — that the BDE is attempting to emu- a user begins to change a record, InterBase saves a copy of the
late with InterBase. The problem is most obvious if you have original record. The user goes about his or her business, but
a rather large table, perhaps 100,000 records, attached to a other users are not prohibited from accessing the same record
TTable. If you call the TTable’s Last method, attempting to in any way.
move the record pointer to the last record, the performance
of the two systems will vary wildly. When the editing user posts the changes, the original copy is
compared to the current record. If the versions are different
The Paradox table’s cursor will move almost instantly (most likely because another user beat them to the punch)
because it knows the physical location of the last record in the user’s changes are rejected.
the data file and can simply increment the file pointer to
that location and retrieve the data. The InterBase table, What this means is that individual users can’t lock others out
however, is going to present some problems. First, because of records. In the above travel reservation scenario, the first
the data is unordered, there is some question about what traveler to commit a reservation gets the seat, even if several
constitutes “lastness.” It must impose an order before it were considering it simultaneously. The downside, of course,
can decide which record is “last.” By default, the last is that the changes are not rejected until the record is posted,
record will be considered to be the record whose primary after the work of editing has been done. This can be mitigat-
key has the greatest value. ed, however, by refreshing the changed fields and resubmit-
ting. TTable and TQuery do this transparently, for example,
It’s now necessary to determine the value of the maximum using the UpdateMode property. The post can be resubmitted
primary key. Once that value has been determined, the if all fields match the originals, only the changed fields
record matching that value can be retrieved. However, if the match, or only the primary key matches, whichever is appro-
last few records must be retrieved, as would be the case in a priate to ensure integrity.
grid display, the process has to be repeated.
The two approaches reflect a basic difference in philosophy.
Now, InterBase must find the greatest primary key value that is The Paradox pessimistic model assumes that collisions will
less than the maximum primary key value. For the third, it be common, and gives strong control of the record to who-
needs the next-to-next-greatest primary key value, and so forth ever seizes it first. The optimistic InterBase model assumes
until the required number of records is found. An operation that collisions will be rare, and maximizes the ability of users
that is a piece of cake for Paradox is a major pain for InterBase. to share data without interfering with one another, while
In general, attempting to navigate backwards through SQL still maintaining integrity.
tables is inefficient. The BDE buffers groups of records to min-
imize this problem, but if you go backwards far enough, you’ll An important benefit of the InterBase model is that one
have to pause for significant amounts of time in large tables. user who wants to see a stable data set, perhaps to generate
a series of reports that need to reflect the same snapshot of
Locking data, will not interfere with other users who want to change
Whenever two users attempt to change the same piece of data, the same data. In Paradox, the report generator would have
problems can arise that threaten the integrity of the database. to place a write lock on the table to guarantee the data will
Paradox and InterBase address this problem in different ways. not change, and nobody can update data in that table as
long as the lock exists.
Because Paradox has no direct knowledge of what other
Paradox processes are doing, it uses a pessimistic locking In InterBase, old versions of records are retained as long as a
scheme. As soon as a user attempts to change a record, the user is interested in them — so, other users do not have to be
record is locked. No other user can change the record until prevented from updating the records. This means that in
the first user finishes editing, or cancels the changes. This is InterBase, readers never prevent writers from succeeding, nor do
good, in that a user who successfully obtains a lock can defi- writers compromise the results of readers. InterBase is the only
nitely complete the editing operation. It’s also bad, because a SQL database that does this so transparently. When InterBase
user can monopolize a record indefinitely. This would be a proponents are asked what the advantages of InterBase are, this
problem, for example, in a travel reservation system. An inde- record versioning is usually the first thing they mention.

Delphi Informant June 1996 17


Informant Spotlight
Transaction Processing The main advantages are that even more complex process-
As you’ll recall, a basic premise of the set-based model is that sets ing can be delegated to the server, and any number of dif-
of data can be treated as individual entities, regardless of the set’s ferent client applications can call the same procedures. If
specific contents. Transaction processing is an extension of this the procedure is modified on the server, none of the appli-
idea. A transaction is a group of operations that must either all cations has to be rewritten as long as the procedure’s inter-
succeed or all fail. It’s never acceptable only for some to succeed. face remains the same.

For example, your automated teller machine (ATM) performs A trigger is short for triggered procedure. It’s a stored proce-
database transactions. Whenever you withdraw cash, two dure that is not explicitly called by an application, but is exe-
operations must be performed for the bank to properly cuted in response to a data action, such as inserting a new
account for its assets: The balance of your account must be record. Triggers allow you to perform extremely complex data
reduced, and the balance of cash on hand must also be validation, and are guaranteed to execute within the same
reduced by the same amount. Obviously, the preferred situa- transaction that performed the triggering operation. If any
tion is for both operations to succeed, but if the power goes operation fails, all changes made by triggers associated with
off in the middle of the operation, it’s absolutely not accept- that operation are also rolled back.
able for one account to be updated and not the other —
both operations must fail to maintain the proper accounting. InterBase supports stored procedures that return result
sets, which can be treated exactly as read-only tables, as
Transaction processing allows this to happen. The operations well as triggers that simply perform data transformations
in a transaction are not permanent until the whole transac- and don’t return any results. It supports essentially unlim-
tion is committed. Until that time, it may be rolled back to ited numbers of triggers for each table, which can occur
the starting point. A rollback can be explicitly triggered using before or after inserts, updates, and deletes. If more than
the Rollback method, or it can occur automatically when a one trigger is associated with an operation, their order of
system failure occurs. execution can be specified. Triggers can make changes that
execute other triggers, in a chain-reaction fashion, but all
InterBase fully supports transactions. In fact, all operations such cascading actions are still contained within a single
occur within the context of a transaction. In the absence of transaction.
explicit programmer control, the BDE automatically “wraps”
every operation in its own transaction. For example, every Paradox does not support either of these concepts. All data
time you post a record, a transaction is started and committed processing must be done at the client. Each application must
immediately after the post. Using the TDatabase component, contain the same code to maintain the data, and each appli-
you can explicitly control a single transaction and have it cation must be modified if the method of handling data
encompass as many operations as you like. must be changed.

However, the BDE does not fully support InterBase’s transac- There is no guarantee that an operation will be completed
tion capabilities. TDatabase methods can only be used against once it’s started. For example, cascading the delete of a
a single InterBase database, and only one transaction may master record to its detail records can fail in midstream,
exist at a time for each BDE alias. InterBase itself supports leaving details undeleted. If this cascade were implemented
multiple simultaneous transactions per connection, and a as a trigger in InterBase, either all or none of the records
transaction can also encompass more than one database, but would be deleted. Furthermore, the code to cascade the
the BDE doesn’t surface these abilities. You’ll have to make delete must be written into each different application that
calls to the InterBase API to use these features. uses the Paradox data. Using InterBase, it only needs to be
written once in a trigger. The application simply deletes
Paradox doesn’t support transactions. Whenever a record is post- the master record and InterBase takes care of deleting the
ed, the changes are permanently written to the table. It requires detail records.
another edit to manually change it back if a rollback is desired.
In addition, the system will not guarantee that a group of opera- Making the Choice
tions will all either succeed or all fail. It’s possible to simulate Choosing between Paradox and InterBase has important
some of this capability through some tricky programming and implications for your project. Accordingly, it’s essential to
temporary tables, but eventually the records must be modified know what is important for your situation. Figure 1 shows
one at a time in a batch, which leaves a window for failure. And some general principles that can help you make a decision.
there’s no way you can program a Paradox application to recover
from a system failure such as a power outage or disk crash. These are guidelines, not rules. Most of them assume a net-
work is involved. If you are contemplating a single-user system,
Triggers and Procedures Paradox is usually the best choice. The Local InterBase Server
A stored procedure is a piece of code that is stored in the can be deployed as a single-user system, but without concur-
database along with the data. It allows the server to per- rency issues, many InterBase features don’t apply. If the ability
form complex manipulation of data entirely on the server. to browse data is important, Paradox is also a good choice.

Delphi Informant June 1996 18


Informant Spotlight
Paradox Is Better When ... InterBase Is Better When ...
Primarily used by fewer than Primarily used by more than
10 concurrent users. 10 concurrent users.
Data and data structures must Data should be centrally
easily be modified by end-users. maintained and protected.
Client machines are compara- Server is much more powerful
ble in power to the server. than the clients.
Plenty of network bandwidth. Network is loaded.
Speed and convenience are Data integrity is crucial.
more important than integrity.
Little network and SQL Skilled network and database
expertise is available. administrators are available.
Only one application will Several applications may
routinely access the data. access the data.
Applications will be responsi- Database will enforce data
ble for maintaining data integrity independently of
integrity. applications.
Small to moderate amounts Moderate to large amounts of
of data (< 100MB). data (>100MB).

Figure 1: Comparing Paradox and InterBase.

Conclusion
It’s important to remember that Paradox and InterBase are
substantially different systems, even though the BDE
attempts to make them look similar. It’s a seductive, but dan-
gerous idea that converting an application from one to the
other involves nothing more than changing an alias. They
require significantly different design concepts, and a design
that is efficient with one will likely not be with the other.
Selecting which system to use is a crucial decision that must
be made at the start of a project. It will profoundly impact
your subsequent development effort. ∆

Kevin J. Bluck is an independent consultant based in Sacramento, CA. His specialty


is database development with Borland products such as Delphi, Paradox, Borland
C++, and InterBase. Kevin can be reached on CompuServe at 103447,3510, or
on the Internet at [email protected].

Delphi Informant June 1996 19


DBNavigator
Delphi 2 / Object Pascal

By Cary Jensen, Ph.D.

Strainless Filtering
Delphi 2’s New and Powerful Filtering Capabilities

common need in database applications is the ability to display or


A manipulate a subset of records. As described previously in this column,
you can do this in Delphi 1 using ranges, linked DataSets, and SQL
queries. Delphi 2 now offers you one more choice: filters. This month’s
DBNavigator takes a look at this new feature.
The Delphi 2 filter capability is available strates the use of the new OnFilterRecord
through the three descendants of TDBDataSet: event handler. Finally, the use of filters to
TTable, TQuery, and TStoredProc. And there navigate records is demonstrated.
are four properties involved: Filter, Filtered,
FilterOptions, and OnFilterRecord (an event Filtering with Properties
property). Using these properties, you can There are two properties that — used together
instruct a DataSet to display fewer than all of — produce a filtered DataSet. The properties
its records. What makes filtering special, as are Filter and Filtered. Filtered is a Boolean
opposed to using ranges and linked DataSets property that you use to turn the filter on and
(the only other techniques that work with all off. If you want to filter a record, set Filtered
TDataSet descendants), is that filters do not to True; otherwise set Filtered to False.
require an index. As a result, filters are more
widely applicable than the other record-limit- When Filtered is set to True, the DataSet uses
ing techniques. the value of the Filter property to identify
which records to display. You assign to this
Although the primary application of filters is to property a string that contains at least one
limit the records displayed in a DataSet, there is comparison operation involving at least one
an additional capability offered by filters that is field in the DataSet. You can use any com-
unmatched by ranges, linked DataSets, and parison operators, including =, >, <, >=, <=,
SQL queries. Specifically, filters permit you to and <>. As long as the field name doesn’t
display an entire database and still navigate include spaces, you can include the field
only the records that match the filter. For name directly in the comparison without
example, you can set a filter to match records delimiters. For example, if your DataSet
based on the state of California, but still display includes a field named Country, you can set
all records in the DataSet. Then, use the four the Filter property to the following value to
new DataSet methods that let you move to the filter only for customers in the US:
first, last, next, and previous record where the
Customer resides in California, skipping over Country = 'US'
any records in between. The non-California
records will still appear among the California If the field named includes a space (field
records, but Next (for instance) will move to names in Paradox tables can include spaces),
the next California record. you must enclose the named field in brack-
ets. For example, if your DataSet is a Paradox
This article will demonstrate three applica- table, and you want to display only those
tions of filters. The first application makes records where the customer’s last name is
use of properties alone. The second demon- Jones, and the field name in the table is last

Delphi Informant June 1996 20


DBNavigator
name, you would assign the following value to the Filtered
property:

[last name] = 'Jones'

These examples have demonstrated only simple expressions.


However, complex expressions can also be used. Specifically,
you can combine two or more comparisons using the and, or,
and not logical operators. In addition, more than one field can
be involved in the comparison. For example, you can use the
following Filter property value to limit records to those where
the City field is San Francisco, and the last name is Martinez:

City = 'San Francisco' and [last name] = 'Martinez'

However, a value assigned to the Filter property does not


automatically mean that records will be filtered. Only when
the Filtered property is set to True does the Filter property
produce a filtered DataSet. Furthermore, if no value appears
in the Filter property, setting Filtered to True has no effect.

The use of the Filter and Filtered properties is demonstrated in


the project FILTER.DPR, whose main form is shown in
Figure 1. This project contains an Edit component, in which
the user can type a filter string. The OnExit event handler for
this property assigns the Text property of this Edit component
to the Filter property of the DataSet. Figure 2 shows this form
after a filter string has been entered. Notice that only those
records that match the Filter are displayed in the form’s
DBGrid. Figure 1 (Top): The main form for the FILTER.DPR project. This
project permits the user to enter a filter statement at run time.
The form in Figures 1 and 2 also contains two CheckBox Figure 2 (Bottom): The Customer table is being filtered. Only
those companies that are located in the US, and whose last invoice
components. These components control a third filter-related date is later than or equal to January 1st, 1995, are displayed.
property of DataSets, the FilterOptions property. This proper-
ty is a set property, with two values: foCaseInsensitive and record should be included in the DataSet. You can perform
foNoPartialMatch. When foCaseInsensitive is included in the almost any test you can imagine with this event handler. If,
set, the filter is not case-sensitive. When foNoPartialMatch is based on this test, you wish to exclude the current record
included in the set, partial matches are also included in the from the DataSet, you set the value of the Accept parameter
filtered DataSet. Note, however, in my admittedly limited to False. Note that this parameter is True by default.
testing, I could find no affect of the foNoPartialMatch value
in this set. In all my tests, all comparisons were complete. The use of OnFilterRecord is demonstrated in the project
The following code, attached to the OnChange event handler ONFILT.DPR (shown in Figure 3). When the user marks
for the Case Sensitive check box, demonstrates the run-time the check box labeled Show orders for the following year
manipulation of the FilterOptions property: only, the Filtered property of the DataSet (Table1 in
DataModule2) is set to True. The year to be filtered is con-
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
trolled by an UpDown control. In this case, the filtering is
if not CheckBox1.Checked then being performed in the OnFilterRecord event handler for
Table1.FilterOptions := the DataSet:
Table1.FilterOptions + [foCaseInsensitive]
else
procedure TDataModule2.Table2FilterRecord(
Table1.FilterOptions :=
DataSet: TDataSet; var Accept: Boolean);
Table1.FilterOptions - [foCaseInsensitive];
begin
end;
if (Table2.FieldByName('SaleDate').Value >=
StrToDate('1/1/' +
Using the OnFilterRecord Event Handler IntToStr(Form1.UpDown1.Position))) and
( Table2.FieldByName('SaleDate').Value <=
There is another — somewhat more flexible — way to define
StrToDate('12/31/' +
a filter. Instead of using the Filter property, you can attach IntToStr(Form1.UpDown1.Position))) then
code to the OnFilterRecord event handler for the DataSet. Accept := True
else
This event handler is passed by reference a Boolean property,
Accept := False;
named Accept, that you use to indicate whether the current end;

Delphi Informant June 1996 21


DBNavigator
This code uses The first four lines of this event handler update the cap-
the Position tions of the four buttons that the user navigates with using
property of the the filter. The last line sets the filter. Notice the use of the
UpDown con- #39 character in this assignment statement. This is neces-
trol (on Form1) sary to enclose the State string in the single quotation
to accept only marks that identify string literals in Object Pascal.
those records
within the The remainder of this project is very simple. The top four
specified year. buttons shown in Figure 4 call the table methods First,
If the current Last, Next, and Prior. The second set of buttons calls the
record passes new filtered navigation methods FindFirst, FindLast,
this test, the FindNext, and FindPrior.
value of Accept
is set to False. Figure 3: The ONFILT.DPR project demonstrates
the use of OnFilterRecord.

It’s important to note that if you set the Filter property to a


filter string, and also assign code to the OnFilterRecord prop-
erty, both will be applied when Filtered is True. That is, only
those records that match the filter string and those that are
accepted by the event handler will appear in the DataSet.

Navigating Using a Filter


Whether you have set Filtered to True or not, you can still use
a filter for the purpose of navigating selected records. For
example, although you may want to view all records in a
Figure 4: The FILTNAV.DPR project demonstrates how you can
database, you may want to quickly move between records navigate a DataSet using a filter, even when the filter is not
that meet specific criteria. For example, you may want to be being actively applied.
able to quickly navigate between those records where an
unpaid account balance exists. Conclusion
The new filter feature of Delphi 2 provides you with a new
In Delphi 2 the DataSet objects surface four methods for nav- set of tools for presenting users with subsets of records, as
igating using a filter. These methods are FindFirst, FindLast, well as permitting them to navigate selected records, even
FindNext, and FindPrior. When you execute one of these while viewing the entire DataSet.
methods, the DataSet will locate the requested record based
on the current Filter property or OnFilterRecord event handler. These valuable new filtering capabilities are welcome.
This navigation, however, does not require that the Filtered However it’s important to emphasize that these filtering oper-
property be set to True. ations do not make use of indexes. Therefore you should use
these filtering techniques only on a subset (i.e. the result of a
The use of these special properties is demonstrated in the query, view, or range) of a large database. ∆
project FILTNAV.DPR, shown in Figure 4. As you navi-
gate the project, it is constantly setting the filter to the The demonstration projects referenced in this article are
state for the current record. available on the Delphi Informant Works CD located in
INFORM\JUNE \96 \DI9606CJ.
This is done by attaching the following code to the
OnDataChange event handler for the DataSource that
points to the CUSTOMER.DB table:
Cary Jensen is President of Jensen Data Systems, Inc., a Houston-based database
procedure TForm1.DataSource1DataChange( Sender: TObject; development company. He is author of more than a dozen books, including the
Field: TField); upcoming Delphi In-Depth [Osborne/MacGraw-Hill, 1996]. He is also Contributing
begin Editor of Paradox Informant and Delphi Informant, and is this year’s Chairperson of
Button5.Caption := the Paradox Advisory Board for the upcoming Borland Developers Conference. You can
'First ' + Table1.FieldByName('State').AsString; reach Jensen Data Systems at (713) 359-3311, or via CompuServe at 76307,1533.
Button6.Caption :=
'Last ' + Table1.FieldByName('State').AsString;
Button7.Caption :=
'Next ' + Table1.FieldByName('State').AsString;
Button8.Caption :=
'Prior ' + Table1.FieldByName('State').AsString;
Table1.Filter := '[State] = ' + #39 +
Table1.FieldByName('State').AsString +
#39;
end;

Delphi Informant June 1996 22


From the Palette
Delphi 1/2 / Object Pascal

By Ray Konopka

Components &
Sub-Components
Encapsulating Multiple Controls

he ability to build custom components is one of Delphi’s most exciting fea-


T tures. It is also one of its more popular features judging by the number of
components that are available on the World Wide Web, the Borland Delphi
CompuServe forum (GO DELPHI), and of course, the Delphi Informant
CompuServe forum (GO ICGFORUM). Aside from being Delphi components,
most of the components available from these sources have one thing in com-
mon: they encapsulate a single control. While this is certainly the norm, it is
by no means a restriction — the VCL is sufficiently rich to support encapsulat-
ing multiple controls. To illustrate the issues involved in this process, this article
describes how to create a data-aware address component.
The RzAddress component is a single com- why anyone would want to build a compo-
ponent that consists of separate sub-com- nent like this. Aside from the ever popular
ponents representing the different fields in answer, “Because you can,” there are two
a typical US mailing address. The compo- principle reasons for encapsulating multi-
nent comprises five DBEdit components, ple controls in a single component: the
one DBComboBox, and six Label compo- first is to promote reusability; the second is
nents. The arrangement of these compo- to promote consistency.
nents is illustrated in Figure 1, which
shows the RzAddress component being For data entry applications, entering an
used in an application. address is a common task. Unfortunately,
the forms used to enter address informa-
Before getting into the details of the tion generally capture additional informa-
TRzAddress class, you may be wondering tion not relevant to the address. This extra
information prevents the form from being
reused in other applications. However, a
component like RzAddress forces you to
think about reusing a portion of the form,
rather than the entire form.
The second benefit, consistency, is more
of an issue with end users. Let’s continue
with the address example. Without a
reusable component, every application
that provides fields for entering an address
will vary — at least slightly — from the
others. The length of the last name field
may be shorter in one application than
another. The state field may be represent-
ed by an edit field in one program, and by
Figure 1: Editing an address the easy way! a combo box in another. By creating a sin-

Delphi Informant June 1996 23


From the Palette
gle component to represent the group of controls, consis- Accessing Sub-Components through Properties
tency can be maintained between applications. There are three basic ways of accessing a sub-component
through the main component. All three involve proper-
The TRzAddress Class ties, and each provides a different level of control. The
The RzAddress component is implemented in the RzAddr first way is to provide the main component with a
unit (see Listing Two beginning on page 25). The generic property that gets mapped to each sub-compo-
TRzAddress class descends from TWinControl because it nent. The DataSource property is an example of this
essentially needs to be able to contain other controls with- type of access. Notice that the TRzAddress class does not
in itself, and a window handle is needed to support this maintain an internal field for holding the DataSource
feature. However, TWinControl is not your only option — value. Instead, the sub-components themselves are used
if you would like a border around the controls, the TPanel to manage the property. As you can see from the
or TGroupBox classes serve equally well as ancestors. SetDataSource method, when the TRzAddress.DataSource
is changed, the DataSource properties for all sub-compo-
The private section of this class contains an object field for
nents are updated with the new value.
each sub-component. The sub-components are created in
the Create constructor. One-by-one, each sub-component The second way of providing access to sub-components is
is dynamically created and positioned within the to provide individual properties that correspond to proper-
RzAddress component. Note that the coordinates passed ties in each sub-component. FirstNameField, LastNameField,
to the SetBounds method are relative to the main compo- and CityField are examples of this type of access. Each one
nent’s client area. of these properties correspond to the DataField property of
one of the sub-components. Unlike the DataSource proper-
A few supporting methods are provided to make it easier
ty, these field names cannot be shared among the sub-com-
to construct the sub-components. These include
ponents. Figure 2 shows the FirstNameField property being
CreateLabel, CreateCombo, and CreateEdit. Each of these
edited to link the corresponding sub-component to the
methods creates an object of the appropriate type, and
appropriate table column. (As an aside, the Field properties
then sets the Parent and Visible properties. The
also provide a good example of using indexed properties. All
CreateLabel method then sets the caption of the label,
six properties are supported by the same access methods:
while the CreateCombo method sets the Sorted property to
GetField and SetField.)
True. The CreateEdit method finishes by assigning the
OnChange event of the sub-component to point to the
TRzAddress.DoChange method. We’ll come back to the
importance of this later in the article.
After all the sub-components are created, the internal
FStateList is populated. FStateList is a string list object that is
used to populate the State combo box. The combo box itself
cannot be used to store the strings because it is dynamically
created, and therefore does not provide persistent storage. A
StateList property of TRzAddress could be displayed, but
there is no real need to give the user direct access to this list.
Because the string list is created within the component, a
destructor must be provided so the memory used by the
string list can be freed. Speaking of destructors: who’s
responsible for destroying the sub-components? Well, actu-
ally we are. What I mean is, the component writer is Figure 2: Editing the FirstNameField property.
responsible for making sure the sub-components are
released when the main component is destroyed. However, The third way of providing access to sub-components is
you may have already noticed there is no code that specifi- to expose a reference to the sub-component. Because this
cally frees the sub-components. allows complete access to the sub-component, it is gener-
ally not wise to do. As an example, the EdtFirstName
When each sub-component is created, Self is passed to the property provides a reference to the FEdtFirstName edit
Create constructor. This causes the sub-component to be field. With this reference, the end user has access to the
placed into the TRzAddress.Components list. The Components properties of FEdtFirstName, and therefore, could affect
list is defined in the TComponent class, and when the main the way the entire RzAddress component behaves. For
component is destroyed, the inherited destructor from example, the edit control could be moved or resized.
TComponent frees all the components in the Components list. More dramatic is the problem of setting the DataSource
So, as long as a valid owner is passed to the constructor for property of the FEdtFirstName field directly. A sub-com-
each sub-component, we do not have to worry about clean- ponent reference can be used to bypass all other types of
ing up the sub-components. access.

Delphi Informant June 1996 24


From the Palette
Exposing Events that Occur in Sub-Components uses
Earlier, I mentioned that each edit field created gets its Classes, Controls, StdCtrls, DB, DBCtrls,
OnChange event assigned to the DoChange method. Like Graphics, ExtCtrls, RzCommon;
properties, events can be exposed individually, or shared; but
type
because of the event architecture, a hybrid between the two TEditField = ( efFirstName, efLastName,
can be achieved. Any time a change event occurs in one of efStreet, efCity, efZip);
the edit fields, the DoChange method is called. Since the TEditChangeEvent =
procedure (Field : TEditField;
Sender parameter identifies which component generated the
Text : string) of object;
message, this information can be passed on to the end user’s TRzAddress = class(TWinControl)
event handler for the main component’s OnChange event. private
FEdtFirstName : TDBEdit;
The OnChange event for the RzAddress component receives FEdtLastName : TDBEdit;
two parameters. The first parameter is an enumerated value FEdtStreet : TDBEdit;
FEdtCity : TDBEdit;
indicating the sub-component that generated the event. For FCbxState : TDBComboBox;
example, if the change event occurred in the FEdtCity edit FEdtZip : TDBEdit;
field, the first parameter would have a value of efCity. The { Internal List of State Abbreviations }
second parameter contains the current contents of the edit FStateList : TStringList;
{ Common Change Event for all Edit Fields }
field. Using these two parameters, a user can create a single FOnChange : TEditChangeEvent;
event handler to manage all of the OnChange events that
occur within each of the sub-components. function GetCharCase : TEditCharCase;
procedure SetCharCase(Value : TEditCharCase);
Conclusion function GetDataSource : TDataSource;
procedure SetDataSource(Value : TDataSource);
You may have noticed that encapsulating multiple controls function GetField(Index : Integer) : string;
within a single component is very similar to dynamically procedure SetField(Index : Integer; Value : string);
creating components on a form at run time. This is not function CreateEdit : TDBEdit;
function CreateLabel(S : string) : TLabel;
surprising since forms are part of the Visual Component
function CreateCombo : TDBComboBox;
Library. In essence, a form just happens to be a very intelli- procedure CreateStateList;
gent multi-control component. Once the sub-components procedure DoChange(Sender : TObject);
are created, building a multi-control component simply
protected
requires defining the interactions between the controls and
procedure Change(Field : TEditField;
providing sub-component access. Text : string); dynamic;
procedure CreateWnd; override;
Portions of this article are adapted from material in Ray
Konopka’s, Developing Custom Delphi Components [The public
Coriolis Group, 1996]. ∆ constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
The demonstration files referenced in this article are avail-
property EdtFirstName : TDBEdit
able on the Delphi Informant Works CD located in read FEdtFirstName;
INFORM \JUNE \96 \DI9606RK.
published
property CharCase : TEditCharCase
read GetCharCase write SetCharCase;

Ray Konopka is the author of Developing Custom Delphi Components, published property DataSource : TDataSource
by The Coriolis Group. Ray is also the founder of Raize Software Solutions, Inc., read GetDataSource write SetDataSource;
supplier of Delphi consulting services. Ray can be reached at [email protected]
or [email protected]. property FirstNameField : string
index 1 read GetField write SetField;

property LastNameField : string


index 2 read GetField write SetField;
Begin Listing Two — The RzAddr Unit
{ RzAddr Unit. This unit implements the RzAddress property StreetField : string
component which is comprised of the following edit index 3 read GetField write SetField;
fields: FirstName, LastName, Street, City, and Zip. The
State field is actually a combo box which is populated property CityField : string
with the 50 states including the District of Columbia. index 4 read GetField write SetField;
The edit fields are data-aware, and thus this component
can be hooked up to a DataSource. property StateField : string
Developing Custom Delphi Components — Ray Konopka } index 5 read GetField write SetField;

unit RzAddr; property ZipField : string


index 6 read GetField write SetField;
interface

Delphi Informant June 1996 25


From the Palette

property OnChange : TEditChangeEvent function TRzAddress.CreateLabel(S : string) : TLabel;


read FOnChange write FOnChange; begin
Result := TLabel.Create(Self);
property Font; Result.Parent := Self;
property ParentFont; Result.Visible := True;
end; Result.AutoSize := True;
Result.Caption := S;
procedure Register; end;

implementation function TRzAddress.CreateEdit : TDBEdit;


begin
uses Result := TDBEdit.Create(Self);
DsgnIntf; Result.Parent := Self;
Result.Visible := True;
{ TRzAddress Methods } { Assign OnChange event of each Edit field
constructor TRzAddress.Create(AOwner : TComponent); to point to TRzAddress.DoChange method. }
var Result.OnChange := DoChange;
TempLbl : TLabel; end;
begin
inherited Create(AOwner); function TRzAddress.CreateCombo : TDBComboBox;
begin
{ All labels are created using the TempLbl component Result := TDBComboBox.Create(Self);
because we do not need to reference these controls Result.Parent := Self;
elsewhere. Clean up is handled when the TComponent Result.Visible := True;
ancestor class frees all components on the Components Result.Sorted := True;
list. } end;
TempLbl := CreateLabel('First Name');
with TempLbl do SetBounds(0, 8, Width, Height); procedure TRzAddress.CreateWnd;
FEdtFirstName := CreateEdit; begin
FEdtFirstName.SetBounds(67, 4, 97, 20); inherited CreateWnd;

TempLbl := CreateLabel('Last Name'); { When CreateWnd is called, the Items list of FCbxState
with TempLbl do SetBounds(182, 8, Width, Height); is cleared. Therefore, the contents of the FStateList
TempLbl.Alignment := taRightJustify; are copied back into FCbxState. }
FCbxState.Items.Assign(FStateList);
FEdtLastName := CreateEdit; end;
FEdtLastName.SetBounds(240, 4, 137, 20);
procedure TRzAddress.CreateStateList;
TempLbl := CreateLabel('Street'); begin
with TempLbl do SetBounds(0, 36, Width, Height); FStateList := TStringList.Create;
FEdtStreet := CreateEdit; FStateList.Add('AK');
FEdtStreet.SetBounds(67, 32, 310, 20); FStateList.Add('AL');
FStateList.Add('AR');
TempLbl := CreateLabel('City'); FStateList.Add('AZ');
with TempLbl do SetBounds(0, 64, Width, Height); FStateList.Add('CA');
FEdtCity := CreateEdit; FStateList.Add('CO');
FEdtCity.SetBounds(67, 60, 121, 20); FStateList.Add('CT');
FStateList.Add('DC');
TempLbl := CreateLabel('State'); FStateList.Add('DE');
with TempLbl do SetBounds(200, 64, Width, Height); FStateList.Add('FL');
TempLbl.Alignment := taRightJustify; FStateList.Add('GA');
FCbxState := CreateCombo; FStateList.Add('HI');
FCbxState.SetBounds(240, 60, 50, 20); FStateList.Add('IA');
FStateList.Add('ID');
TempLbl := CreateLabel('Zip'); FStateList.Add('IL');
with TempLbl do SetBounds(300, 64, Width, Height); FStateList.Add('IN');
TempLbl.Alignment := taRightJustify; FStateList.Add('KS');
FEdtZip := CreateEdit; FStateList.Add('KY');
FEdtZip.SetBounds(326, 60, 51, 20); FStateList.Add('LA');
FStateList.Add('MA');
CreateStateList; FStateList.Add('MD');
FStateList.Add('ME');
Width := 382; FStateList.Add('MI');
Height := 86; FStateList.Add('MN');
end; { = TRzAddress.Create = } FStateList.Add('MO');
FStateList.Add('MS');
destructor TRzAddress.Destroy; FStateList.Add('MT');
begin FStateList.Add('NC');
FStateList.Free; FStateList.Add('ND');
inherited Destroy; FStateList.Add('NE');
end; FStateList.Add('NH');
FStateList.Add('NJ');

Delphi Informant June 1996 26


From the Palette

FStateList.Add('NM'); { Use FEdiFirstName to Get Current DataSource }


FStateList.Add('NV'); Result := FEdtFirstName.DataSource;
FStateList.Add('NY'); end;
FStateList.Add('OH');
FStateList.Add('OK'); procedure TRzAddress.SetDataSource(Value : TDataSource);
FStateList.Add('OR'); begin
FStateList.Add('PA'); if Value <> FEdtFirstName.DataSource then
FStateList.Add('RI'); begin
FStateList.Add('SC'); { Assign All Internal Controls to Same DataSource }
FStateList.Add('SD'); FEdtFirstName.DataSource := Value;
FStateList.Add('TN'); FEdtLastName.DataSource := Value;
FStateList.Add('TX'); FEdtStreet.DataSource := Value;
FStateList.Add('UT'); FEdtCity.DataSource := Value;
FStateList.Add('VA'); FCbxState.DataSource := Value;
FStateList.Add('VT'); FEdtZip.DataSource := Value;
FStateList.Add('WA'); end;
FStateList.Add('WI'); end;
FStateList.Add('WV');
FStateList.Add('WY'); function TRzAddress.GetField(Index : Integer) : string;
end; { = TRzAddress.CreateStateList = } begin
case Index of
procedure TRzAddress.Change(Field : TEditField; 1: Result := FEdtFirstName.DataField;
Text : string); 2: Result := FEdtLastName.DataField;
begin 3: Result := FEdtStreet.DataField;
if Assigned(FOnChange) then 4: Result := FEdtCity.DataField;
FOnChange(Field, Text); 5: Result := FCbxState.DataField;
end; 6: Result := FEdtZip.DataField;
end;
{ TRzAddress.DoChange. This method gets called if the end;
OnChange event occurs for any of the edit fields
contained in this component. The Change event dispatch procedure TRzAddress.SetField(Index : Integer;
method is called to surface those events to the user. } Value : string);
procedure TRzAddress.DoChange(Sender : TObject); begin
var case Index of
Field : TEditField; 1: FEdtFirstName.DataField := Value;
begin 2: FEdtLastName.DataField := Value;
3: FEdtStreet.DataField := Value;
if Sender = FEdtFirstName then 4: FEdtCity.DataField := Value;
Field := efFirstName 5: FCbxState.DataField := Value;
else if Sender = FEdtLastName then 6: FEdtZip.DataField := Value;
Field := efLastName end;
else if Sender = FEdtStreet then end;
Field := efStreet
else if Sender = FEdtCity then { Register Procedure }
Field := efCity procedure Register;
else begin
Field := efZip; RegisterComponents(RaizePage, [ TRzAddress ]);
Change(Field, TDBEdit(Sender).Text); { The following RegisterPropertyEditor calls instruct
end; the Object Inspector to hold off accepting the text
entered into the specified fields until the Enter key
function TRzAddress.GetCharCase : TEditCharCase; is pressed. }
begin RegisterPropertyEditor(TypeInfo(string), TRzAddress,
Result := FEdtFirstName.CharCase; 'FirstNameField', TStringProperty);
end; RegisterPropertyEditor(TypeInfo(string), TRzAddress,
'LastNameField', TStringProperty);
procedure TRzAddress.SetCharCase(Value : TEditCharCase); RegisterPropertyEditor(TypeInfo(string), TRzAddress,
begin 'StreetField', TStringProperty);
if Value <> FEdtFirstName.CharCase then RegisterPropertyEditor(TypeInfo(string), TRzAddress,
begin 'CityField', TStringProperty);
FEdtFirstName.CharCase := Value; RegisterPropertyEditor(TypeInfo(string), TRzAddress,
FEdtLastName.CharCase := Value; 'StateField', TStringProperty);
FEdtStreet.CharCase := Value; RegisterPropertyEditor(TypeInfo(string), TRzAddress,
FEdtCity.CharCase := Value; 'ZipField', TStringProperty);
FEdtZip.CharCase := Value; end;
end;
end.
end; End Listing Two

function TRzAddress.GetDataSource : TDataSource;


begin

Delphi Informant June 1996 27


OP Tech
Delphi / Object Pascal

By Keith Wood

Talking to Yourself
A Look at Recursion in Object Pascal

e’ve all seen those cereal boxes that have pictures of someone holding
W the same cereal box, which has another picture of someone, etc. This is
an example of recursion, and it’s a powerful tool for the programmer as well.

Recursion arises when something is defined mixed up during recursion since we are call-
in terms of itself. Pascal allows for recursive ing the same function? No — Pascal provides
functions and procedures that are well suited separate variables each time the function is
for particular programming problems. To called. They are only valid within that partic-
show how it all works, this article looks at ular instance of the function, and retain their
recursion in general, and presents a recursive values across any other recursive calls. Of
function, two recursive plotting procedures, course this does have the effect that the space
and a binary tree class. available for variables (the “stack”) is slowly
eaten away as we go deeper into the recursive
Recursion calls. If this continues indefinitely, the space
Normal processing in a program is iterative. is eventually exhausted and an error occurs.
This means that part of the algorithm may
be executed in a for, while, or repeat loop. Factorials
In recursive processing, the entire algorithm The factorial function in mathematics is a
is re-executed from the beginning on a small- classic recursion example. This function
er part of the problem. computes the product of all the integers
between one and a given positive value (the
Two things are necessary for using recursion in result gets very large, very quickly). It’s
programming. First, the problem must be denoted by the exclamation mark ( ! ) and
defined in terms of itself; second, it must have can be defined by the following:
a terminating condition. Since the portion of
the problem being solved at each level of recur- 1! = 1
sion is smaller than the previous step, eventual- n! = n * (n - 1)!
ly we get to a very small or easy problem for
which we can immediately provide the answer In other words, one factorial is equal to one,
(the terminating condition). This answer can while n factorial — where n is any positive
then be passed back up the levels for further integer greater than one — is equal to the
manipulation before obtaining the final result. value n times the factorial of one less than n.
The first part of the definition is the termi-
For particular types of problems, recursive nating condition and the second part is the
algorithms can provide a solution in a few recursive call.
steps that would be extremely complex using
an iterative algorithm. To implement this in Pascal we define a
function (see Figure 1) that takes one integer
Within a function or procedure we make use parameter and returns a long integer
of values held in variables. Won’t these get (remember that it grows very quickly). We

Delphi Informant June 1996 28


Op Tech

function Factorial(Value: Integer):LongInt;


begin
{ Check for invalid parameter }
if Value < 1 then
raise Exception.Create('Invalid value for Factorial');

{ Termination condition }
if Value = 1 then
Result := 1
else
{ Call function again with smaller value }
Result := Value * Factorial(Value - 1);
end;

Figure 1: A recursive factorial function.

need to check that a valid value has been entered initially: it Figure 4: Definition of the tree structure.
cannot be zero or negative. If an incorrect value is found then
an exception is raised to notify the program of the problem. Plotting Trees
A routine that allows us to plot images of trees based on the
With a valid value, we check for the terminating condition structure is shown in Figure 4. A tree consists of two branches
(in this case the value being equal to one), stop the recursion, from the one base point. These can have different lengths, a
and return the predefined result if this is so. If we are not at and b, and can be inclined at different angles, α (alpha) and
the terminating condition, we need to call the function again β (beta), from the vertical.
with the next lowest value, and compute the product with
the current value to obtain the final result. To complete the figure, we then plot the same structure at the
end of each branch on a reduced scale, using the direction of
When called with a value that branch as the new “vertical.” This sort of tree is effectively
Value Result
greater than one, the function a fractal, with parts of it being repeated at an ever smaller scale.
5 5 * 4! halts its execution at the
4 4 * 3! recursive call until the The algorithm for this can be stated as:
3 3 * 2! required value is returned
from the next level down. If the length of the main branch is below a predefined
2 2 * 1! threshold (the terminating condition) then exit the proce-
Once the terminating condi-
1 1 tion is reached, the results are dure without doing anything. Otherwise, given a point
passed back through the pre- and a direction (the “vertical”), plot the two branches of
2 2*1=2
ceding levels building up the the tree. The first is to the left of vertical by a specified
3 3*2=6 final result as it goes. Figure 2 angle and with a given length. The second is to the right
4 4 * 6 = 24 is a picture of this process. of vertical by a, possibly, different angle and with a length
reduced from the first by a given amount. Then call the
5 5 * 24 = 120
To see this in “real life,” we procedure recursively for each new endpoint and its corre-
Figure 2: Steps in computing 5! can put a breakpoint on the sponding angle, but with a reduced main branch length.
result of the terminating
condition (in this case, Result := 1) and then re-execute the This process is captured in the code in Figure 5. We must
program. When it stops, display the call sequence with the pass numerous parameters to it, being the starting point, the
menu command View | Call Stack. The recursive function calls current direction of the vertical (in degrees), the two angles
and their parameters are shown in the Call Stack window. by which the branches are offset, the length of the main
branch and the amounts by which to reduce this to produce
The factorial function the length of the secondary branch, and the length of the
is implemented in the main branch for the next level.
FACT1.PAS unit that
accompanies this arti- We must delve into some trigonometry to calculate the end-
cle. The project, FAC- points for the branches. The mod operator is used to ensure
TRIAL.DPR (see that the angles being used remain in the range of 0 to 359,
Figure 3), allows you preventing any possibility of overflow. Angles are defined in
to select an input degrees, since this is easier for us to use, but must be convert-
value for the factorial Figure 3: Our example project, ed to radians before Object Pascal can use them. Radians are
FACTRIAL.DPR.
function between 0 used in all of Pascal’s trigonometric functions, and are calcu-
and 12 (this being the limit for a long integer result). Note lated based on 180 degrees being equal to Pi radians. The
that using zero results in an exception being raised; this has conversion is done using a predefined value to reduce the
been left to show what happens with invalid input. amount of computation required in the plotting process.

Delphi Informant June 1996 29


OP Tech

{ Recursive plotting routine }


procedure TForm1.DrawImage(CurPoint: TPoint;
CurAngle, LeftAngle,RightAngle: TAngle;
Length: Integer; Ratio, Shrink: Real);
var
Angle: Real;
NewPoint: TPoint; Figure 6:
begin A recursive
{ Terminating condition - line too short }
tree plotting
if (Length < MinLength) or Finish then
program.
Exit;

{ Let something else have a go }


Application.ProcessMessages;

{ Calculate the new endpoint for the left side }


Angle :=
((CurAngle - LeftAngle) mod 360) * ConvertToRadians;
NewPoint.X := CurPoint.X + Round(Cos(Angle) * Length);
NewPoint.Y := CurPoint.Y + Round(Sin(Angle) * Length);
{ And draw a line to it }
TreeImage.Canvas.MoveTo(CurPoint.X, CurPoint.Y); Figure 7: The
TreeImage.Canvas.LineTo(NewPoint.X, NewPoint.Y); first two steps
{ Then call procedure again with reduced size from
in building the
end of line }
Von Koch
DrawImage( NewPoint, (CurAngle - LeftAngle) mod 360,
LeftAngle, RightAngle, Round(Length * Shrink),
snowflake
Ratio, Shrink); curve.

{ Calculate the new endpoint for the right side }


Angle :=
((CurAngle + RightAngle) mod 360) * ConvertToRadians;
NewPoint.X := Plotting a Fractal
CurPoint.X + Round(Cos(Angle) * Length * Ratio); For the third example of recursion we are drawing another
NewPoint.Y :=
CurPoint.Y + Round(Sin(Angle) * Length * Ratio);
fractal shape, this time the Von Koch snowflake curve. It’s
{ And draw a line to it } defined as consisting of an equilateral triangle, each side of
TreeImage.Canvas.MoveTo(CurPoint.X, CurPoint.Y); which is divided into three equal parts, with the central part
TreeImage.Canvas.LineTo(NewPoint.X, NewPoint.Y);
{ Then call procedure again with reduced size from end
being replaced by two sides of a smaller equilateral triangle
of line } based on that part (see Figure 7). This structure is then
DrawImage(NewPoint, (CurAngle + RightAngle) mod 360, applied repeatedly to each of the smaller triangles so created.
LeftAngle, RightAngle,
Round(Length * Ratio * Shrink), Ratio, Shrink);
end; So the recursive algorithm that we are using can be defined as
follows:
Figure 5: The recursive procedure for plotting trees.
Given the two endpoints for this segment, determine its
The procedure contains some extra code to allow for addi- length. If this falls below some threshold (the terminating
tional processing. The first is a call to the ProcessMessages condition) then simply draw this segment on the screen.
method of the Application object. This allows Windows to Otherwise, compute the three additional points that
perform any other outstanding processing. The second is the define the curve over this segment. These are the points
alternate terminating condition, being when the flag Finish is one and two thirds of the way along the line and the
set to True. This variable is defined outside the procedure and midpoint at the top of the central equilateral triangle.
can be used to halt the drawing process if it’s taking too long. Then call the procedure recursively for each of these com-
To do this in our case, the Draw button on the form changes ponent segments in turn.
to a Cancel button once it has started the plotting process. A
second click on it sets Finish to True, which it can do because To calculate the points along the segment is very simple for
of the call to ProcessMessages, and this causes the drawing pro- the two actually on the line. The third point — the midpoint
cedure to terminate on its next cycle. — requires some trigonometry to determine its position.
Basically we find the angle of the line segment (always from
To see this procedure in action look in the TREES1.PAS unit start to finish since this determines on which side the peak
and the TREES.DPR project that accompany this article. appears) and subtract 30 degrees, which is the angle that the
This program allows most of the parameters to the procedure midpoint makes to the original segment through the starting
to be set interactively before drawing the resultant tree (see point. Then using trigonometry, find the x and y offsets to
Figure 6). The Symmetric check box disables the Right Angle the point from the starting point.
and L/R Ratio% controls, tying the first to Left Angle and set-
ting the second to 100 percent. This is merely a convenience The code for all of this is shown in Figure 8. Note that two
to allow easier plotting of symmetric trees. constants have been defined earlier to reduce the amount

Delphi Informant June 1996 30


OP Tech

{ Recursive fractal plotting routine }


procedure TForm1.DrawSegment(FromPoint, ToPoint: TPoint);
var
X, Y: LongInt;
Length, Angle: Real;
MidPoint, FirstThird, SecondThird: TPoint;
begin
if Finish then Exit;

{ Determine length of line segment }


X := ToPoint.X - FromPoint.X;
Y := ToPoint.Y - FromPoint.Y;
Length := Sqrt(Y * Y + X * X);

{ Terminating condition }
if Length < MinLength.Value then
begin { Draw the line segment }
FractalImage.Canvas.MoveTo(FromPoint.X, FromPoint.Y);
FractalImage.Canvas.LineTo(ToPoint.X, ToPoint.Y);
Exit;
end;

{ Allow something else to have a go }


Application.ProcessMessages;

{ Determine the angle of the line segment }


if X = 0 then
begin
if Y > 0 then
Angle := Pi / 2
else
Angle := - Pi / 2;
end Figure 9: The recursive Von Koch snowflake plotting program.
else
Angle := ArcTan(Y / X);
to be plotted in the reverse direction, generating an inverted
if X < 0 then Von Koch snowflake. Because the plotting routine may take
Angle := Angle - Pi;
some time to complete, a call to the ProcessMessages method
{ Compute intermediate points on this line } of the Application object allows other Windows programs to
MidPoint.X := FromPoint.X + perform some processing of their own, and for this routine
Round(Length / Root3 * Cos(Angle - Degrees30));
MidPoint.Y := FromPoint.Y +
to be canceled, similar to the tree plotting program above.
Round(Length / Root3 * Sin(Angle - Degrees30));
FirstThird.X := FromPoint.X + Round(X / 3); Binary Tree
FirstThird.Y := FromPoint.Y + Round(Y / 3);
SecondThird.X := FromPoint.X + Round(2 * X / 3);
The final example of recursion involves creating a sorted
SecondThird.Y := FromPoint.Y + Round(2 * Y / 3); binary tree object. This object maintains a group of other
objects in a specified order, with searching and processing
{ And recursively draw each segment }
DrawSegment(FromPoint, FirstThird);
being on average faster than a straight array. A binary tree is
DrawSegment(FirstThird, MidPoint); built up from nodes. Each node has a value and two point-
DrawSegment(MidPoint, SecondThird); ers (hence binary) to those nodes that have values less than
DrawSegment(SecondThird, ToPoint);
end;
this one and those that have values greater than this one.

Figure 8: The recursive procedure for plotting the Von Koch Note that these pointers may be empty. The first node in the
snowflake curve. tree is referred to as the root, and the tree is usually depicted as
growing down from the root, with node values increasing to
of calculation required. These are Root3 which is the square the right (see Figure 10). Those nodes at the bottom of the tree
root of three and Degrees30 which is 30 degrees expressed with no descendants are referred to as leaf nodes. Note that the
as radians. Also note that we are making four recursive calls root is the only means of access to the entire tree. All process-
from this procedure, one for each component line segment. ing must start from here.

This procedure can be found in the SNOWFLK1.PAS unit. As you can see, each node and its descendants form a sub-
The sample project, SNOWFLAK.DPR, allows the maxi- tree within the main tree. This gives us the self-definition
mum segment length to be set and uses this as the threshold required for recursion, with the leaf nodes or empty point-
value when plotting the curve (see Figure 9). The form ers being the terminating conditions. The processes that
should plot different figures for values of 5, 10, 30, and 90. we must be able to perform on a binary tree include
Note that some of the other values may produce slightly inserting a new value, removing an existing value, check-
incomplete curves because of the closeness of the segment ing whether a value is present, and processing all the
lengths to the threshold value. It also allows for the segments nodes in order.

Delphi Informant June 1996 31


OP Tech
To imple-
{ Recursive procedure to insert an object into the tree }
ment this procedure TBinaryTree.InsertInTree(obj: TBinaryTreeObject;
in Delphi var ptr: PBinaryTreeNode);
begin
we can
if ptr = nil then { Terminating condition - add }
make use of begin
the class ptr := New(PBinaryTreeNode);
ptr^.NodeObject := obj;
structure
ptr^.Left := nil;
and define ptr^.Right := nil;
a binary end
{ Terminating condition - already there }
tree class,
else if obj.IsEqualTo(ptr^.NodeObject) then
TBinaryTree, Figure 10: A sorted binary tree sample. begin
and its asso- ptr^.NodeObject.Free; { Free previous object }
ptr^.NodeObject := obj; { And assign new one }
ciated methods. It’s derived from TObject, since it requires
end
only the very basics of a class definition. It will have a sin- { Check in appropriate half of tree }
gle private variable, that points to the root of the tree, and else if obj.IsLessThan(ptr^.NodeObject) then
InsertInTree(obj, ptr^.Left)
several public functions and procedures to allow us to
else
manipulate the tree. The tree’s nodes are defined as a InsertInTree(obj, ptr^.Right);
record, TBinaryTreeNode, each of which holds an object end;
and two pointers to other nodes:
Figure 11: The recursive binary tree insertion procedure.
{ The binary tree nodes }
PBinaryTreeNode = ^TBinaryTreeNode; we check the right. If we encounter an empty pointer in this
TBinaryTreeNode = record
direction then the value is obviously not in the tree and we
NodeObject: TBinaryTreeObject;
Left, Right: PBinaryTreeNode; must add it at this point, since this is where we should have
end; found it (see Figure 11). This procedure is called by the pub-
licly available Insert method, starting at the root.
To ensure that the objects to be stored in the binary tree can be
properly manipulated by it, we create an abstract base class called The notation in this code includes references to pointers. Type
TBinaryTreeObject that defines the minimum requirements for PBinaryTreeNode is a pointer to a TBinaryTreeNode record. To
such objects. Within it are declared the comparison functions refer to the contents of that record we use the pointer symbol
necessary to position an object in the tree, IsEqualTo and ( ^ ) to access the object being pointed to. Thus ptr is a
IsLessThan, and a procedure to act upon an object when process- pointer to a node, while ptr^ is the node record itself (a
ing all the nodes in order, Process. All these are declared as being process known as de-referencing). References to objects within
virtual and abstract. The former means that they can be overrid- Object Pascal are also pointers, but this is hidden by Object
den in a class derived from this one, while the latter means that Pascal that automatically de-references them when necessary.
they must be overridden since they are not defined here.
Since we only have a pointer declared for our use, we must
Note that a reference to an object is really a pointer to that use the New function to allocate space dynamically when we
object and that it is allocated space somewhere in memory. want to create a new instance of the record. Conversely, when
This memory should be released for other uses when we have we are finished with an object being pointed to, we must
finished with an object. Since the tree holds objects of a class release its resources using the Dispose procedure.
derived from TBinaryTreeObject of which we have no knowl-
edge, we cannot create new instances of these objects to hold The var directive in the parameter list for the procedure means
in the tree within TBinaryTree itself. Thus, we store the that within its code we are working with the actual item passed
pointers to objects that were created outside the binary tree in (in this case a pointer). Any changes to the parameter also
class and must take on the responsibility of releasing their update the original in the calling environment. Normally we
resources once we are finished. would only have access to a copy of the item and would be
unable to affect the original. We need to use the original point-
Grafting er in our case to correctly position the new node in the tree.
To insert a new value into the tree we must start at the root
(initially empty) and traverse the structure to see whether the In our implementation of the tree we are also replacing a
value is already there. This is done by comparing the value node if it already exists in the tree. This allows for any depen-
with that of the current node. If they are equal then the value dent values of the object to be updated. Remember that we
is already in the tree and nothing further needs to be done (a must free the original object with that value since we have
terminating condition). taken over responsibility for it and we no longer require it.

If they are not equal then we need to determine which of the Pruning
node’s sub-trees to search to find it. If the value is less than Inserting an object into the tree is fairly straightforward, but
the current node then we look in the left sub-tree, otherwise removing objects is much more complicated. As before, we

Delphi Informant June 1996 32


OP Tech
search the tree for the object to be removed, starting at the out further changes. If we always used nodes from one sub-
root, and recursively processing the left or right sub-tree as tree and the rate of deletions is relatively high, then the tree
required. If we reach an empty pointer then we can stop since can degenerate into a linked list, which removes the advan-
the object is not in the tree anyway. tages of the tree structure. To overcome this we randomly
pick one of the sub-trees to process for each deletion.
Once the required node has been located it must be removed.
This will involve restructuring the tree below this node since In all cases where we found the object, its resources must
we need to maintain the order inherent in the tree. If this be released and the node that contained it destroyed. The
node is a leaf node then all we need to do is break the link code for all of this is shown in Figure 12. The recursive
from the previous node in the tree to this one. If the node searching function, RemoveFromTree, is initially called by
has only one descendant then we can safely change the link the publicly available method Remove with the root as the
from the previous node to point to this sub-tree. starting point. Once the object is found, control is passed
to the ReorganiseInTree procedure. This is separated from
If the node has two descendants then things become more the searching code to make both easier to follow. It then
involved since we cannot move both up to the previous determines whether or not the node has less than two
level. We do not want to move large numbers of nodes descendants and invokes the recursive sub-tree replacement
around to get everything in the right order again. The solu- procedures as required.
tion turns out to be quite simple: find the largest node in
the left sub-tree, or the smallest node in the right sub-tree, Note that the replacement procedures are declared within
and replace the current node with that one. the ReorganiseInTree procedure. This gives them access to
the variables in that procedure, which we do need. A flag is
This node is guaranteed to be able to replace the node passed back to the user to indicate whether or not the
being removed by the definition of the tree structure, with- record existed in the tree.

{ Recursive function to remove an object from the tree { Recursive procedure to replace with maximum
and return a flag showing whether it was there } in left sub-tree }
function TBinaryTree.RemoveFromTree(obj: TBinaryTreeObject; procedure ReplaceWithMax(var ptrRep: PBinaryTreeNode);
var ptr: PBinaryTreeNode): Boolean; begin
begin { Terminating condition - found maximum }
if ptr = nil then { Terminating condition - not there } if ptrRep^.Right = nil then
Result := False begin
{ Terminating condition - remove } ptr := ptrRep; { Adjust pointers }
else if obj.IsEqualTo(ptr^.NodeObject) then ptrRep := ptrRep^.Left;
begin ptr^.Left := ptrRemove^.Left;
ReorganiseInTree(ptr); ptr^.Right := ptrRemove^.Right;
Result := True; end
end else { Recursive call to next level }
{ Check in appropriate half of tree } ReplaceWithMax(ptrRep^.Right);
else if obj.IsLessThan(ptr^.NodeObject) then end;
Result := RemoveFromTree(obj, ptr^.Left)
else begin
Result := RemoveFromTree(obj, ptr^.Right); { Remember current node for later disposal }
end; ptrRemove := ptr;
if (ptr^.Left = nil) and (ptr^.Right = nil) then
{ Procedure to reorganise the tree from this { Terminating condition - no more branches }
point - deleting current node } ptr := nil
procedure TBinaryTree.ReorganiseInTree( { Terminating condition - move right tree up }
var ptr: PBinaryTreeNode); else if ptr^.Left = nil then
var ptr := ptr^.Right
ptrRemove: PBinaryTreeNode; { Terminating condition - move left tree up }
else if ptr^.Right = nil then
{ Recursive procedure to replace with minimum ptr := ptr^.Left
in right sub-tree } else
procedure ReplaceWithMin(var ptrRep: PBinaryTreeNode); { Replace current object with min in right branch
begin or max in left branch - this is done randomly
{ Terminating condition - found minimum } to reduce distortions to the tree structure }
if ptrRep^.Left = nil then begin
begin if Random(2) = 0 then
ptr := ptrRep; { Adjust pointers } ReplaceWithMin(ptr^.Right)
ptrRep := ptrRep^.Right; else
ptr^.Left := ptrRemove^.Left; ReplaceWithMax(ptr^.Left);
ptr^.Right := ptrRemove^.Right; end;
end { Free object at node to be deleted }
else { Recursive call to next level } ptrRemove^.NodeObject.Free;
ReplaceWithMin(ptrRep^.Left); { And destroy the node }
end; Dispose(ptrRemove);
end;

Figure 12: Recursive binary tree deletion function and procedures.

Delphi Informant June 1996 33


OP Tech
Easy Processing { Recursive procedure for processing all objects in tree
Processing the entire tree in order is very simple with a recursive Can be done in ascending or descending order using flag }
procedure TBinaryTree.ProcessInTree(descending: Boolean;
algorithm. At each node, simply process all the nodes in the left ptr: PBinaryTreeNode);
sub-tree (all less than this one), then process this node and begin
finally those in the right sub-tree (all greater than this one). if ptr = nil then { Terminating condition }
Exit;
{ Process appropriate half of tree before this object }
When we reach an empty pointer we can halt the process. To if descending then
process all the nodes in reverse order, we just reverse the algo- ProcessInTree(descending, ptr^.Right)
else
rithm: processing right, then the current one, then left. This ProcessInTree(descending, ptr^.Left);
is shown in Figure 13. Again it’s called by a public method, { Call process method of the object }
ProcessAll, starting at the root. Remember that the Process ptr^.NodeObject.Process;
{ Process appropriate half of tree after this object }
method of the objects in the tree must be defined in the class if descending then
derived from the base binary tree object class for this to work. ProcessInTree(descending, ptr^.Left)
else
ProcessInTree(descending, ptr^.Right);
The binary tree class also defines some other functions: end;
whether an object exists in the tree, and returning the mini-
mum and maximum values in the tree, but these are left for
the reader to work through.
Figure 13 (Top):
To make use of the binary tree class we must first derive a binary Recursive binary
tree object to be stored in it. In the example program we derive a tree processing in
order.
class that just contains an integer value, TBinaryTreeInteger, Figure 14 (Left):
although it could contain anything that we wanted. We must The example form
then override the three abstract methods with real definitions. for our binary
For example, the IsEqualTo function is defined as: tree example.

{ Equality for a binary tree integer - values equal? }


function TBinaryTreeInteger.IsEqualTo(
other: TBinaryTreeObject): Boolean;
begin
Result := (Value = (other as TBinaryTreeInteger).Value);
end;
The examples presented in this article show how recursion can
be used to implement various algorithms within Object Pascal.
Note that the parameter must be a TBinaryTreeObject, since It’s a powerful tool when applied to the right sort of program-
this is what was defined in the base class, but that it can be ming problem, and frequently produces concise and elegant
cast as our derived class to gain access to its contents. The algorithms as a solution. Fortunately Object Pascal makes it
Process method will simply display the contents of each node. easy to use this tool, without which we might need to perform
some quite complex steps to achieve the same results.
The example form itself in the BNRYTREE.DPR project
(see Figure 14) allows us to manipulate the binary tree. The Some other examples where recursion can be of great use are in
value to be used is entered in the spin edit control, and one most problems involving fractals, in backtracking algorithms,
of the command buttons is pressed to initiate an action, the where a path is followed until it proves fruitless before backing
results of which are displayed in the memo component. up and trying another path, in traversing tree structures (not
just binary ones), and any other algorithm that is defined in
For each action, except Print Tree, a new TBinaryTreeInteger terms of itself. In fact, have a look under recursive loop in the
object is created and passed to the binary tree for processing. If Object Pascal Language Guide. If you want to practice using
the binary tree does not incorporate it into itself, then we must recursion, try to program a recursive function that reverses the
free that object after it has been used. In the case of finds with- order of characters in a string. Remember to define the prob-
in the tree, we are returned a pointer to the actual object in the lem in terms of itself and then implement this in the code.
tree, if it exists. This object must not be freed since the tree still
has a reference to it (the original object that we created was Now if I can just get you back to the start of this article for
freed by the tree before it returned the found one). The binary another look ... ∆
tree class is defined separately in the BINTREE.PAS file so
that it can easily be incorporated into other projects. The demonstration projects referenced in this article are available
on the Delphi Informant Works CD located in INFORM\-
Conclusion JUNE\96\DI9606KW.
Recursion involves defining a problem in terms of itself. Keith Wood is an analyst/programmer with CSC Australia, based in
Each time a smaller part of the problem is worked on, until Canberra. He started using Borland’s products with Turbo Pascal on
we reach a position where we can easily compute the answer. a CP/M machine. You can reach him via e-mail at
[email protected] or by phone (Australia) 6 291 8070.

Delphi Informant June 1996 34


Dynamic Delphi
Delphi 1 / Object Pascal

By Andrew J. Wozniewicz

DLLs: Part IV
Wrapping Up Dynamic Link Libraries in Delphi

n this series, we’ve discovered some of Delphi’s formidable capabilities


I to create and use dynamic link libraries (DLLs). In this final installment,
we’ll cover dynamically loading a DLL, releasing a DLL, using dynamically
loaded DLLs, and accessing data in a library. The end of the article features
an overview for the use of DLLs for your reference.

Dynamically Loading a DLL — nated strings are not and may run up to 64KB
LoadLibrary characters. A PChar is essentially a null-termi-
The keys to explicitly loading a DLL are a nated string (the issue of pointers deliberately
pair of standard Windows API functions that is avoided here) for all intents and purposes.
are always used in conjunction: LoadLibrary
and GetProcAddress. First, you must issue a Null-terminated strings are native to the
call to LoadLibrary, which is declared in the Windows API, but Delphi shields you from
WinProcs unit, as follows: them most of the time. When you need to
make a direct call to an API subroutine,
function LoadLibrary(
however, there is no “protection” — you are
LibFileName: PChar): THandle;
dealing with Windows directly and must use
You can use this function by passing it a the data types that Windows expects.
string value, for example:
A null-terminated string is an array of charac-
var
ters. Unlike Pascal strings, which have the
ALibrary: THandle;
begin length byte at the beginning, null-terminated
... strings do not explicitly store their lengths, but
ALibrary := LoadLibrary('dllfirst.dll');
instead mark their ends with the null-character
...
end (ASCII 0) — hence, their name. A null-termi-
nated string can be much longer than a Pascal
This example, however, hides a number of dif- string, up to the maximum limit of 64KB.
ficulties and issues that arise when you attempt
to use the LoadLibrary function. The first issue The LoadLibrary function expects you to
is making sure the WinProcs unit is in the uses pass the file name of the library as a null-ter-
clause of the unit or module in which you minated string. The example in this section
intend to use LoadLibrary. The second issue is looks deceptively simple because it hides the
that the function takes a single parameter, fact that the string passed as the actual para-
LibFileName, which is of type PChar. meter is a null-terminated string:

So far, you have avoided dealing with the non- 'dllfirst.dll'

Pascal string types, known as null-terminated


strings. The standard string type was sufficient This line is treated by the compiler as the
for most purposes. Note, however, that strings native Pascal string or a null-terminated string,
are limited to 255 characters, while null-termi- depending on the context. In this case, because

Delphi Informant June 1996 35


Dynamic Delphi
the function expects a null-terminated string, the compiler
uses
ensures that the literal constant is treated as such. This is possible ..., WinTypes, WinProcs, SysUtils;
only because the call uses a literal constant. Otherwise, without
var
the additional information from the context in which the literal
AFileName: string;
constant is used, you would not be able to tell whether 'dll- ABuffer: array[0..255] of Char;
first.dll' refers to a Pascal string or a null-terminated string. ALibrary: THandle;
Both look exactly the same when written as true constants.
begin
...
Typically, however, you would not use literal constants for the AFileName := 'dllfirst.dll';
...
file name with the LoadLibrary function. After all, you can
StrPCopy(ABuffer,AFileName);
import the library of interest implicitly by using static constants. ...
The advantage of using LoadLibrary is the ability to specify a ALibrary := LoadLibrary(ABuffer);
...
variable as its parameter, thereby filling the actual value of the
if ALibrary <= HINSTANCE_ERROR then
string at run time. This is where the incompatibility problem { There was a problem! }
between Pascal strings and null-terminated Windows strings else
{ It is safe to use the library! }
creeps in. The compiler does not accept a Pascal string variable
...
in place of the actual parameter for the LoadLibrary function. end;

Note that you must provide the complete file name in a call Figure 1: Explicitly loading a DLL via a LoadLibrary call.
to the LoadLibrary function. At a minimum, this involves
providing the file name and the extension. Because file name After you have the name of the library file to load, you can
extensions other than the default .DLL are possible, no proceed with the conversion to a null-terminated string. The
default extension is assumed. You have to explicitly supply ABuffer variable serves as the storage for the file name string
the extension, if any, even if it is DLL. after the conversion occurs. The StrPCopy subroutine con-
verts between a Pascal string and a null-terminated string.
Fortunately, you don’t have to worry too much about null- The first parameter to StrPCopy is the destination buffer
terminated strings to make the LoadLibrary call. Just remem- where the null-terminated equivalent will be stored. The sec-
ber that it’s not a straightforward Pascal string. Typically, you ond parameter is the Pascal string you want to convert.
must translate between a String variable in which you likely
will have the file name of the library stored, and what the After the conversion, you are ready to call LoadLibrary. The
LoadLibrary function requires. return value of this function is assigned to the variable
ALibrary, declared as a THandle. (We’ll discuss this more
Calling LoadLibrary later.) In a nutshell, it’s a “token” through which you can refer
Without getting into too much detail, Figure 1 illustrates the to the library after it has been loaded. You will need to check
steps to issue the LoadLibrary call. This code shows how to the value of the returned token, however, because a value
use the LoadLibrary function in a generic situation, when the below the predefined HINSTANCE_ERROR indicates an
file name of the library to use for the call is stored as a Pascal error condition. The last three lines of code determine if the
string, as it typically would be. call to LoadLibrary was successful.

First, the uses clause ensures that the required subroutines are The result of the LoadLibrary function call is a value of type
visible. You need WinProcs to access the LoadLibrary function, THandle. This value is a token, or an “abstract value,” that
and SysUtils to access StrPCopy (the conversion routine that enables you to identify the library to Windows after it has
translates between a Pascal string and a null-terminated string). been loaded successfully. It’s important to realize that, as long
You also need WinTypes to use the HINSTANCE_ERROR as the LoadLibrary call was successful, the numeric value of
constant defined there when checking for the result of the the handle it returns is of no importance to you directly. You
LoadLibrary call. simply supply whatever value LoadLibrary returned to other
functions, such as GetProcAddress, that require it.
Then, the necessary variables are declared:
The only time you need to actually look at the value
var returned by LoadLibrary is directly after the call, because
AFileName: string;
ABuffer: array[0..255] of Char;
the return value may indicate an error condition. You can
ALibrary: THandle; check whether a call to LoadLibrary was successful by com-
paring it with a predefined constant declared in the
AFileName is the String variable in which you store the file name WinTypes unit: HINSTANCE_ERROR. If LoadLibrary
originally. Here, for simplicity, the AFileName variable gets its returns a value that is less than or equal to this predefined
value from a straightforward constant assignment. However, this constant, the call succeeded in actually loading the library,
can be replaced by a more elaborate scheme, such as getting the and it’s not safe to use any of the functions that need the
value from a user, reading it from an .INI file, and so on. library to be loaded, such as GetProcAddress.

Delphi Informant June 1996 36


Dynamic Delphi
A call to LoadLibrary does not necessarily result in the library uses
being loaded from disk. If the library is already loaded (i.e. WinTypes, WinProcs;

it’s already being used by another application or by another var


instance of the same application), the DLL’s “usage counter” TheLib: THandle;
maintained by Windows is incremented. Only one copy of FillStr: function (C : Char; N : Byte): string;
UpCaseFirstStr: function (const S: string): string;
the library itself resides in memory at any time. This, after LTrimStr: function (const S: string): string;
all, is the reason for having DLLs: to be able to share code. RTrimStr: function (const S: string): string;
StripStr: function (const S: string): string;

Releasing a DLL begin


Assuming that the call to LoadLibrary was successful, you ...
need to ensure that you free the library once you no longer { Initialize the variables }
@FillStr := nil;
need it. This is again different from the situation of implicitly @UpCaseFirstStr := nil;
importing the DLL, where Windows itself takes the responsi- @LTrimStr := nil;
bility for both loading and unloading the library when neces- @RTrimStr := nil;
@StripStr := nil;
sary. Once you have taken over the responsibility to explicitly ...
load the library, you also must ensure that it’s possible for { Load the library dynamically }
Windows to unload it when it’s no longer needed. TheLib := LoadLibrary('DLLFIRST.DLL');
if TheLib > HINSTANCE_ERROR then try
...
To tell Windows that your application is no longer interested { Retrieve the subroutine addresses }
in the library, you must issue a call to the standard Windows if TheLib > HINSTANCE_ERROR then begin
@FillStr := GetProcAddress(TheLib,'FillStr');
FreeLibrary procedure. It’s declared inside WinProcs as follows: @UpCaseFirstStr :=
GetProcAddress(TheLib,'UpCaseFirstStr');
procedure FreeLibrary(LibModule: THandle); @LTrimStr := GetProcAddress(TheLib,'LTrimStr');
@RTrimStr := GetProcAddress(TheLib,'RTrimStr');
@StripStr := GetProcAddress(TheLib,'StripStr');
As you can see, FreeLibrary takes a single parameter, end;
LibModule, of type THandle, that identifies the library to be ...
released. This is the same “token” handle that LoadLibrary { Use routines here, almost same as before. e.g.: }
if Assigned(LTrimStr) then
returns. In fact, a call to FreeLibrary does not necessarily S := LTrimStr(' This is fun! ');
result in the library being unloaded immediately. It all { S now equals 'This is fun! ' }
depends on who else is using the same library at the time. If S := StripStr(' Teach Yourself Delphi Now! ');
{ If StripStr = nil, an exception occurs and control
there are other applications also using the library in ques- jumps to the finally block; if no exception occurs,
tion, the call to FreeLibrary merely decrements a usage S now equals 'TeachYourselfDelphiNow!' }
counter maintained by Windows. Only after the usage ...
finally
counter reaches zero is the library unloaded. { Release the library once you are done }
FreeLibrary(TheLib);
It’s important to remember that if you used LoadLibrary to end;
...
access a DLL, you must use a corresponding FreeLibrary to end.
release it. Otherwise the usage counter is never decrement-
ed to zero and the library remains loaded even if it’s no Figure 2: Dynamically loading the sample DLLFirst library.
longer being used. When your program terminates, nor-
mally or abnormally, it’s your responsibility to issue the to obtain the address. (As before, you may need to translate the
FreeLibrary call. Pascal string where you stored the procedure name into a null-
terminated string required by the GetProcAddress call.)
Using Dynamically Loaded DLLs
Assuming the call to LoadLibrary was successful, you can take Note that Windows does not give you any way of retrieving
steps to retrieve the addresses of the subroutines you want to any information about the parameter lists and return types of
use from the library. The most convenient way of doing this is the DLL subroutines you are accessing. In other words, you
by declaring a subroutine type variable, the value of which you must know the signature, or the declaration of the subroutine
fill at run time with the address retrieved from a DLL via a call you want to use beforehand. You can dynamically retrieve the
to GetProcAddress (you’ll see an illustration of this shortly). run-time address of the subroutine in a DLL, but not run-
time type information about it.
GetProcAddress is declared in WinProcs as follows:
Loading and Calling
function GetProcAddress( Module: THandle;
ProcName: PChar): TFarProc;
Now take a look at how you would go about dynamically load-
ing and calling subroutines in the DLLFirst library we’ve devel-
This declaration takes two parameters. The first, Module, is the oped in this article series. Figure 2 provides a template for using
library handle previously returned from the call to LoadLibrary. any DLL. Simply substitute the definitions and names specific to
The second parameter, ProcName, is a null-terminated string DLLFirst in the listing with ones pertaining to the specific
that contains the name of the subroutine for which you want library you want to access.

Delphi Informant June 1996 37


Dynamic Delphi
The code in Figure 2 gives you an idea of what’s involved that the functional variable contains a valid address.
when using an explicitly loaded DLL. The example DLL- A different approach to error-checking is taken with this
FIRST.DLL is used here as an illustration of the steps needed. statement:

The key to successfully using the subroutines in the library S := StripStr(' Teach Yourself Delphi Now! ');

is to provide a set of variables that can hold the values of


run-time addresses of the library subroutines. In the var sec- The new style of programming makes use of the exception-
tion, a set of variables is declared, conveniently named the handling mechanism built into Delphi. Remember that the
same as the function addresses of which they will be storing. try..finally block you set up earlier is in effect here:

S := StripStr(' Teach Yourself Delphi Now! ');


The library handle, TheLib, is retrieved via a call to
LoadLibrary. A try..finally exception-handling block is set
If the call to StripStr is unsuccessful, such as when the value
up to protect the “working” code from the possibility that
of StripStr is nil, a General Protection Fault (GPF) exception
the code executed within the try block may have failed. If
occurs. Instead of performing any useful action, the control
an attempt to call a subroutine from the library within the
of execution immediately jumps to the finally block. The
try block results in a failure, an exception is thrown. The
finally block traps the exception and, in this case, makes sure
finally block is guaranteed to execute the FreeLibrary call,
that the library is properly unloaded:
releasing the resources taken by the library.
finally
In other words, after the initial determination that the FreeLibrary(TheLib);
library call was successful is made by the if statement, the end;

finally clause of the try..finally block ensures that the library


is unloaded properly, even if an exception occurs during the After you are done using the dynamic library that you have
execution of the statements inside the try part. loaded explicitly, be sure to release it with a call to
FreeLibrary. The code in Figure 2 ensures that the library is
Assuming that the LoadLibrary operation was successful, the eventually unloaded by making a call to FreeLibrary:
actual run-time addresses of the subroutines imported from
FreeLibrary(TheLib);
the DLLFirst library module are retrieved.
Before the call is made, however, the code checks if the
The @ operator in front of the variable names is meant to
library has been successfully loaded in the first place.
deliberately circumvent the strong type-checking mechanism
of Object Pascal and, for a short moment, to treat the proce-
The remainder of our article points out another important
dural variables as if they are pointer variables (variables stor-
issue that makes using DLLs different from using regular
ing memory addresses of an unspecified type). Otherwise the
units: accessing data inside the library.
compiler would complain about the incompatibility between
the two sides of these assignment statements. Observe that
Accessing Data in a Library
GetProcAddress returns a THandle type, while the left-hand
An important point when you are considering implementing
side variables are declared as function-typed variables.
DLLs is that there’s no way of directly exporting data from
them. Unlike in the case of a simple, statically linked unit,
The subroutine variables, after the address values are assigned
where a variable declared in the interface section is potential-
to them, can be used to call the appropriate subroutines. For
ly visible to, and accessible from, any other unit or Pascal
example, the following line of code appears exactly as if a call
module in a project, variables declared inside a library remain
to a normal subroutine is being made:
“external” and private to that library. The only interface avail-
S := LTrimStr(' This is fun! ');
able to the users of the library is the subroutine interface:
procedures and functions.
The function LTrimStr is being called and returns a value as
before. The interesting point here is that LTrimStr is not a func- You are free to declare global variables inside a library mod-
tion per se, but a reference to a functional variable, the value of ule, and declaring them is just as easy as doing so in a pro-
which was determined at run time. The difference is that you gram module. However, the variables you declare inside a
must check the variable’s value for validity, because unlike stati- DLL are private to that DLL. You must provide a procedural
cally linked or implicitly imported subroutine addresses, the interface to allow the applications using the library to access
subroutine’s address may not be available. The determination as the variable (or variables) declared inside it when needed.
to whether it’s safe to make the call is done with this code:
Warning! Always remember DLLs are shared resources.
if Assigned(LTrimStr) then Global variables in a DLL are shared across all clients of
the DLL. If one client application using the DLL changes
This if statement is an example of the traditional approach to its value through a procedural interface to the DLL, all
error-checking. A run-time error is prevented by making sure other clients will also see the new, changed value. This may

Delphi Informant June 1996 38


Dynamic Delphi

library ExtStr;
The exports clause lists the subroutines actually exported
var from a DLL. All the exported subroutines must have
AString: string; been declared with the export directive.
function GetValue: string; export;
The most convenient way of accessing the subroutines
begin inside a DLL is by creating an implicit import unit,
Result := AString; declaring the headers of the subroutines, and binding
end;
them to the corresponding routines inside a DLL via the
procedure SetValue(AValue: string); export; external directive.
begin There are many ways of binding the subroutines declared
AString := AValue;
end;
as external to the actual subroutines implemented inside a
DLL. The subroutines can be bound by their declared or
exports assumed name, or by an ordinal number.
GetValue index 11,
SetValue index 12;
A LoadLibrary standard API function can be used to
provide a greater degree of control over when a partic-
begin ular DLL is loaded. There must be a matching call to
end.
FreeLibrary for each invocation of the LoadLibrary
Figure 3: Exporting a string variable from a DLL via a procedural function.
interface. Before you can use the subroutines inside a library explic-
itly loaded with a call to LoadLibrary, you must retrieve
be useful sometimes, but often is unwanted, unexpected,
their addresses via a call to GetProcAddress.
and may be disastrous. Writing multi-user servers as DLLs
You can store the values retrieved by GetProcAddress in
is a tricky issue that is beyond the scope of this series.
procedural-type variables, which can later be used to call
the subroutines.
The library presented in Figure 3 indirectly exports a string
You cannot export data elements directly from a DLL. To
variable, AString, by providing two access subroutines:
make data available to applications using a DLL, you
GetValue and SetValue. The code accomplishes the goal of
must provide a procedural interface, consisting of a
exporting a data element from a library module. The data
GetXXXX function and a SetXXXX procedure, to retrieve
element “exported” by the DLL, AString, is declared early.
and change the value of a particular variable in question.
The AString variable is not visible outside the module, however.
This discussion of DLLs has only scratched the surface of the
To enable applications using the library to obtain and change
issues involved. Be sure to consult other references when you
the value of the variable, two access subroutines are defined:
are considering making heavy use of dynamic linking. The
The GetValue function, to retrieve the current value of
examples given here are just a foundation for knowledge.
the variable.
The SetValue procedure, to change the value of the variable.
Now go build some DLLs! ∆
These subroutines actually are exported via an exports
This article was adapted from material for Teach Yourself
clause, and the client applications can operate effectively on
Delphi in 21 Days [SAMS, 1995], by Andrew Wozniewicz
the value of the variable without “seeing” the variable direct-
and Namir Shammas.
ly. This is similar to the concept of access methods for a class
property. In both cases, the access subroutines shield the
The example DLLFIRST.DLL and its associated files are
using code from directly manipulating the value, and may
available on the Delphi Informant Works CD located in
introduce side-effects, as well as validation and checking.
INFORM \JUNE \96 \DI9606AW.
Conclusion
This series has discussed the following issues:
Like application modules, DLLs are executable modules,
but they are not directly executable. Andrew J. Wozniewicz is president and founder of Optimax Development
Corporation (http: //www.webcom.com/~optimax), a Chicago-based consultan-
DLLs make it possible for many running applications, or cy specializing in Delphi and Windows custom application development, object-
many instances of the same application, to share code and oriented analysis, and design. He has been a consultant since 1987, developing
binary resources. primarily in Pascal, C, and C++. A speaker at international conferences, and an
DLLs are created in Delphi by replacing the keyword early and vocal advocate of component-based development, he has contributed
program with the keyword library in the main Delphi articles to major computer industry publications. Andrew can be contacted on
CompuServe at 75020,3617 and on the Internet at [email protected].
project file, and making some additional changes to the
standard project file generated by Delphi.
The export directive makes a subroutine exportable —
capable of being exported by a DLL and used by an
application external to that DLL.

Delphi Informant June 1996 39


New & Used
Delphi 1 Utility

By Robert Vivrette

Memory Monitor for Delphi


Plug Your Delphi Application Resource Leaks

rogramming for an environment such as Microsoft Windows is full of


P pitfalls. Fortunately, many of the development languages available
today (such as Delphi) take great strides to protect us from these hazards.
However, a programmer can still get into trouble fairly easily.

One significant problem is resource leak- the state of each of these memory heaps.
age. All of you who have worked with Fortunately, Windows 95 takes great
Windows 3.x or Windows for Work- strides to alleviate much of the basic
groups are familiar with the problem. A restrictions on the use of these memory
program uses pieces of memory from two segments. A determined, ill-behaved pro-
small supplies called the GDI and USER gram, however, can still wreak havoc.
heaps. Each of these is limited to 64KB,
so when a program takes some of this As useful as these resource monitoring pro-
memory and doesn’t give it back, less is grams are, they are — after all — merely
available for the next application. monitors. They do nothing to help uncover
Eventually, enough badly behaved pro- the programming flaw that generated the
grams bring Windows to its knees, forc- leak in the first place. In addition, they
ing the user to restart the machine. typically only monitor the relatively small
GDI and USER heaps. Leaks relating to
This has given rise to a multitude of the global memory pool are much more
resource monitoring programs that display difficult to track.

What would you think of an oil compa-


ny that expended huge amounts of
money to create a device that measured
the amount of oil that leaked from
improperly designed hull plates of a
super-tanker? Waste of money, right? The
oil company should spend that money
designing better hull plates so the oil
wouldn’t leak in the first place.

Resource monitoring gauges are much


like this. They track the amount of mem-
ory a program is leaking after it has
already been shipped to thousands, or
tens of thousands, of users. It does little
to help a programmer fix the problem,
but rather indicates to the user when the
machine is about to crash.

Delphi Informant June 1996 40


New & Used
Enter MemMonD button (see Figure 2). When the button is clicked, a single
MemMonD (for Memory Monitor for Delphi) is not a TBitmap component is instantiated in memory:
typical memory monitoring tool. Rather it is a tool used
procedure TForm1.BitBtn1Click(Sender: TObject);
by Delphi programmers to track down leaks before the
begin
application leaves the shop. It allows programmers to see Bitmap1 := TBitmap.Create;
the lines of source code that are causing memory leaks so Label1.Caption := 'Bitmap Created';
they can modify the code and correct the problem. end;

Using MemMonD is about as simple as it gets. Before Most of you will recognize the
testing, the application needs to be compiled with the problem here. Aside from the
Map file: Detailed radio button selected on the Linker fact that the program does
page of the Project Options dialog box (see Figure 1). nothing with the TBitmap, it
This setting tells the Delphi compiler to generate a map also doesn’t release it from
file (*.MAP) during the linking stage that shows (among memory. The object was
other things) memory addresses for the various pieces of Figure 2: A simple “leaky” instantiated, telling Delphi to
code in the program. demonstration program. create various internal struc-
tures for the TBitmap, yet it is
never removed from memory. Delphi has no way of flag-
ging this type of logic error. My use of the TBitmap is syn-
tactically correct — as far as it goes.

To see how
MemMonD
helps in this sit-
uation, let’s run
this sample
program while
MemMonD is
watching. After
launching
MemMonD
and selecting
Figure 1: Delphi’s Project Options dialog box. the program to
monitor, you’ll
Then it’s a simple matter of firing up MemMonD, selecting see something Figure 3: MemMonD at work, displaying
the application to test, and running that application. Your similar to each unit’s address.
program will run normally with MemMonD sitting in the Figure 3. The
background watching what your program is doing. (Its main list box shows all the units that have been compiled
window even has shifting eyes to indicate that it’s at work.) into the example project. (Note that the user has com-
plete control over what memory issues will be reported.
Exercise all portions of your test program, then shut it This is just one “mode.”)
down. MemMonD will prepare a report that contains infor-
mation about global memory and stack consumption, and a After running the sample program, clicking on the but-
list of pointers that were never freed — each with a reference ton (and creating the TBitmap), and then quitting,
to the line of source code where the pointer was allocated. MemMonD will generate a report that uncovers our
problem (Figure 4). As you can see, MemMonD noticed
It will also catch one of the more difficult bugs to track there were 2 pointers to memory, accessing a total of 44
down in a Delphi program — namely, the use of the bytes, that were not released. It also indicates that these
FreeMem procedure with a different size parameter from pointers were allocated in the UNIT1.PAS file on line
that used when the GetMem or AllocMem procedure was 29. If we go back to the source for our demonstration
used to reserve the memory. application, we see the following on line 29:

Bitmap1 := TBitmap.Create;
To demonstrate MemMonD’s principal capabilities, let’s
look at a simple example.
In this case MemMonD has spotted the culprit as being the
creation of a bitmap that was never released. To remedy this
An Example
problem, the programmer would then need to include a:
The example application performs one simple task — and
performs it incorrectly. It presents a form with a single Bitmap1.Free;

Delphi Informant June 1996 41


New & Used
statement at an MemMonD, you get a user
appropriate license file that enables its
spot in the stack check hooking and block
application. overwrite options.
MemMonD detects and reports resource
Keeping Currently, MemMonD only leaks in Delphi applications. It’s also
capable of generating profile statistics
Perspective works with the 16-bit version of for the most time-consuming routines,
Obviously, this Delphi. However the author has the most stack-consuming, or the most
frequently-called routines. The regis-
sample pro- indicated that a Delphi 2 com- tered version features stack check hook-
ing and block overwrite options.
gram is about patible version is in the works MemMonD should be in every Delphi
as trivial as you and should be available by the developer’s tool kit.
can get. Any time you read this. Price: Single-user license US$49;
programmer unlimited site-license US$147. A share-
ware version is available as MEMM-
worth his or Figure 4: MemMonD identifies the prob- MemMonD was created by Per ND.ZIP in the “Debugger/Tools” library
her salt would Larsen (CompuServe section of Borland’s Delphi
lem — two pointers that were never freed CompuServe forum (GO DELPHI). It’s
have spotted — and the line of Object Pascal code that 75470,1320) and costs US$49 also available on the Delphi Informant
created the pointers. Companion Disk and for download
this one with for a single-user license, from the Informant Web page
both eyes US$147 for an unlimited site- (http://www.informant.com) or the
Informant Forum on CompuServe (GO
closed. However, MemMonD is just as capable with the license. There is no written ICGFORUM). Delphi 3rd Party Library.
most complex program you can throw at it. I’ve had the documentation, but the File name: MEMMND.ZIP.
opportunity to test MemMonD with many applications included Help files are suffi- Developer:
where I work and it has paid for itself many times over. cient to learn how the package Per Larsen
CompuServe: 75470,1320
works. There is a shareware
Many leaks (including this one) could allow an applica- version of the package (with-
tion to run for hours or days with no apparent side out some of the extended capabilities) that can be
effects. It is simply a matter of how much memory is found as MEMMND.ZIP in the “Debugger/Tools”
leaking and from where. If it is a leak from one of the library section of Borland’s Delphi CompuServe forum
limited GDI or USER heaps, you could see nasty (GO DELPHI). Users can register the software via
behavior quite early. However, if the leak is simply out SWREG (ID 9253) and will obtain, by e-mail, a file
of the global memory pool, it could be weeks before the that unlocks the extended capabilities of the package.
program would misbehave.
High Marks
In our example, the program is leaking 44 bytes every MemMonD gets very high marks from me. It has
time the button is clicked. My machine has 32 already saved me untold effort — and embarrassment —
megabytes of memory, so it would likely take quite a tracking down memory leaks in some of the applications
few mouse clicks to exhaust the system’s available mem- I’ve been working on. (Hmmm. Should I admit that I
ory. However, as mentioned above, programs are never have written a leaky program?)
as simple as this example. If this leak were inside a loop
that created hundreds of bitmaps, it would constitute a I honestly feel that no serious Delphi programmer
major resource leak. should be without MemMonD. I eagerly await the 32-
bit version — and have expressed this to the author on
Interestingly, MemMonD also points out some memory numerous occasions. It already has a permanent place
leaks in the Delphi VCL. Granted these leaks are in my suite of utilities, and when I die, they will have
minor, and Borland has commented that these leaks are to pry it from my fingers. ∆
negligible and were purposely left in for performance
reasons. However, the registered version of MemMonD
comes with patches that you can insert into the VCL to
correct them if you wish.
Robert Vivrette is a contract programmer for Pacific Gas & Electric and Technical
But Wait, There’s More 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
What I have described here are only the basic capabili- be reached on CompuServe at 76416,1373.
ties of MemMonD. It’s also capable of providing profil-
ing statistics on the most time-consuming routines, the
most stack-consuming, or the most frequently-called
routines. These are very handy features that allow you
to see where your program is spending most of its time.
Armed with this information, you can optimize those
sections of code. In addition, when you register

Delphi Informant June 1996 42


TextFile

Developing Custom Delphi Components


If components are the heart this section is an excellent issues such as building “wrap-
of Delphi, Ray Konopka overview of Delphi’s object per” components and encap-
finds himself in the position model. The author addresses sulating multiple controls in a
of a pioneering surgeon. many key object-oriented component. The final part of
Until now, there has been a programming (OOP) issues the book examines advanced
paucity of substantive infor- — such as virtual methods, topics related to component
mation about developing method pointers, class meth- development, most notably a
custom components. The ods, virtual constructors, chapter on property editors
Borland documentation is and run-time type informa- and component editors.
helpful, but woefully incom- tion — that I haven’t seen Perhaps the most interesting
plete. And early third-party discussed in other third- chapter in the book for me It should be noted that the
books fail to discuss the issue party Delphi books. was the one on building data- book was written using
in any depth. Additionally, Unfortunately, Components aware business components. Delphi 1, but that all example
while the source code for the doesn’t cover them in as much This chapter is the first dis- components compile without
Visual Component Library detail as one would ideally cussion I’ve seen on develop- problem under Delphi 2. An
(VCL) is invaluable, it is no like. Additionally, if you are ing persistent business objects appendix includes informa-
substitute for a solid ground- new to Object Pascal or OOP in Delphi. Konopka shows a tion on moving to the 32-bit
ing in the basics. terminology, you may want to technique that allows you to version of Delphi.
Ray Konopka solves this have a second reference attach a “business compo- In the past 15 months, the
problem — and many more handy. Terms such as forward nent” to the normal data link Delphi developer community
— in his Developing Custom class, virtual, and abstract are (i.e. TTable->TDataSource-> has been forced to deal with a
Delphi Components, published probably not adequately TDBEdit). This allows you to lack of information on
by Coriolis Group Books. defined for the uninitiated. work with business objects in advanced programming sub-
While not flawless, The second part of the book code, store their data persis- jects. Now Ray Konopka’s
Components is the first book introduces the reader to tently in a database, and even Components serves as a pio-
to address the weighty subject Delphi’s component architec- display object data using neering book to address this
of Delphi component devel- ture. Konopka spends a chap- built-in data-aware controls. recognized need. However,
opment, and it tackles the ter dissecting a component While Components is the what ultimately makes
challenge with precision and and follows with a look at the first book to address busi- Developing Custom Delphi
focus. It’s obvious — some- Visual Component Library. ness objects in Delphi, the Components a winner isn’t that
times painfully so — that The actual process of building solution proposed is limited it’s the first to cover these
some books include topics a component is then detailed to simple objects, and would advanced issues, but that it
solely so they can be included in a cradle-to-grave manner, not stand up to a complex also covers them comprehen-
in the table of contents. Not starting with creating the unit object model without modi- sively, with clarity and insight.
so with Components. Even file with the Component fication. Nonetheless, at a
sections devoted to relatively Expert, writing the code, minimum, this discussion — Richard Wagner
minor subjects, such as excep- installing it onto the provides a jumping off point
tion handling and debugging, Component Palette, and con- for development of a more
have considerable meat to cluding with a test of the intricate real-world solution. Developing Custom Delphi
them and stand up on their Design-Time Interface. Developing Custom Delphi Components by Ray
own. Add to this Konopka’s Part three — which for Components comes with a Konopka, Coriolis Group
easy-to-read writing style and many readers will represent CD-ROM filled with Books, 7339 East Acoma
the result is a book that any the core of the book — builds source code for all compo- Dr., Suite 7, Scottsdale, AZ
serious Delphi developer on this initial discussion in nents discussed. Among the 85260, (800) 410-0192,
should add to his or her order to guide the reader many examples are: progress http://www.coriolis.com.
library. through the process of build- and slider bars, an applica-
Components begins by dis- ing components in all the tion launcher, an e-mail ISBN: 1-883577-47-0
cussing the basics developers major categories: graphical, mailer, a data status control, Price: US$39.99
should know before building dialog, non-visual, and data- and an employee business 585 pages, CD-ROM
components. Included in aware. It also covers related component.
Delphi Informant June 1996 43
File | New
Directions / Commentary

A Zero Sum Game?


f Mark Twain were alive today, and in our line of work, he would perhaps quip about Windows 95 and Windows NT by
I saying “reports of their death have been greatly exaggerated.” At various times over the past few years, each has been tout-
ed as being both the operating system (OS) of the future and as a failure. Andrew Schulman wrote in Unauthorized
Windows 95 [IDG Books, 1994]: “Products such as NT…speak to too small a niche to be interesting” and followed with:
“Windows 95 will be the standard desktop OS for the next five years.” Recent columns now say the opposite, painting a
gloomy picture for Windows 95 as a “home” operating system. If you believe everything you read, you may be quite con-
fused. Forget the hyperbole. Neither OS is perfect for everyone, but each has convincing reasons for its use.
A Remarkable Similarity. We’re tempted significant to developers is in Windows and proprietary information leave the
to choose an operating system like we NT: stability, crash protection, pre- confines of a secure office environment
choose a spouse: you can only pick emptive multitasking capabilities (even is a notion that gives most corporate
one. Microsoft is altering this age-old with Win16 apps), and Windows 95 MIS managers nightmares. With
trend on the desktop by minimizing UI compliance. Developers will typi- Windows 95, you have no way to fully
the differences in the two platforms for cally face the least number of hurdles protect data on the notebook.
both users and developers. First, the in terms of hardware constraints, since However, NT’s secure NTFS file sys-
popular Windows 95 user interface is many of you who use Delphi 2 already tem prevents users from gaining access
now available in Windows NT 4.0 have a Pentium with at least 24MB of to data unless they have logged into
(Shell Update Release), giving users the RAM. Grade: Windows NT (A+), NT itself. Grade: Windows NT (B-),
same “look and feel” across platforms. Windows 95 (B-). Windows 95 (B-).
Second, since applications — the Corporate. If perception is reality, SOHO. For most users in the SOHO
lifeblood of any operating system — Windows 95 is doomed for the corpo- category, Windows 95 is the only one
are designed for a specific environment, rate desktop. While at one time it of the two that makes sense. Critical
ISVs have always had to select the plat- seemed inevitable that Windows 95 factors, such as performance and Plug
form on which to put their develop- would be the next standard for the cor- and Play, help get the job done quicker
ment efforts. Quite ingeniously, porate desktop, today’s conventional with the least amount of effort. In this
Microsoft gently coerced vendors to wisdom says that Windows NT 4.0 will “Just Do It” environment, you will
support both Win32 platforms by own this market in 12 months. Slower- want Windows 95. Grade: Windows
requiring Win95 Logo applications to than-expected corporate sales, Windows NT (B-), Windows 95 (A).
work on both systems. Therefore, 95 bashing in the trade press, and an Making a decision ... for now. In 1996,
although Windows 95 and Windows incoherent message from Redmond choosing a desktop operating system
NT have fundamentally different archi- have left many companies holding off need not be an either/or proposition.
tectures, they can be remarkably similar on Windows 95. Robustness, security, The right decision depends on the con-
for both users and developers. and client/server stability are proving text. For now, it seems clear that
Making a choice. Even if both can key factors in convincing many medi- Microsoft is comfortable providing a
coexist in the marketplace and in your um- to large-sized companies to opt for two-tiered solution. But whether
company, which one is right for you Windows NT. Grade: Windows NT Redmond’s dual OS approach is short-
and for users of your software? Let’s (A), Windows 95 (B). or long-term remains unclear. I suspect
look at four environments most Delphi Mobile. Which OS should reign on the answer depends largely on the mar-
developers have to be concerned with the notebook? Neither is perfect, so ket’s ability to accept the notion that
— development, corporate, mobile, your decision depends fundamentally two desktop platforms can — in fact —
and small office/home office (SOHO) on where you stand on the “Ease of use coexist and complement each other. ∆
— and see how Windows 95 and vs. Security” issue. Touting strong PC
Windows NT rate in them. card support, hot docking, and — Richard Wagner
Development. Probably the single advanced power management,
most important environment for you Windows 95 is perhaps the best OS Richard Wagner is the Chief Technology
personally is the one on which you ever created for the mobile user. Officer of Acadia Software in Boston,
develop software. For those of us However, many companies deploying MA. He welcomes your comments at
developing 32-bit Windows applica- mobile work forces have a more impor- [email protected].
tions, there is no contest; everything tant concern: security. Letting sensitive

Delphi Informant June 1996 44

You might also like