On The Cover: October 2002, Volume 8 Number 10

Download as pdf or txt
Download as pdf or txt
You are on page 1of 41

October 2002, Volume 8 Number 10

Cover Art By: Arthur A. Dugoni Jr.

ON THE COVER
5

First Look

Delphi 7 Studio Cary Jensen, Ph.D.


The new version of Delphi is here, and Cary Jensen has the inside story.
And what a story! For starters, Delphi 7 Studio includes Kylix 3 for
Delphi, and a preview version of Borlands new .NET compiler. Then
there are the IDE enhancements, new database features, Rave Reports
... Need we go on?

26

Greater Delphi

Object Picker Component Marcel van Brakel


Part of the Microsoft Windows 2000 API, the object picker dialog box
provides an interactive way to get at Active Directory information.
Marcel van Brakel introduces the dialog box, demonstrates how you
can use it in your applications, and shares a nifty Delphi component
that wraps it up for easy use.

FEATURES

REVIEWS

11

33

CodeRush 5 and 6

37

Abbrevia 3

Columns & Rows

Choosing a Database Bill Todd


Whats the right database for your next big project? Its an important
question that doesnt get asked often enough. From transaction processing to security, noted database expert Bill Todd has put together a
series of checklists to help you make the right decision.

Product Review by Alan C. Moore, Ph.D.


Product Review by Alan C. Moore, Ph.D.

DEPARTMENTS
16

Delphi at Work

XML Building Blocks: Part II Keith Wood


Keith Wood continues his generic-XML-component series by demonstrating
how to create DOMs from alternate data sources, modify the DOM by adding a time-stamp element, merge several parts to form a new document,
and create copies of a document for separate, subsequent processing.

22

The API Calls

Active Directory via ADO Shane Miller


Shane Miller shows you how to use ADO to get information about
particular Active Directory users by employing Active Directory Service
Interfaces (ADSI) and SQL queries using Lightweight Directory Access
Protocol (LDAP) and how to steer clear of the pitfalls.
1 October 2002 Delphi Informant Magazine

Symposium by Jerry Coffey

Delphi Tools

40

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

Symposium
Borland Makes News, Deals, Waves
Recently, I was wandering through the local Borders. Nothing unusual
there; I tend to haunt bookstores. What was unusual was that Borland
CEO, Dale Fuller, caught my eye or at least a picture of him did.
There he was, dressed in black as always, looking stylish on the cover
of Fast Company magazine. The headline screamed BACK IN THE
BLACK and promised to describe How You Can Get on the Road to
Recovery. Exciting stuff!
Why? It was the first time I had seen Borland mentioned in a positive
light in a magazine on a newsstand (other than in this magazine) since
well since I cant remember. By the way, you can check out the
article, Borland Software: Back in the Black by Linda Tischler, at
http://www.fastcompany.com/online/60/borland.html. Among other
things you get Fullers frank appraisal of Borlands state when he first
took the reins: [Borland] wasnt on the ropes, it was hanging by a rope.
It wasnt even still twitching. It also chronicles his sharp strategy for
getting 125 million dollars from Microsoft.
As an aside, I could not stifle a rueful smile at this quote from investment analyst, Audrey Snell, senior vice president and codirector of
research at Brean Murray & Co.: I knew that they had great products
and a large installed user base, but they had also had lousy management
for a long time. It takes real talent to run a software company into the
ground, because youre dealing with a perfect business model: 85% gross
margins, 30% operating margins, 20% net margins, tons of free cash
flow. You have to be really and truly asleep at the switch to lose that.
Snell now rates Borland as ...a strong buy ... It has correctly identified
three of the major trends in the industry for the next 10 years: the growth
of wireless, the importance of cross-platform development, and the
development of Web services to facilitate e-commerce. Those could be
gigantic businesses.
For longer than I care to think about, Borland had been off the radar
of the technical press, or any press for that matter. The last time there
was a flurry of coverage was when then-CEO, Del Yocam, renamed the
company to Inprise. All the propellerhead pundits jumped in to say what
a bad idea that was, and how Borland er, Inprise was doomed. And
they were right; Inprise was going nowhere fast. I used to joke (again,
ruefully) that Inprise was a fitting name because it stood for Inept Enterprise. The once proud and mighty Borland was at its nadir.
Thankfully, things have changed dramatically. Dale Fuller has Borland not
only back in the black, but on the right track as well. Borland is making
news again, and the news is good. Take these articles for instance: Borland,
IBM set bundles for developers by Margaret Kane (http://news.com.com/
2100-1001-954322.html), and Borland, BEA shake hands on Java by
Wylie Wong (http://news.com.com/2100-1001-949713.html). Both are
from CNET Networks, Inc. The only deals discussed by previous Borland
execs were ones involving sale of the company (remember the Corel
fiasco?). Happily, this is all well in the past; Fuller is making bold strategic
moves and the media are taking note.
Borlands canny early embrace of Microsofts .NET initiative provides further cause for good press. A case in point is another CNET
piece by Wylie Wong, Borland to wield tools against Microsoft
(http://news.com.com/2100-1001-954958.html). In it, Wong
describes Borlands Galileo project the as-yet-unnamed Delphi
for .NET product, set to roll out in the first quarter of next year.
In brief, under Dale Fullers leadership, Borland is raising its profile
and looking good.
Thanks for reading.

Jerry Coffey, Editor-in-Chief


[email protected]
2 October 2002 Delphi Informant Magazine

Delphi
T O O L S

New Products
and Solutions

Book Picks
.NET Security
Jason Bock, Pete Stromquist, Tom
Fischer, and Nathan Smith
Apress

ISBN: 1-59059-053-8
Cover Price: US$44.95
(310 pages)
www.apress.com

Windows XP in a Nutshell
David A. Karp, Tim OReilly,
and Troy Mott
OReilly

Turbo Demo 2.0 Special Offer


Bernard D&G
released Turbo
Demo 2.0,
a Windows
application that
makes it easy to
create professional
software
demonstrations
as Java/HTML or
Flash animations.
Turbo Demo keeps
generated movie
demos small. The
demos can be
played online to
sell products and
services, used for
technical support
or training, or
incorporated
into Windows Help files to
provide animated, interactive
user support. They can also be
played on computers with no
Internet connection, making
them useful as classroom
learning tools or on-site sales
presentations.
Using the programs intuitive
Wizard, you can quickly
capture screens, import
pictures in all of the popular
formats, annotate images with
comic-strip style balloons,
and include hot spots that let
users interact with the demos.
In as little as 15 minutes, you
can create a professional demo
of a software program that

used to take many days and


many thousands of dollars to
produce.
Turbo Demos presentations
require about 100 KB per
minute. There are no plugins required to view the
demos, and all major Web
browsers can play them in
real-time, online, or offline.
The generated demos can be
customized, from including
the users company logo, to
a customized color scheme,
navigation buttons, and
complete program skin.
A fully-functional, 30day trial version may be
downloaded from the company

Web site. Turbo Demo runs


under Windows 95/98/ME/
NT4/2000/XP, and the demos
are platform independent. This
product is available in English,
Spanish, German, and French.
Bernard D&G
Price: Special introductory price available in
October 2002 for Delphi Informant readers.
Turbo Demo Standard 2.0 at US$490 instead
of US$890 and Turbo Demo Professional
2.0 at US$790 instead of US$1,390
for a single-user license, with multi-seat
and corporate licenses available. To take
advantage of this limited special offer,
e-mail: [email protected].
Contact: [email protected]
Web Site: www.turbodemo.com

Kylix 3 Brings Rapid C++ Development to Linux Developers

ISBN: 0-596-00249-1
Cover Price: US$29.95
(616 pages)
www.oreilly.com

3 October 2002 Delphi Informant Magazine

Borland announced Kylix


3, the first Rapid Application
Development (RAD) solution
for C++ and Delphi on the
Linux operating system.
For the first time, Linux
developers can quickly create
GUI, database, Web, and Web
Services applications in C++,
the preferred programming
language for the Linux
operating system/platform.
With Kylix 3, enterprises
can affordably build highperformance Linux applications
with existing C++ programming
skills at high speeds.
Kylix 3 offers the first twoway visual design of C++
applications. Until now, Linux

developers have been required


to use non-visual integrated
development environments
that are better suited to operating-system and device-driver
development.
Kylix 3 improves the speed
and quality of componentbased development and gives
developers the flexibility to
build applications in C++
or Delphi for the platform
of their choice. Developers
can combine Kylix 3 with
C++Builder or Delphi to create
single-source, cross-platform
Linux/Windows applications.
Likewise, Borland continues
to build on its commitment to
Web Services with expanded

support for Web Services


development and compliance
with the latest Web Services
standards built into Kylix 3.
Borland Kylix 3 will be available in three editions: Kylix 3
Enterprise, Kylix 3 Professional,
and Kylix 3 Open Edition.
Borland Software Corporation
Price: For new users, Kylix 3 Enterprise
edition is US$1,999 and Kylix 3
Professional edition is US$249. Special
pricing is available for existing Kylix,
C++Builder, and Delphi users. Kylix
3 Open Edition will be available for
free download from www.borland.com/
products/downloads.
Contact: (800) 632-2864
Web Site: www.borland.com

Delphi
T O O L S

New Products
and Solutions

Book Picks
Web Services Essentials
Ethan Cerami
OReilly

ASTA Technology Group Releases ASTA 3


ASTA 3 by ASTA Technology
Group allows Delphi developers the ability to build secure
thin-client applications that run
over the Internet and also support
Palm, Windows CE, Java, C#, and
other non-VCL clients. ASTA 3
is a mature product built on the
ASTA foundation started in 1998
with ASTA 1. This release has
more features and documentation
with a 700+ page PDF manual
and documented tutorials.
ASTA 3s highlights include a
new server format that allows
for servers to run as NT services
or normal EXEs, remote server
administration, a new smartthreading model that combines
pooled database and persistent
sessions, built-in strong encryption options, a formal instant
messaging API, and enhanced
http support to get through any
firewall with built-in stateless
authentication.

Raize Software Introduces


DropMaster
ISBN: 0-596-00224-6
Cover Price: US$29.95
(288 pages)
www.oreilly.com

Borland Kylix Developers Guide


Charles Calvert, et al.
SAMS

ISBN: 0-672-32060-6
Cover Price: US$59.99
(700 pages, CD-ROM)
www.samspublishing.com

Raize Software released


DropMaster, a native VCL component set that encapsulates the complexity of OLE inter-application
drag-and-drop within four easyto-use components. DropMaster
supports dragging text, images, and
even custom data formats to other
applications. The DropMaster
components enable a Delphi or
C++Builder application to accept
plain text, rich text, file lists, URL
links, images, and custom data
formats from other applications
that support OLE drag-and-drop.
There are special events available
that allow developers to customize
the drag-and-drop process.
DropMaster also comes with
more than 40 example projects.
Examples include accepting
messages dragged from Outlook,
Outlook Express, Netscape, and
Eudora; dragging URLs from
an application to a browser or
the desktop; dragging multiple
custom formats; and dragging
JPEG images.
Raize Software, Inc.
Price: US$99
Contact: (630) 717-7217
Web Site: http://www.raize.com

4 October 2002 Delphi Informant Magazine

ASTA 3 takes the burden


off of developers and helps
build cross-platform, thinclient, zero administration,
secure, and scalable database
applications that run over
the Internet. Developers
should concentrate on coding
applications rather than
dealing with low-level issues

like threading, encryption,


or writing SQL update
statements. With ASTA,
you set properties instead of
implementing with code.
ASTA Technology Group, Inc.
Price: Starting at US$199
Contact: [email protected]
Web Site: www.astatech.com

Software By Martin Announces DLite


DLite from Software By
Martin is a Delphi companion
tool that allows you to easily
edit, compile, and run your
projects. DLite gives you access
to the DCC32 command-line
compiler to quickly compile
projects using any installed version of Delphi. The editor has
the familiar look and feel of the
Delphi IDE.
DLite can switch between any
installed version of Delphi with
a simple keystroke combination, find text in files, compile
or recompile test versions to

give to your quality assurance


department, function as your
everyday editor, build batch files
from within the DLite IDE,
and modify non-related project
files (like a SQL script file) that
would normally cause the IDE
to recompile the project.
DLite supports Delphi 2
through 6. A free 30-day evaluation is available for download.
Software By Martin
Price: Starting at US$18
Contact: [email protected]
Web Site: www.softwarebymartin.com

First Look
Delphi 7 Studio

By Cary Jensen, Ph.D.

Delphi 7 Studio
Borlands Lucky Number

ince Delphis release in February of 1995, Borland has been relatively consistent
in shipping a new version of Delphi every 12 to 16 months. The notable exception
to this was Delphi 6, which was released almost two years after Delphi 5, in large
part because of the resources Borland committed to creating Kylix, Delphis Linuxenvironment cousin. With Delphi 7 Studio, Borland is back on track. The products
anticipated September release comes 15 months after Delphi 6, despite two releases
of Kylix in the past year.
In this first look, Ill take Delphi 7 Studio out
for a test drive, to examine its updated and new
features. This article is based on a beta version
of Delphi 7 Studio. The actual shipping product
may differ from the copy I reviewed. Ive tried
to report which features appear in which version
of the product, but you may find it helpful to
refer to the product-feature matrix available from
Borlands Delphi site at http://www.borland.com/
delphi/index.html.

includes the Delphi-language version of Kylix


3. Now, you can create Windows and Linux
executables from the same box.

In short, Delphi 7 Studio is an important step


toward Borlands goal of 100 percent .NET
compliance while maintaining backwards
compatibility to your existing applications and
environments. Delphi 7 Studio continues Delphis long-standing status as Borlands premier
development tool for creating cross-platform
applications quickly, as well as applications that
communicate across the Internet.

And, speaking of name changes, Borland is


officially referring to the language you use with
Delphi 7 Studio as the Delphi language. Even
the venerable Object Pascal Language Guide is
now called the Delphi Language Guide. Once
again, this appears to be in anticipation of
Delphis .NET support, in which you will be
able to build true .NET managed assemblies
using the Delphi language.

The Name Game

Aside from the name changes, a lot is new inside


Delphi 7 Studio. Changes include minor enhancements to the IDE, new and improved components,
database updates, a great database-reporting tool,
improved compiler and debugging support, and
expanded Web tools. Ill describe each of these in
the following sections.

You must have noticed the change in name. Its


no longer simply Delphi 7, but Delphi 7 Studio.
The Delphi of old was a Windows-platform
development environment. Delphi 7 Studio is
more than that. Not only does it include the
Windows compiler we all know and love, it also
5 October 2002 Delphi Informant Magazine

Delphi 7 Studio also includes a preview version


of Borlands new .NET compiler, and a .NET
version of the Visual Component Library (VCL)
is not far off. This is great news; Borland is
clearly on its way to fulfilling its promise of
platform agnosticism.

First Look
displays a list of symbols, such as variables,
methods, and properties that are in scope
of your active cursor, permitting you to
select from the list instead of typing the
entire symbol reference. With Delphi 7
Studio, you can customize the colors of
the various symbol elements that appear
in this list.

Figure 1: Enhancements to the Component palette.

Updated IDE
The IDE has
been tweaked
to incorporate a
small but welcome
collection of new
features. One of
the more obvious
is the updated
look, incorporating
XP-style colors in
its menus. Another
little update
will please those
developers who are
tired of working
with cramped
Component
Figure 2: The Components dialog box.
palettes: A new
drop-down list
appears when a page of the Component palette contains more
components than can be displayed at one time (see Figure 1).
The components that otherwise would not be visible on the Indy
Clients page of the Component palette are displayed using this
drop-down list.
Another updated feature is available in the Components dialog box,
shown in Figure 2. You display this dialog box by selecting View |
Component List from Delphis main menu. Now, you can select a
range of consecutive components by selecting the component at the
beginning of the range and then S-clicking the last one in the
range. Or, you can select or deselect two or more non-contiguous
components by C-clicking them. After selecting two or more
components from the component list, clicking Add to form places
those components on the current form, frame, or data module.
Some of the biggest and most welcome additions to the IDE
are found in the Code Insight feature of the Code editor. Lets
consider code completion first. The code-completion feature
6 October 2002 Delphi Informant Magazine

Code completion has also been expanded


to provide this assistance with HTML,
wireless markup language (WML), extensible
hypertext markup language (XHTML), and
extensible stylesheet language (XSL). For
example, with an HTML file (a text file with
an htm or html file extension) open in the
editor, typing < engages code completion.
The displayed list is context-sensitive,
including only those XHTML elements that
are valid from the position of your cursor.
For example, if you have an empty HTML
file open in the editor and you press <,
code completion displays only the HTML
tag (because a well-formed HTML file
must begin and end with this tag). After completing the beginning
HTML element, <HTML>, code completion will add the required
end element, </HTML>, positioning your cursor between the two. In
fact, because a <BODY> element is typically the next element, code
completion inserts that start tag as well.
In addition to offering assistance with HTML, XHTML, XSL,
and WML elements, code completion provides completion for
attributes. For example, with an HTML file open in the editor, if
you enter a < and then select A (anchor) from the displayed list,
code completion will display a list of the available attributes for
the anchor tag, as shown in Figure 3. In short, code completion
makes Delphis editor a handy tool for quickly creating Web
documents or segments for use in your Web-based applications.
Another area in which Code Insight has been improved is its
code-templates feature. This feature, of which few developers are
aware, permits you to save code segments that can be inserted
quickly into the editor at the position of your cursor by pressing
CJ. Now, code templates have been updated to include
template categories. For example, you can create XML templates,
HTML templates, Pascal templates, or even C# templates.
Depending on the type of source file youre editing in the Code
editor, pressing CJ will permit the insertion of templates
appropriate for that file type.
In earlier versions of Delphi, code templates were added and
modified using the Code Insight page of the Editor Properties dialog
box (select Tools | Editor Options to display this dialog box). In Delphi
7 Studio, a new Source Options page (see Figure 4) has been added
to the Editor Properties dialog box. You use this page to control how
the editor behaves for different source-file types. (You can even add
your own custom source-file types.) You add, modify, or delete code
templates for the selected source-file type by clicking the Edit Code
Templates button on the Source Options page. Figure 5 displays the
Code Templates dialog box for HTML templates.
The final major update to Code Insight is the addition of a new
open-tools API interface, named IATOCodeCompletionServices,

First Look
licenses to deploy applications that use
DataSnap components to communicate
between two or more machines.

Figure 3: Code completion for HTML attributes.

for creating custom code-completion managers. Few developers


will be interested in writing a custom code-completion manager,
but its nice to have this feature opened, permitting third-party
vendors to create these valuable additions to Delphis editor.

Database Updates
With this release, Borland is officially deprecating Borland SQL
Links for Windows, the native-language drivers that Borland
Database Engine (BDE) users can use to access remote database
servers. In short, Borland will no longer update its existing SQL
Links for Windows drivers. This will affect you only if you are
using SQL Links for Windows now and want to upgrade your
database server to a version not supported by the current version
of SQL Links for Windows. If that happens, youll have to fall
back on one of Delphis other client-server data options, such as
dbExpress, ActiveX Data Objects (ADO), InterBase Express, or a
third-party solution.
As for additions, there are several associated with database development. For starters, Delphi 7 Studio now includes several updated
dbExpress drivers and one new driver. The updated drivers are for
DB2 7.2, Informix SE, InterBase 6.5, MySQL 3.23.49, and Oracle
9i. The new dbExpress driver is for SQL Server 2000.
One of the components that relies on dbExpress drivers, SQLClientDataSet, is being replaced by a new, lightweight component
named SimpleDataSet. (SQLClientDataSet is still available, but
does not appear on the Component palette by default.) To put
it plainly, the SimpleDataSet component has a better design
than SQLClientDataSet, and developers who dont need all the
features the combination of SQLConnection, SQLDataSet,
DataSetProvider, and ClientDataSet offers will be pleased with
this new component.
On the DataSnap front, Borland has added a new connection
component named SOAPConnection. Using this component,
DataSnap client applications can use DataSnap servers
implemented as a Web Service.
Those who use the Enterprise and Architect versions of Delphi
7 Studio will be pleased to learn that these products include a
full deployment license for DataSnap application servers. With
these versions, it will no longer be necessary to obtain additional
7 October 2002 Delphi Informant Magazine

And thats not all. Of all the database


features added to Delphi 7 Studio, the
best is that Rave Reports Borland Edition
is now included. For years, database
developers have complained about Delphis
reporting options, which included the
much-maligned ReportSmith as well as
QuickReport, making reporting add-ons
a must-have for most database developers.
Now, one of the most popular reporting
add-ons, Rave Reports from Nevrona
Designs, comes with Delphi and the
included Kylix. This gives Delphi database
developers a valuable, cross-platform
reporting solution that exceeds anything
Borland has provided in the past.

Enhancements in Debugging and Compiling


Youll find a number of additions in Delphi 7 Studios compiling
and debugging options. For those of you who will be moving to
.NET, you will be pleased to learn that the compiler can generate
.NET compatibility warnings, enabling developers to steer clear
of code thats not allowed in managed assemblies.
Compiler warnings and hints are also easier to manage in
Delphi 7 Studio. The Project Options dialog box contains a new
Compiler Messages page (shown in Figure 6) which you can
use to suppress all hints or warnings without having to resort
to compiler directives. This page also permits you to suppress
individual compiler messages if you wish.
For those hints or warnings that appear after compiling a unit,
additional information is now available online. Select a hint or
warning in the Messages window and select View | Additional
Message Info to download information from Borlands Web site. As
you might expect, this feature requires a connection to the Internet.
Another feature update thats useful for debugging is the distinction
between module-loading messages and process messages in the event
log. Previously, module-loading messages were grouped with process
messages. Now, process messages and module-loading messages can
be independently included or excluded from the event log.
Another debugger enhancement is the ability to color-code
messages that appear in the event log. Each event-log message
category can be assigned a different foreground and background
color, permitting you to distinguish between message types
quickly when viewing the event log. Both this feature and control
of module-loading messages are configured from the Event Log
page of the Debugger Options dialog box, shown in Figure 7.
The Watches window also has been updated for those developers
who make extensive use of watches. Now, watches can be assigned
a group name. The Watches window will display a separate page
for each group. To see the current value of a watched expression,
select the tab associated with the group to which it belongs.

New Controls, Classes, and Functions


A number of new components, classes, and run-time library

First Look

Figure 4: The Source Options page of the Editor Properties


dialog box.

Figure 5: The Code Templates dialog box for HTML templates.

variables and functions are introduced in Delphi 7 Studio. For


example, the Win32 page of Delphi 7 Studio includes a new
combo-box component, ComboBoxEx. ComboBoxEx is a combo
box with auto-complete capability.

manipulation, the functions LeftBStr, RightBStr, and MidBStr


have been added.

Delphis visual controls have been updated to make use of XP


themes (this feature is only available on machines running XP
or later). Simply deploy an appropriate manifest file in the same
directory as your executable, and Delphi takes care of the rest,
automatically adjusting the look of your application as the user
selects new XP themes.
Delphi 7 Studio also includes three new standard data-set actions
that can be used with ClientDataSets. For developers who use
action lists, these actions make applying, reverting, and canceling
changes to ClientDataSets effortless. Note that these actions are
used only with ClientDataSets that get their data from a data-set
provider. ClientDataSets that get their data from a local file can
already save their data automatically (although an explicit call to
SaveToFile is recommended).
A number of new global variables have been added to the Variants
unit to influence the behavior of some of the variant-related
run-time library functions. These include NullEqualityRule,
NullMagnitudeRule, NullStrictConvert, NullAsStringValue,
PackVarCreation, and RangeCheckVariants. Similarly, complex
number support from the VarCmplx unit has been expanded
with the addition of the VarComplexLog2, VarComplexLog10,
VarComplexLogN, VarComplexTimesImaginary, and
VarComplexTimesReal functions.
Also, multi-byte character set (MBCS) support has been added
to a number of string functions. The LeftStr, RightStr, and
MidStr functions in the StrUtils unit have been overloaded to
support MBCS. Because this might break some code that relied
on these functions, AnsiLeftStr, AnsiRightStr, and AnsiMidStr
were introduced to provide the same features as LeftStr, RightStr,
and MidStr, without the overloading. Similarly, for those people
who used LeftStr, RightStr, and MidStr to perform byte-level
8 October 2002 Delphi Informant Magazine

Two new conversion functions have been added to the SysUtils


unit. TryFloatToCurr and TryFloatToDateTime return True if the
conversion can be performed (and provide the converted value in
an out parameter) and False if the conversion isnt possible.
The TStrings class has two new properties: ValueFromIndex and
NameValueSeparator. These properties are handy when your TStrings
descendant holds name-value pairs. ValueFromIndex permits you to
assign the value part of a name-value pair based on its ordinal position
within the list. NameValueSeparator permits you to define the separator
to use for name-value pairs (the default is the equals sign).
If you use the Internet Direct (Indy) socket components, youll
be interested to learn that there are a number of new Indy-related
components. These appear on two new pages of the Component
palette, Indy Intercepts and Indy I/O Handlers. The components
that appear on these pages provide a mechanism for managing the
binding of the socket connection, and the additional processing of
data being transferred over the socket connection.
Other new components that appear on the Component palette
include those that are associated with IntraWeb and Rave Reports.
These tools are discussed elsewhere in this article.

Did Someone Say .NET?


Delphi 7 Studio is not the Delphi for .NET that Borland talked
about last May at the 13th annual Borland Conference in
Anaheim, but its an important evolutionary product. In addition
to including specialized compiler hints and warnings that will
help you create projects that are easier to move to .NET, Delphi
7 Studio includes a preview version of Borlands new Common
Intermediate Language (CIL) compiler, as well as the ability to
interact with .NET components.
When Delphi for .NET ships, Borlands CIL will be able to compile
your applications into managed assemblies, which will be able

First Look

Figure 6: The Compiler Messages page of the Project Options


dialog box.

Figure 7: The Event Log page of the Debugger Options


dialog box.

to interact seamlessly with code compiled with any other CILcompliant compiler. A beta edition of the .NET version of the runtime library is included with the preview compiler. Borland hopes
to ship a .NET version of the VCL sometime later this year, taking
Delphi developers one step closer to 100 percent .NET compatibility.

As for Web Services, a large number of improvements and


additions keep Borlands Web Services support at the leading
edge of distributed computing. These features include a
Universal Description Discovery and Integration (UDDI)
browser to locate registered Web Services visually and import
them, automatic UDDI client fail-over support, support for
binary attachments, as well as support for the Web Services
Inspection Language.

For the time being, Delphi can import existing .NET classes as
COM objects. Similarly, managed assemblies can use Delphi 7
compiled objects, also through COM interfaces.

Dynamic Web Sites and Web Services


Heres another place where Delphi 7 Studio includes a number of
valuable updates. For starters, Delphi 7 Studio includes the awardwinning IntraWeb Web-development tool from AToZed Software,
with a limited-feature version shipping in the Professional edition,
and the full version shipping with Delphi 7 Studio Enterprise and
Architect. IntraWeb is also found in the Delphi-language Kylix
version included in Delphi 7 Studio, making it a solid tool for
building cross-platform Web applications.
IntraWeb is Web development the Delphi way. Using special wizards
and components, you design your Web site by using the same RAD
techniques you use to design regular forms. The difference is that
when you compile your project, HTML and JavaScript are generated
into HTML files that form the basis of your Web application. These
applications can be deployed for use as Apache or Internet Server
application programming interface (ISAPI) Web server extensions.
Its also possible to use IntraWeb to design individual Web pages
visually. Then, these Web pages can be incorporated into existing
WebBroker and WebSnap applications.
In the version of IntraWeb that ships with the Enterprise and
Architect versions of Delphi 7 Studio, IntraWeb applications can
be deployed as stand-alone Web servers. This full version also
includes automatic state management, further simplifying one of
the more time-consuming aspects of Web development. Users of
Delphi 7 Studio Professional can upgrade to the full version of
IntraWeb for an upgrade fee. To learn more about this, visit
http://www.AToZedSoftware.com.
9 October 2002 Delphi Informant Magazine

And Professional edition users are going to love this news: Full
Web Service support for building clients and servers can be found
in Delphi 7 Studio Professional. This is true for both the Delphi
and Kylix products. Thank you, Borland!

Which Version Is Best for You?


There are four versions of Delphi 7 Studio: Personal, Professional,
Enterprise, and Architect. The Personal edition is designed to
give developers an inexpensive way to kick the tires, if you will.
It lacks a version of Kylix, database capabilities, and Web tools;
but it includes the basic Windows components, run-time library,
Code editor, compiler, and debugger. Its cheap, but its not for
the serious developer.
Delphi 7 Studio Professional is the workhorse edition of Delphi.
It comes with Kylix 3 Professional IDE for the Delphi language;
an impressive collection of database-related components; source
code for most of the VCL, component library for Linux (CLX),
and run-time library; and multiple solutions for building Web
and Internet applications. Its the most popular version of
Delphi, and it provides a solid combination of capabilities for a
reasonable price.
For developers needing to create client-server and distributed
applications, Delphi 7 Studio Enterprise edition provides a powerful
blend of features. This edition includes everything in the Professional
edition, plus much more. For example, the Enterprise edition includes
the Kylix 3 Enterprise IDE for the Delphi language, the complete
version of IntraWeb, DataSnap for building multi-tier applications,
and a full deployment license for DataSnap. It also includes XML

First Look
document object model (DOM) support; XML transformations;
TeamSource for source-code version control; language-translation
tools; and ModelMaker, a unified modeling language (UML) design
tool. (See the August 2002 issue of Delphi Informant Magazine for a
recent review of ModelMaker.) The Enterprise edition of Delphi is the
second-most-popular version, and it provides a high-end solution for
almost all your development needs.
With Delphi 7 Studio, Borland is releasing a new, high-end
edition called Delphi 7 Studio Architect. It contains everything in
the Enterprise edition, plus Bold for Delphi. Bold for Delphi is a
development framework that generates true business objects based
on UML class diagrams. (For a recent review of Bold for Delphi,
see the July 2002 issue of Delphi Informant Magazine.) With
ModelMaker, Bold for Delphi gives Delphi 7 Studio Architect
users a design-to-deployment solution for building applications
based on UML class diagrams. This approach to development
is commonly called model-driven architecture (MDA), and it
represents state-of-the-art software development.

Conclusion
Delphi 7 Studio is an important new release. With many updates
and enhancements, including support for XP themes and .NETrelated compiler warnings, this release is going to be hard to resist.
I think Simon Thornhill, vice president and general manager of
the RAD Products Group at Borland, said it best: With Delphi
7 Studio, Borland continues to give developers the means to
begin moving to the future of .NET without abandoning their
past. Keep this in mind the next time you speak to a Visual Basic
developer who is going to have to relearn VB or adopt a completely
new language, such as C# or even Delphi. Windows and Linux
executables, Web Service extensions, Web Services, and .NET soon
will all be available using the Delphi language. Delphi continues to
be the right choice for modern software development.

Cary Jensen is president of Jensen Data Systems, Inc., a Texas-based training


and consulting company that won the 2002 Delphi Informant Magazine
Readers Choice Award for Best Training. He is the author and presenter for
Delphi Developer Days (http://www.DelphiDeveloperDays.com), an information-packed Delphi seminar series that tours North America and Europe.
Cary is also an award-winning, best-selling co-author of 18 books, including
Building Kylix Applications (Osborne/McGraw-Hill, 2001), Oracle JDeveloper
(Oracle Press, 1999), JBuilder Essentials (Osborne/McGraw-Hill, 1998), and
Delphi In Depth (Osborne/McGraw-Hill, 1996). For information about on-site
training and consulting, contact Cary at [email protected] or
visit his Web site at http://www.JensenDataSystems.com.

10 October 2002 Delphi Informant Magazine

Columns & Rows


Databases / Transactions / SQL

By Bill Todd

Choosing a Database
Checklists for Selecting the Best Database for a Task

common question among Delphi developers is: Which database should I use?
It would be an easier question to answer if there werent so many choices. This
article examines how to determine which database is right for you and your application. At the end of this article I hope youll have a checklist that you can use to make
a good choice based on your needs.
I was going to write this article without mentioning any database by name, but found it too
difficult. When I do mention a particular product,
however, its as an example only. If you look at the
significant players in all segments of the database
market, there are no bad products. Instead, there
is a variety of products with different features. A
particular feature may be an advantage for one type
of application and a disadvantage for another.

11 October 2002 Delphi Informant Magazine

In the event of a hardware or software failure,


will it be necessary to recover up to the instant
of the failure?
Is database replication required?
Will data access consist solely of inserts and
updates, or will there be queries?
How complex will the queries be, and how
much data will they scan?

The Basics

The answers to these questions should prepare you


for this next set of questions.

Different databases have different strengths and


weaknesses, so it stands to reason that your decision will involve matching the databases capabilities to your applications requirements. Necessarily,
that means you cannot choose a database until you
have defined your requirements, so lets start with a
few basic questions:
Is the application for a single user or multiple
users?
How many simultaneous users will you have?
How many simultaneous users will you have in
10 years?
How big will your database be?
How big will your database be in 10 years?
How many hours per day will the database
be in use?

Does the database support multiple simultaneous users? If the application is multi-user, you
should eliminate all desktop databases from your
list of candidates and focus on database servers. Client-server applications reduce network
traffic and provide better performance with
many simultaneous users. Most importantly,
the chances of a system crash causing a corrupt
database are much lower with a database server,
particularly if the PC that hosts the database software is dedicated to that task, has an
uninterruptible power supply, and is physically
secure. SQL database servers are available in
every price range, including free, so you have no
reason not to use one.

Columns & Rows


What is the database architecture? The vast majority of database
servers today use the relational model, but its not the only choice.
Perhaps an object database, a hierarchical database, a hash-based
database, an associative database, or some other architecture would
be better for your application. Relational databases are the most
common, though. So, for this article, I will assume youre choosing a
relational database.
On what operating systems and hardware platforms does the
database run? You need to consider the number of users and database
size now, and how they will change in the future. If the database
size and number of users both are small, a database that runs on a
single Windows PC with a single processor may be all you need.
If you need to support hundreds or thousands of users, youll need
a product that runs on more powerful hardware. If you expect the
load on your database system to increase over time, you need to
look at database servers that support multiple operating systems and
hardware platforms. That way, you can start with a single PC for
your database server and scale up to a multi-processor PC, a cluster
of servers, a large Sun system, or even an IBM mainframe, without
having to change your software.
If your database will be hundreds of gigabytes or even terabytes
in size and will support many users, youll want the ability
to spread the database across multiple drives, and the ability to
control which database objects reside on which drives, in order
to maximize performance. This level of ease in configuring the
database provides the best performance and hardware utilization.
However, it also means youll be working with a very complex
database that requires a highly skilled database administrator to
install, configure, and maintain. If your database must be online
24 hours a day, seven days a week, you may need backup servers
and automatic fail-over, so users dont have any interruption of
service if a server fails.

Locking vs. Versioning


Which concurrency control architecture does the database use?
There are two models for controlling concurrent access to data in a
database: the locking model and the versioning model. The principal
advantage of the versioning model is that readers never block writers.
Put another way, if one user is executing a long-running query that
selects and analyzes data, and this query requires a consistent view of
the data as it was at one moment in time, other users will be able to
update rows used by this query while the query is running. The same
isnt true for locking databases.
What is the smallest amount of data that can be locked? Lock
granularity is a measurement of that amount. Most databases today
support row-level locking. However, you may encounter databases
that offer only page-level locks as their most granular. This means
that instead of locking one row, an entire page of rows must be
locked, which reduces concurrent access to data. Page locks are a
particular problem in small tables that are accessed by many users.
The chances are high that two users may need to update rows on the
same page at the same time.
Does the database use automatic lock escalation? If so, under what
conditions? Its great if the database youre considering supports
row-level locking, but what happens when a user reads or updates a
large number of rows? Because the overhead of maintaining a large
number of locks can hurt performance, some databases will escalate
to less granular locks to reduce the number of locks the server must
maintain. This means you suddenly could find that the database was
12 October 2002 Delphi Informant Magazine

Character

Description

Atomicity

All changes that are part of a transaction


succeed or fail as a single unit, no matter
how many rows and tables are involved.

Consistency

The database will always be left in a consistent state. If the database server crashes, all
active transactions will roll back automatically when the server restarts, so the state of
the database will be as it was before any of
the active transactions began.

Isolation

Changes made by uncommitted transactions cannot be seen by other users.

Durability

Once a transaction is committed, all


changes that are part of that transaction
become a permanent part of the database
and cannot be lost.

Figure 1: Characteristics of a transaction; the ACID test.

locking pages or even entire tables not individual rows. Obviously, this can severely limit concurrent access to data.
What lock types does the database use, and how do they coexist? When considering a locking-model database, it is critical to
understand how locks conflict with one another. What happens
when one user places one type of lock on a row, page, or table,
and a second user tries to place the same or a different type
of lock on the same object? The best way to understand lock
conflicts is with a lock-conflict table that shows all the lock types
down the left side and across the top. Each cell in this grid would
represent an attempt to place two locks simultaneously, and the
cells would contain explanations of what happens when the locks
are attempted. This may sound like a trivial task, but it isnt. For
example, the last time I looked at IBMs DB2, it had 12 different lock types. That means you have 144 different combinations
of locks to consider! Even worse, if you want to understand what
conflicts you may encounter, you need to understand which lock
types your application will use at what times.

Transactions
Does the database support transactions? Be careful here. Some
databases claim to support transactions when they really dont. A true
transaction must exhibit four characteristics, sometimes called the
ACID test (see Figure 1).
What transaction-isolation levels does the database support? Again,
you need to be careful in evaluating transaction-isolation levels. Some
database vendors use transaction-isolation levels that dont exactly
match those defined by the American National Standards Institute
(ANSI). For example, InterBases snapshot transaction-isolation level
provides the same consistent view of the data as ANSI serializable
isolation, but doesnt guarantee that a sequential order of execution
of concurrent transactions will produce the same result. To add to
the confusion, ANSI-standard repeatable-read isolation may not
give you the behavior you expect. Figure 2 shows the ANSI-standard
transaction-isolation levels.
What happens when the transaction log gets full?
If the database uses a transaction log, what happens if it gets full?
Will the transaction log expand dynamically? If not, what do you
have to do to increase the size of the transaction log?
Can the log size be increased while the database is in use?

Columns & Rows


Isolation Level

Description

Read uncommitted

Also known as dirty-read isolation. Other


transactions can see changes made by a
transaction that has not committed. Very
few databases support read-uncommitted
isolation because it violates the isolation rule
for transactions and lets other transactions
see and use values that can vanish from the
database if the transaction rolls back.

Read committed

Other transactions can see changes made


by a transaction as soon as it commits.

Repeatable read

If a transaction using repeatable-read


isolation selects a set of rows and then
later selects the same set of rows, it will
see exactly the same values in each field
of each row that it read the first time.
Note, however, that if new rows have been
inserted into the database since the first
SELECT, and those new rows satisfy the
WHERE clause, they will be returned by the
second SELECT. The read is repeatable only
for those rows that were read the first time.

Serializable

Serializable isolation guarantees that if you


execute a SELECT more than once during
the course of the transaction, youll get
exactly the same result set every time. It
also guarantees that if multiple serializable
transactions execute concurrently, some
sequential order of execution will produce
the same results.

Figure 2: ANSI-standard transaction-isolation levels.

Can you place the transaction log on a separate drive from the
database files so that both the database and the log will not be
lost if a single hard drive fails?

Can transactions span more than one database? Some databases


allow a single transaction to make changes in more than one database; others dont.
Does the database have a method of generating sequential
numbers? Its common to store data in relational databases that
dont contain a natural primary key. The most common solution
is to use a sequential number as a surrogate primary key. This is
such a common requirement that I would not consider a database
that doesnt have a way to generate sequential numbers safely in a
multi-user environment. Some databases have a special field type
to do this. Other databases provide another mechanism, such
as the generators used by InterBase. The database also should
provide a way to change the next value that will be generated, so
the value can be reset after testing or for other reasons. You also
may want the option to disable automatic generation of numbers
when importing data that already has a primary key assigned to
each row.

What functions are included in the SQL implementation?


A complete library of date, time, math, and string functions
enhances the power of SQL and the databases stored procedure
and trigger language. This dramatically can reduce the amount of
data manipulation that must be programmed into client applications. If you need to do statistical analysis, look for those functions, as well.
Can you write your own functions? Its also very valuable to be
able to write your own functions and call them in SQL statements, stored procedures, and triggers. This makes it easy for you
to add specialized functions that arent available in the databases
standard function library.
Can SELECT statements span databases? Not all databases
support the ability to join tables from two or more databases in
a SELECT statement. If you need this capability, make sure the
databases youre considering have it.
How does the query optimizer work? The power and sophistication of the query optimizer can have a huge impact on the
performance of your application and can save you a lot of time
you might spend otherwise on trying to improve the performance
of your queries. Ask yourself these questions:
Does the optimizer look for the lowest-cost solution?
Does the optimizer estimate table statistics, or does the system
track them?
How and when are the table statistics updated?
Does the optimizer consider hardware cost?
Is the optimizer aware of the data, such as the minimum,
maximum, average, and distribution of values?
Are there types of queries that are not optimized, such as
multiple outer joins?
What features are provided to improve performance? See if the
database youre looking at supports the following feature: clustered tables, clustered index, shared procedure cache, shared data
cache, and write caching.
Clustered tables place selected tables near each other on disk for
faster access. A clustered index stores the rows in sorted order, so
the data rows are the leaf nodes of the index. This makes the index
smaller and the searches faster. Shared-procedure caches and shareddata caches let multiple users share the same cache for improved
memory use and performance. Write caching improves performance
by reducing the number of write operations at the expense of safety.
If you use write caching and the server crashes, everything in the
cache will be lost, and it is likely that the database will be corrupt.

SQL Implementation

Does the database support stored procedures? Stored procedures


are written in the databases procedure and trigger programming
language. These procedures can be called by client applications,
triggers, or other stored procedures. Check for the following
features:
Do stored procedures support all data types?
Can you create statements on the fly and execute them?
Can you treat a stored procedure that returns a result set as a
table in SQL statements?

What level of compliance to the ANSI SQL standard does the


database provide? If you require particular features of the ANSI SQL
standard, make sure the database supports them. If you need any
non-standard features, such as full-text search or cross tabulation,
make sure the products you consider support them.

Does the database support triggers? Triggers, like stored procedures, are functions written in the databases procedure and trigger language. But triggers are executed automatically when data is
inserted, deleted, or updated. Check for features such as:

13 October 2002 Delphi Informant Magazine

Columns & Rows

Can you attach multiple triggers to a single event?


Can you specify the order in which multiple triggers fire?
Can you choose whether the trigger fires before or after the event?
Can triggers fire events to notify the client of some condition?

Triggers can execute when a row in a table is inserted, deleted,


or updated. The ability to write more than one trigger fired
by the same event means you can write and test many small,
simple, single-function triggers instead of one large one. It makes
development, testing, and maintenance easier. If you can attach
multiple triggers to a single event, its critical that you be able to
specify the order in which they execute, in case the triggers are
dependent on each other.
Some databases dont allow you to have triggers that fire both before
and after the event. This is very limiting. For example, one of the most
common uses of triggers is to validate data before updates or inserts
are allowed to proceed and block the operation if the data is not valid.
Events allow the database to notify the client that something has
happened, without the overhead of having the client application query the database frequently. For example, you might want
a sales-administration system notified if a customer cancels an
order. Another example would be to notify the purchasing application if the inventory level of a product falls below a set amount.
Can you change metadata while the database is online? Do you
have to shut down the database before you can add new tables,
add new columns to existing tables, or make other changes to the
database? If you can make metadata changes while the database is
online, when will connected users see the changes?
Does the database support declarative referential integrity?
You can waste a lot of time writing triggers to enforce referential
integrity or, worse, enforcing it in your client applications. The
database should allow you to declare referential-integrity relationships between tables and enforce them automatically. It should
also let you specify whether you want deletes cascaded to the child
table and whether you want primary key updates to be cascaded
to the child tables automatically.

Hardware Failure
Does the database support replication? If the database supports
replication, does it support synchronous replication, asynchronous replication, or both? Synchronous replication updates the
target database instantly when the source database changes. If
synchronous replication is used, what happens if the connection
to the target database is lost? Will you be unable to make any
changes to the source database? Will changes be logged and replicated when the target database comes back online?
Asynchronous replication logs changes in the source database and replicates them to the target database later. The disadvantage of asynchronous replication is that the target database is always behind the source
database by some period of time. The advantage is that you dont need
a permanent connection between the source and target databases.
Note that for databases that dont support a separate transaction
log, replication may be the only way to provide up-to-the-minute
recovery if a catastrophic hardware failure occurs.
What options are available for backing up the database?
Can the database be backed up while its in use?
14 October 2002 Delphi Informant Magazine

Are differential or incremental backups supported, or must


you back up the entire database? A differential backup backs
up all changes since the last full backup. An incremental
backup backs up all changes since the last backup of any kind.
Does the database maintain a separate transaction log?
Can the transaction log be backed up while the database is online?
Does the transaction log allow differential or incremental backups?

How do you recover from a server crash or hardware failure?


If the database server crashes, all transactions that were active at
the time of the crash must be rolled back automatically when the
database server restarts, to leave the database in a consistent state.
How is this accomplished? If it requires processing the transaction log, how long will this take with the maximum number of
transactions you expect to have open at one time?
This is one area in which versioning databases have an advantage
when compared with locking-model databases. Nothing needs
to be changed in a versioning database except the status code for
the transactions that were active, and this takes only a second or
two. Any record versions left behind by the transactions that were
rolled back will be ignored and cleaned up automatically by the
normal garbage-collection process.
What happens if you have a catastrophic hard-disk failure? Can you
restore the last full backup and replay the transaction log to roll the
database forward to the moment of the crash? What are your options
if the entire server machine and all of its hard drives are destroyed?
Are there repair tools for recovering a corrupt database? How
good are they?

Other Features
There are other important questions you should include on your
checklist. They may include the following:
Can you import and export data in XML format?
What other data formats do you need to import and export
routinely? Does the database support them?
What tools are provided for importing and exporting data?
Are gateways available for other databases with which you
need to exchange data?
Does the database interface to the development tools you use?
Is there an ODBC driver?
Is there an OLE DB driver?
Is there a dbExpress driver?
Is there a native component suite for Delphi?
Are there case tools that support this database?

Maintenance, Security, and Cost


When considering whats required to maintain the database, ask
yourself the following questions:
What steps must you take to keep the database operating at peak
efficiency? Do you have to rebuild indices, update table statistics, or reorganize the data on disk to reduce fragmentation?
Can these routine maintenance processes be automated?
Can these operations be performed while the database is in use?
Will the database and log files grow dynamically, or must the
DBA monitor and expand them?
How much skill is required of on-site personnel to maintain
the database?
Are performance-monitoring tools included to help you tune
the database?
How many tuning parameters are there?

Columns & Rows


A careful look at maintenance and tuning requirements will help
you determine whether you need a specially trained database
administrator to support your database. For example, Oracle
is reported to have more than 400 tuning parameters. Clearly,
tuning is not a task for the casual user.
How much does it cost?
What is the cost of the software?
What are the per-seat licensing costs?
What is the cost of a support contract?
What is the cost of updates?
What is the cost of training for your development and support staff?
Will you need a full-time, specially trained database administrator? At what cost?
What security features does the database have?
Is user security maintained at the server level or the database
level?
How granular is security for database objects? Can you grant
access at the table level? At the column level?
Are roles supported to make administration easier?
Does the database support encryption? How good is the algorithm?
Can you restrict the users access to metadata?
Can you protect your data, metadata, stored procedures, or
triggers from access by anyone (including the database administrator) at a client site?
How dependent is the database on operating-system security?
Security needs vary widely. If youre setting up a database server
within your business, security at the operating-system level may
be sufficient to prevent unauthorized people from gaining access
to the database files. If the database will be installed on notebook
computers that can be lost or stolen, you face a different set of
problems. If youre selling a commercial product and need to
protect your intellectual property from everyone, including your
clients, you need yet another set of security features.

Conclusion
The key to choosing the right database for your project or organization is to first create a thorough definition of your requirements.
While not exhaustive, this checklist gives you a place to start. As
you define your requirements, alter this list to make it match your
needs, and youll have a useful tool for comparing products.

Bill Todd is president of The Database Group, Inc., a database consulting and
development firm based near Phoenix. He is co-author of four database programming books, author of more than 90 articles, a contributing editor to Delphi
Informant Magazine, and a member of Team B, which provides technical support
on the Borland Internet newsgroups. Bill is a nationally known trainer and is a frequent speaker at Borland Developer Conferences in the United States and Europe.
He has taught Delphi programming classes across the country and overseas.
Readers may reach him at [email protected].

15 October 2002 Delphi Informant Magazine

Delphi at Work
XML / DOMs / Interfaces / Delphi 6

By Keith Wood

XML Building Blocks


Part II: Manipulating DOMs and XML Documents

ast month, I introduced the concept of XML building blocks components designed to
work with XML documents expressed as Document Object Models (DOMs). Theyre built
around two basic interfaces: IXMLProducer defines components that generate DOMs for
use by other components; and IXMLConsumer defines components that accept an existing
DOM and read, alter, or otherwise process it.

Figure 1: A SQL query as XML.


16 October 2002 Delphi Informant Magazine

By implementing both interfaces in the one component, you can create chains of objects that make
small, well-defined contributions to the XML processing. Then, you can combine these in different
ways to customize the functionality of a particular
application. The components supply published
properties and so can be connected and configured
at design time. That leaves a single call to the first
producer to start the entire sequence.

Database to XML

Last month, you saw the TXMLBuildingBlock base


class that provides common abilities for components that implement one or both of these interfaces. From that class came components to load a
DOM from an existing document, write a DOM
out as text, and apply an XSL transformation to a
DOM. There are other building blocks, as well...

Mapping from a query result into XML is fairly


straightforward. The fields returned by the query
can be represented in several ways. You could have
an element for each field, with the tag name being
the same as the field name, and with the value of
the field being the text content. Alternately, you
could have a generic field element that contained
the field name and value as attributes. Another
option would be to incorporate the fields into the
element for the record as a whole and express them
as attributes named by the field names. Which
format you use depends on your preference and on
how much data is involved.

Because XML is a standardized data format and


because data commonly resides in a relational database, it would be useful to have an XML building block that generates XML documents from a
database query. By taking advantage of Delphis
database support, you can submit a SQL query to
various relational-database implementations and
then transform the result.

Surrounding the individual field elements (if


you choose that approach) is another element
representing the entire record. This element could
have a generic name (such as record ) or, if the
query only references one table, the elements name
could be based on that table name. The top-level
element groups all the records from a query and is
also a requirement of a properly formatted XML
document. Again, this element could be named
generically (with a name such as query) or could be
based on the database in use.

Delphi at Work
The TXBBSQL component extends TXMLBuildingBlock and adds
properties (see Listing One on page 20) to identify the database with
which to work (DatabaseName) and the SQL query to submit (SQL).
Additional properties let you specify the names of the top-level (TagName) and record-level (RecordTagName) elements, and indicate how
the data is presented (FieldFormat), as elements or as attributes. The
Consumer property is promoted to published visibility so you can
connect this component to another one for further processing of the
generated XML document.
In the CreateDocument method (required for the IXMLProducer
interface), a TQuery object is created, initialized with the database
name and query, and opened. Its fields are examined, and any memo
fields are assigned an event handler to return their content properly
(usually, DisplayText returns the class name for these fields). For each
record, you create a new DOM element and add it to the document.
Then, for each field, you create an element and text, an element and
attributes, or just attributes, depending on the format requested.
When completed, the DOM is sent to any consumer registered with
the class. Figure 1 shows the results of querying the Biolife database
by piping the output of the TXBBSQL component into a tree view.
Each field is presented as a separate sub-element.

Text to XML
Straight text files are another common source of material for XML
documents. The TXBBTextFile class derives from TXMLBuildingBlock
(see Figure 2). TXBBTextFile allows you to set the name of the file to
read (FileName), and allows you to set whether the content is enclosed in
a CDATA section in the XML (AsCDATA). Inherited properties are published to make them available: TagName for the top-level element name,
OnTagCreate to let you modify that element, and Consumer to pass on
the new document for further processing.
You override the CreateDocument method to read in the text from
the nominated file and add it as the text content (enclosed in a
CDATA section if requested) of the top-level element in a new
document. The name of the original file is added as an attribute
of the main element for future reference. Then the resulting XML
document is sent on to the registered consumer of this component.
Figure 3 shows the outcome of wrapping a text file in XML and
displaying it in a Memo component.

Adding a Time Stamp

{ Create a new DOM around contents of a text file. The


FileName property locates the file which is then made
into an XML document. Top-level tag is named according
to TagName property, or 'file' if blank. It contains a
single text or CDATA section node (depending on the
AsCDATA property) that has the file's contents. }
TXBBTextFile = class(TXMLBuildingBlock)
private
FAsCDATA: Boolean;
FFileName: TFileName;
public
constructor Create(AOwner: TComponent;
const FileName: TFileName = '';
const TagName: string = ''); reintroduce; overload;
procedure CreateDocument; override;
published
property AsCDATA: Boolean
read FAsCDATA write FAsCDATA default False;
property Consumer;
property FileName: TFileName
read FFileName write FFileName;
property TagName;
property OnTagCreate;
end;
{ Read text file and wrap it in an element. }
procedure TXBBTextFile.CreateDocument;
var
Document: IDOMDocument;
Text: TStringList;
begin
if FileName = '' then
raise EXBBException.Create('Missing filename');
Document := NewDocument(
IfThen(TagName <> '', TagName, 'file'));
Text := TStringList.Create;
try
Text.LoadFromFile(FileName);
Document.DocumentElement.SetAttribute(
'filename', FileName);
if AsCDATA then
Document.DocumentElement.AppendChild(
Document.CreateCDATASection(Text.Text))
else
Document.DocumentElement.AppendChild(
Document.CreateTextNode(EscapeText(Text.Text)));
finally
Text.Free;
end;
DocumentReady(Document);
end;

Figure 2: Include a files contents in the XML.

Suppose you wanted to add a time stamp to


an XML document to indicate when it was
created or used. An XML building block could
perform this as part of a longer chain of processing. By providing a simple component that
does this one thing, you make it easy to reuse
when building a more complex application.
The TXBBTimestamp class is both a consumer and producer of XML DOMs (see
Listing Two beginning on page 20, which
allows its insertion into the middle of a
processing chain. Its Format property lets
you define how the time stamp appears in
the document, and the InsertAtStart property
controls where it is added to the existing
DOM. The inherited TagName property sets
the name of the new element, and Consumer
indicates where to send the result.
17 October 2002 Delphi Informant Magazine

Figure 3: A wrapped text file displayed in a Memo component.

Delphi at Work
extended so it can contain multiple fields,
which are then expressed as child elements or
attributes within the DOM.
Each field contains a name and a format
separated by an equals sign (=), and vertical bars
(|) separate multiple fields. To create attributes,
the name must start with an at symbol (@). If
no fields are present, the converted value just
becomes the text for the time-stamp element
itself. Otherwise, attributes or child elements are
created with their text content set to the specified part of the current date and time. Figure 4
shows the results of adding a time stamp to the
text file loaded earlier. This time, its presented
in a tree view, with the Format value being
year=yyyy|month=MM|day=dd.
Figure 4: The results of adding a time stamp.

{ Merge several DOM documents into a new document under a


new main element (named from the TagName property). Set
number of documents to expect with NumDocuments property.
When that many have appeared via the DocumentReady method
and been combined, they're sent on to this component's
consumer. Reset the component for another combination by
setting NumDocuments again. }
TXBBMerge = class(TXMLBuildingBlock)
private
FCountDown: Integer;
FMergedDocument: IDOMDocument;
FNumDocuments: Integer;
procedure SetNumDocuments(Value: Integer);
public
constructor Create(AOwner: TComponent;
const NumDocuments: Integer = 2;
const TagName: string = ''); reintroduce; overload;
procedure DocumentReady(Document: IDOMDocument);
override;
published
property Consumer;
property NumDocuments: Integer
read FNumDocuments write SetNumDocuments;
property TagName;
property OnTagCreate;
end;
{ Add given document to merged doc; pass on when done. }
procedure TXBBMerge.DocumentReady(Document: IDOMDocument);
begin
if not Assigned(FMergedDocument) then
FMergedDocument := NewDocument(
IfThen(TagName <> '', TagName, 'merge'));
FMergedDocument.DocumentElement.AppendChild(
Document.DocumentElement);
Dec(FCountDown);
if FCountDown = 0 then begin
{ Document is complete. }
NotifyConsumer(FMergedDocument);
FCountDown := NumDocuments;
end;
end;

Figure 5: Combining DOMs.

The time stamp always appears as a separate element immediately


below the main document element. However, its internal format
can vary between straight text and sub-elements, as determined by
its Format property. This propertys value is supplied to the standard
FormatDateTime method to display the current date and time. Its
18 October 2002 Delphi Informant Magazine

Knitters and Splitters


Another frequent task in generating new XML documents is to
combine fragments from several different sources. In this case, each
source is a complete DOM document rather than an actual document fragment. The main elements of these DOMs become the
children of the new top-level element in the combined document.
Building on the TXMLBuildingBlock base, the TXBBMerge component
waits for a specified number of documents to appear and adds them
to its own DOM as it goes (see Figure 5). Once all the constituent
parts have arrived, the complete document is passed to its registered
consumer. Thus, this class acts as a consumer for several other building
blocks, and as the source for later processing in the chain.
Because the way this class processes documents is quite different
from the standard practice, it overrides DocumentReady rather than
ProcessDocument. The NumDocuments property holds the number
of documents to wait for and combine. The FMergedDocument field
holds the new DOM as its being built, and the inherited TagName
property provides the name of the new main element. As each document is added, a counter is decremented. Upon reaching zero, the
completed document is sent on with a call to NotifyConsumer.
Although the XML building-block scheme lets you chain producers
and consumers together (so they operate one after the other on the
same document), there are times when you want several consumers to operate on the one document independently. The TXBBFork
component provides just such abilities. It maintains a list of consumers, rather than just one, and sends to each of them a copy of the
document sent to it (see Figure 6). Again, it overrides DocumentReady
because its functionality differs substantially from the standard.
Because it creates clones of the document for passing on, it may not
be appropriate for large documents. For large documents, you should
use the linear chain inherent in the standard building blocks.
To provide the list of consumers to which this class talks, so that its
both persistent and handled by Delphi at design time, you define it as a
collection. The TXBBConsumerCollection class extends TCollection and
overrides methods to ensure that only appropriate child items are used
(see Figure 7). GetOwner is also overridden to return the TXBBFork object
that owns this collection (as passed in during construction). This allows
the Object Inspector to display the ownership correctly at design time.
All entries in the list are instances of TXBBConsumerCollectionItem
(a descendant of TCollectionItem) and are generated automatically

Delphi at Work
{ Pass DOM document to several consumers. }
TXBBFork = class(TXMLBuildingBlock)
private
FConsumers: TXBBConsumerCollection;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure DocumentReady(Document: IDOMDocument);
override;
published
property Consumers: TXBBConsumerCollection
read Fconsumers write FConsumers;
end;
{ Copy document and pass it on to each consumer. }
procedure TXBBFork.DocumentReady(Document: IDOMDocument);
var
Index: Integer;
begin
for Index := 0 to FConsumers.Count - 1 do
if Assigned(FConsumers.Items[Index].Consumer) then
FConsumers.Items[Index].Consumer.DocumentReady(
Document.CloneNode(True) as IDOMDocument);
end;

Figure 6: Sending documents in all directions.

{ A consumer item for a collection. }


TXBBConsumerCollectionItem = class(TCollectionItem)
private
FConsumer: IXMLConsumer;
FName: string;
procedure SetConsumer(Value: IXMLConsumer);
procedure SetName(Value: string);
protected
function GetDisplayName: string; override;
published
property Consumer: IXMLConsumer
read FConsumer write SetConsumer;
property Name: string read FName write SetName;
end;
{ A collection of consumers. }
TXBBConsumerCollection = class(TCollection)
private
FOwner: TPersistent;
protected
function GetItem(Index: Integer):
TXBBConsumerCollectionItem;
function GetOwner: TPersistent; override;
procedure SetItem(Index: Integer;
Value: TXBBConsumerCollectionItem);
public
constructor Create(Owner: TPersistent);
function Add: TXBBConsumerCollectionItem;
function FindItemID(ID: Integer):
TXBBConsumerCollectionItem;
function Insert(Index: Integer):
TXBBConsumerCollectionItem;
property Items[Index: Integer]:
TXBBConsumerCollectionItem read GetItem write SetItem;
end;

Figure 7: A persistent list of consumers.

through Delphi and the Object Inspector. The items have a Consumer
property, just like the basic building blocks, and a Name property
thats simply for reference purposes at design time. If you need to add
items at run time, you can use the following code:

Figure 8: Modifying the TXBBSQL properties.

of the article for details on downloading it) lets you experiment with
the XML building blocks. The left side of the form shows a wiring
diagram for the building blocks. Just check off the ones you want to
use. The merge and fork components are incorporated automatically
as necessary. Components at the top are the XML producers, those in
the middle modify a DOM that is passing through, and those at the
bottom are the ultimate consumers of the DOM. Several components appear as buttons on the form and have properties that can
be altered at run time. Clicking a button brings up an appropriate
dialog box for the TXBBSQL component, as shown in Figure 8.
Experiment with the combinations to see how the building blocks work
together. A sample XML document (movie-watcher.xml) is supplied
for loading, along with its Document Type Definition and a couple of
appropriate XSL transformations. Click the Go button to start a run after
selecting the components to use and setting their properties.

Conclusion
The XML building-block components are designed to make it easier
to work with XML documents in DOM format. Theyre based on two
interfaces that generate DOMs and then work with them. When combined in the one component, these interfaces let you construct chains of
components that create a DOM, modify it, and then present the results.
Last month, you saw components that:
performed the basic loading of an existing XML document
applied an XSL transformation to a DOM
wrote out a DOM to a file or stream
In this article, you see components that:
create DOMs from alternate data sources
modify the DOM by adding a time-stamp element
merge several parts to form a new document
create copies of a document for separate, subsequent processing
Next month, Ill finish up with more XML building blocks that present
the contents of a DOM in GUI controls. The data can be easily sent to
a tree view, a string grid, a memo field, and a Web browser. Its all just a
few mouse clicks away with these plug-and-play components.

xbbFork.Consumers.Add.Consumer := xbbWriter;

Demonstration
The demonstration program that comes with this article (see the end
19 October 2002 Delphi Informant Magazine

The files referenced in this article are available on the Delphi Informant
Magazine Complete Works CD located in INFORM\2002\OCT\
DI200210KW.

Delphi at Work
Keith Wood hails from Brisbane, Australia. He is a consultant using Delphi
and Java. He started using Borlands products with Turbo Pascal on a
CP/M machine. His book, Delphi Developers Guide to XML, covers many
aspects of XML from a Delphi point of view. You can reach him via e-mail at
[email protected].

Begin Listing One TXBBSQL

{ Create new DOM from SQL query. The DatabaseName and SQL
properties retrieve data which is then made into an XML
document. The top-level tag is named according to the
TagName property, or from the DatabaseName if blank. Each
row from the query becomes an element under this, using
RecordTagName as its name, or 'record' if blank. Fields
from the query then become child elements of the record,
with names taken from field name, and contents as text or
CDATA section (if they contain '<' or '>') nodes. }
TXBBSQL = class(TXMLBuildingBlock)
private
FDatabaseName: string;
FFieldFormat: TXBBFieldFormat;
FOnRecordTagCreate: TXBBRecordTagCreateEvent;
FRecordTagName: string;
FSQL: TStrings;
procedure GetText(Sender: TField; var Text: string;
DisplayText: Boolean);
public
constructor Create(AOwner: TComponent); overload;
override;
constructor Create(AOwner: TComponent;
const TagName: string); reintroduce; overload;
constructor Create(AOwner: TComponent;
const DatabaseName: string; const SQL: TStrings;
const TagName: string = ''); reintroduce; overload;
destructor Destroy; override;
procedure CreateDocument; override;
published
property Consumer;
property DatabaseName: string
read FDatabaseName write FDatabaseName;
property FieldFormat: TXBBFieldFormat read FfieldFormat
write FFieldFormat default xfText;
property RecordTagName: string
read FRecordTagName write FRecordTagName;
property SQL: TStrings read FSQL write FSQL;
property TagName;
property OnRecordTagCreate: TXBBRecordTagCreateEvent
read FOnRecordTagCreate write FOnRecordTagCreate;
property OnTagCreate;
end;
{ Run query against database and convert results to XML. }
procedure TXBBSQL.CreateDocument;
var
Document: IDOMDocument;
RecordElement, FieldElement: IDOMElement;
Query: TQuery;
Index: Integer;
RecTagName, FieldName, FieldValue: string;
begin
if (DatabaseName = '') or (SQL.Text = '') then
raise EXBBException.Create(
'Missing database name or SQL');
RecTagName := IfThen(
RecordTagName <> '', RecordTagName, 'record');
Document := NewDocument(
IfThen(TagName <> '', TagName, DatabaseName));
Query := TQuery.Create(nil);

20 October 2002 Delphi Informant Magazine

with Query do
try
DatabaseName := Self.DatabaseName;
SQL
:= Self.SQL;
Open;
for Index := 0 to FieldCount - 1 do
if Fields[Index] is TMemoField then
Fields[Index].OnGetText := GetText;
while not Eof do begin
{ Create an element for each record. }
RecordElement := IDOMElement(
Document.DocumentElement.AppendChild(
Document.CreateElement(RecTagName)));
if Assigned(OnRecordTagCreate) then
OnRecordTagCreate(Self, RecordElement, Query);
for Index := 0 to FieldCount - 1 do begin
FieldName := Fields[Index].DisplayName;
FieldValue := EscapeText(
Fields[Index].DisplayText);
case FieldFormat of
xfText:
{ And then a sub-element for each field. }
begin
FieldElement := IDOMElement(
RecordElement.AppendChild(
Document.CreateElement(FieldName)));
FieldElement.AppendChild(
Document.CreateTextNode(FieldValue));
end;
xfElement:
{ Add field values as attributes on
separate elements. }
begin
FieldElement := IDOMElement(
RecordElement.AppendChild(
Document.CreateElement(FieldName)));
FieldElement.setAttribute(
value, FieldValue);
end;
xfAttributeOnly:
{ Add field values as attributes
on the record element. }
RecordElement.setAttribute(
FieldName, FieldValue);
end; // case FieldFormat...
end; // for Index := 0 to FieldCount 1...
Next;
end; // while not Eof...
Close;
DocumentReady(Document);
finally
Free;
end;
end;
{ Retrieve contents of a memo field. }
procedure TXBBSQL.GetText(Sender: TField; var Text: string;
DisplayText: Boolean);
begin
Text := TMemoField(Sender).AsString
end;

End Listing One


Begin Listing Two TXBBTimestamp

{ Add timestamp to a DOM. The timestamp appears as a


separate element named from the TagName property (or
'timestamp' if blank) that appears first or last under
existing documents main element (depending on
InsertAtStart property. The Format property defines the
appearance of timestamp and uses same notation as
required by the FormatDateTime function. Format can be
extended to generate multiple date parts under timestamp
element. Separate sub-elements with vertical bars (|),
and sub-element names from formats with equals (=). For
example, a Format of 'year=yyyy|month=MM|day=dd' creates
the following structure:
<timestamp><year>2002</year><month>03</month>

Delphi at Work
<day>07</day></timestamp>.
Prefix a name with '@' to make it an attribute instead.
For example, a format of '@year=yyyy|@month=MM|@day=dd'
creates the following structure:
<timestamp year="2002" month="03" day="07"/>. }
TXBBTimestamp = class(TXMLBuildingBlock)
private
FFormat: string;
FInsertAtStart: Boolean;
procedure SetFormat(const Value: string);
protected
function ProcessDocument(const Document: IDOMDocument):
IDOMDocument; override;
public
constructor Create(AOwner: TComponent;
const Format: string = ''; const TagName: string = '');
reintroduce; overload;
published
property Consumer;
property Format: string read FFormat write SetFormat;
property InsertAtStart: Boolean
read FInsertAtStart write FInsertAtStart;
property TagName;
end;
{ Add a timestamp element (or subtree) to the document. }
function TXBBTimestamp.ProcessDocument(
const Document: IDOMDocument): IDOMDocument;
var
Element: IDOMElement;
DateTime: TDateTime;
{ Format consists of several fields to generate multiple
date parts beneath the timestamp element. }
procedure AddSubFormats(MainElement: IDOMElement);
var
Index: Integer;
Name, SubFormat, WorkFormat: string;
begin
WorkFormat := Format;
repeat
Index := Pos('=', WorkFormat);
if Index = 0 then
Exit;
Name := Copy(WorkFormat, 1, Index - 1);
Delete(WorkFormat, 1, Index);
Index := Pos('|', WorkFormat);
if Index = 0 then
Index := Length(WorkFormat) + 1;
SubFormat := Copy(WorkFormat, 1, Index - 1);
Delete(WorkFormat, 1, Index);
if Name[1] = '@' then
MainElement.SetAttribute(Copy(Name, 2,
Length(Name)), FormatDateTime(SubFormat, DateTime))
else
MainElement.AppendChild(Document.CreateElement(
Name)).AppendChild(Document.CreateTextNode(
FormatDateTime(SubFormat, DateTime)));
until WorkFormat = '';
end;
begin
DateTime := Now;
Element := Document.CreateElement(IfThen(TagName <> '',
TagName, 'timestamp'));
if Pos('=', Format) = 0 then
Element.AppendChild(Document.CreateTextNode(
FormatDateTime(Format, DateTime)))
else
AddSubFormats(Element);
if InsertAtStart then
Document.DocumentElement.InsertBefore(
Element, Document.DocumentElement.FirstChild)
else
Document.DocumentElement.AppendChild(Element);
Result := Document;
end;

End Listing Two


21 October 2002 Delphi Informant Magazine

The API Calls


Active Directory / ADSI / ADO / Delphi 6

By Shane Miller

Active Directory via ADO


Use LDAP to Get the Information You Need

icrosoft provides multiple ways of accessing information stored in Active Directory.


In this article, Ill show you how you can use ActiveX Data Objects (ADO) to gather
information about particular users in Active Directory.
Active Directory Service Interfaces (ADSI) are
installed by default on operating systems beginning
with Windows 2000. You wont be using them
extensively, but you need to use them to determine
the distinguished name of your current domain.
If youre running a version of an operating system
that doesnt have ADSI, go to Microsofts site for
your particular operating system and search for
ADSI. At the time of this writing, ADSI 2.5 was
available for download for Windows 95 and later.
To start, create a new Application project in
Delphi. Add a DataModule to the project and
name it DataModule1. Drop an ADOConnection
and an ADOQuery component onto the data
module, set the ADOConnection components
LoginPrompt property to False, and set the Provider

property to ADsDSOObject. (The complete sample


application demonstrated in this article is available
for download; see end of article for details.)
Next, select the ConnectionString property and click
the ellipsis button (...) to open the Connection
String dialog box. Click the Use Connection String
radio button, then click the Build button to display
the Data Link Properties dialog box (see Figure 1).
On the Provider tab, select OLE DB Provider for
Microsoft Directory Services. On the Connection tab,
select the Use Windows NT Integrated Security radio
button. The Advanced tab displays the Connect
timeout and the permissions that this connection
will have. You dont need to change anything on this
tab, but you should be aware that you could change
it to suit your specific situation. Click OK to close
each dialog box. Now you can test your connection
by setting the Connected property to True.
Once your connection works properly, link the
ADOQuery component to the ADOConnection
component by setting ADOQuerys Connection
property to ADOConnection1.
To query your domain controller for information,
you need to determine the name of your domain
and the format to use. The query will look similar
to a standard SQL query, with minor changes.
Figure 2 shows an example of the SQL query you
would use to get information from the Active
Directory about a particular user. Instead of specifying a table name, youre using Lightweight Directory Access Protocol (LDAP) to specify the domain
you want to query. In the LDAP query, you are
specifying your domain to be mydomain.com.

Figure 1: The Data Link Properties dialog box.


22 October 2002 Delphi Informant Magazine

The ADSI interfaces provide a way of determining


your domain so that hard-coding a specific domain
isnt required. Each directory server has an entry

The API Calls


procedure TForm1.GetUserInfo;
var
Query: string;
begin
ADOQuery1.Sql.Clear;
Query := 'SELECT Name, distinguishedName, ' +
'sAMAccountName, PrimaryGroupID, ' +
'MemberOf ' +
' FROM ''LDAP://DC=mydomain, DC=com'' '+
'WHERE sAMAccountName = ''smiller1''';
ADOQuery1.Sql.Add(Query);
ADOQuery1.ExecSql;
end;

Figure 2: This method uses an LDAP query to get user


information from Active Directory.

function GetObject(const Name: string): IDispatch;


var
Moniker: IMoniker;
Eaten: integer;
BindContext: IBindCtx;
Dispatch: IDispatch;
begin
OleCheck(CreateBindCtx(0, BindContext));
OleCheck(MkParseDisplayName(BindContext,
PWideChar(WideString(Name)), Eaten, Moniker));
OleCheck(Moniker.BindToObject(BindContext, nil,
IDispatch, Dispatch));
Result := Dispatch;
end;
function GetDefaultPath: string;
var
RootDse : Iads;
begin
Result := '';
try
rootDSE := GetObject('LDAP://rootDSE') as IAds;
Result :=
'LDAP://'+rootdse.Get('defaultNamingContext');
rootdse := nil;
except
on E:Exception do begin
Result := 'LDAP://DC=domain,DC=com';
if not InputQuery('Domain Path not found.',
'Please enter the domain path to search',
Result) then
Raise Exception.Create(E.Message);
end;
end;
end;

Figure 3: Methods used to get the path to the current domain.

named RootDSE. By querying for RootDSE, you can determine the


domain the current user is logged on to. This provides an easy way
to add flexibility to your application. To use RootDSE, you need to
import the ADSI type library.
Select Project | Import Type Library, select the Active DS Type Library from the
list, and press the Create Unit button. A unit named ActiveDS_TLB.pas
will be created and added to your project. Add this unit to the uses clause
of your main unit (Unit1).

// Get details about a specific user.


Query := 'SELECT Name,distinguishedName,sAMAccountName, ' +
'PrimaryGroupID,MemberOf FROM '''+GetDefaultPath +
''' WHERE sAMAccountName = ''' + FUsername + '''';
// Get a list of Organizational Units.
Query := 'SELECT name FROM ''' + GetdefaultPath +
''' WHERE objectclass=''organizationalunit''';
// Get a list of distinguishednames of users. The
// distinguishedName can then be used to pull specific
// details about a user.
Query := 'SELECT distinguishedName FROM ''v+GetDefaultPath+
''' WHERE objectclass=''user''';
// If you set Groupname='Domain Users' you get a VarArray
// of members of that group.
Query := 'SELECT member FROM '''+GetDafaultPath+
''' WHERE sAMAccountName = '''+GroupName+'''';
// Get's a list of computers.
Query := 'SELECT distinguishedName FROM '''+GetDefaultPath+
''' WHERE objectclass=''computer''';
// Use the distinguishedName from above to get the
// dnshostname of the computer.
Query := 'SELECT dnshHostName FROM ''LDAP://' +
PathFromAbove + '''';
// Use the distinguishedName from above to get the
// operating system of the computer.
Query := 'SELECT operatingsystem FROM ''LDAP://' +
PathFromAbove + '''';

Figure 4: Example LDAP queries.

is defined in the Active_DS type library. Its an interface that defines a


basic Active Directory object, specifically a user, computer, or group.
By using the GetObject function, you get an instance of the current
IADs on the PC. Once you have that, you get the path of the IADs,
using the get method of the IADs and passing it the property you want
to get in this case, distinguishedName. This allows you to get the
domain name the PC is using for authentication.
By using the domain path as your table name in your queries, you
can construct queries to search for specific users or groups. Figure 4
gives some examples of the queries you can build.

Creating a Class
You can use what youve done so far to create a class that will hold
information about the current user logged on to the PC. To start,
create a class named TCurrentUserInfo (shown in Figure 5). Your
main unit will create an instance of this class; then it can query
its properties.
The properties of this class are set in the FillValues procedure.
FillValues gets the current user name of the person logged on to the
PC (code snippet shown in Figure 6), and utilizes that user name in
the query to Active Directory.
The query selects the name, distinguishedName, PrimaryGroupID,
and MemberOf fields from the server, where sAMAccountName is
equal to the user name of the user who is currently logged on to the
PC. The distinguished name holds the path to the user definition.
For example, if a user is created in the organizational unit, HelpDesk,
then that users distinguished name would be something like:
CN=Shane Miller, OU=HelpDesk, DC=domain, DC=com

There are two functions you need to create in order to get the
RootDSE. They are GetDefaultPath and GetObject (shown in
Figure 3). Youll also need to add ActiveX and ComObj to your units
uses clause in order for these functions to work. The GetDefaultPath
function uses the GetObject function to get an instance of IADs. IADs
23 October 2002 Delphi Informant Magazine

By storing the distinguished name, you can use it in your query to


select fields for that specific user. Then, TCurrentUserInfo properties
would be filled by reading each field value, using the ADOQuery
method FieldByName (see code snippet in Figure 7).

The API Calls


{ TCURRENTUSERINFO: Used to get current info for person
logged into PC through AD. Use: Create an instance of
the class and then query the properties. }
TCurrentUserInfo = class
private
FUserName: string;
FDomainShortName : string;
FDomainDNSName: string;
FDCName: string;
FSitename: string;
FComputerName: string;
FOrgUnit: string;
FAccountName: string;
FGroups: TList;
procedure FillValues;
function GetContainerFromString(Value: string): string;
function GetGroupFromString(Value: string): string;
public
constructor Create;
destructor Destroy; override;
property UserName: string read FUserName;
property AccountName: string read FAccountName;
property OrgUnit: string read FOrgUnit;
property Groups: TList read FGroups;
end;

Figure 5: Methods and properties of the TCurrentUserInfo class.

TMemberKind = (mkUser, mkOrgUnit, mkGroup, mkDomain,


mkComputer, mkCollection, mkService, mkFileService,
mkFileShare, mkLocality, mkOrg, mkPrintJob,
mkPrintQueue, mkUnknown);
TADSIItem = class
private
FPath: string;
fKind: TMemberKind;
function GetName: string;
function GetContainer: string;
protected
property Kind: TMemberKind read fKind write fKind;
public
constructor Create(aPath: string); virtual;
property Name: string read GetName;
property Container: string read GetContainer;
end;
TGroup = class(TADSIItem)
private
function GetGroupID: Integer;
public
constructor Create(aPath: string); override;
property GroupID: Integer read GetGroupID;
property Kind;
end;

Figure 8: Classes used to describe each group.


// Get the current logged in user.
nSize := 20;
GetMem(lpBuffer, nSize+1);
GetUsername(lpBuffer, nSize);
tempStr := Strpas(lpBuffer);
FreeMem(lpBuffer);
// tempstr is now the logged-in username.

Figure 6: Getting the current user name of the person logged on


to the PC.

Datamodule1.ADOQuery1.Open;
if (not IsEmpty) then begin
fUsername := FieldByName('Name').AsString;
fOrgUnit := GetContainerFromString(
FieldByName('distinguishedName').AsString);
fAccountName := FieldByName('sAMAccountName').AsString;
// Get groups.
MemberOf := FieldByName('MemberOf').AsVariant;
// Get the primary group via the primary group token.
Num := FieldByName('PrimaryGroupID').AsInteger;

Figure 7: Setting TCurrentUserInfo properties.

If you had the distinguished name of an organizational unit, you


could use it to get all the users in that organizational unit by setting
your query to:
SELECT *
FROM 'LDAP://OU=HelpDesk, DC=domain, DC=com'
WHERE objectclass = 'user'.

Each user in Active Directory is assigned to a primary group. By


default, a newly created users primary group is Domain Users.
PrimaryGroupID holds the ID for the users primary group.
MemberOf is a variant array that contains a list of group names to
which this user belongs. The primary group is not contained in this
list, which is why you need to query for PrimaryGroupID. To get
24 October 2002 Delphi Informant Magazine

the PrimaryGroupIDs group name, you need to execute a second


query that gets all groups. Then, you can walk through that list
and compare PrimaryGroupID to the groups primarygrouptoken.
TCurrentUserInfo holds a TList that contains pointers of type TGroup
(see Figure 8). Each TGroup is a group to which this user belongs.
Once youve coded TCurrentUserInfo, your main unit needs to create
an instance of TCurrentUserInfo. Then it can query its properties
(see Figure 9).

Problems and Pitfalls


When using LDAP, you may need to escape some characters in the
LDAP path. For example, a path of:
LDAP://CN=I/S HelpDesk, OU=HelpDesk, DC=mydomain, DC=com

is invalid and will throw an exception. This is because of the forward


slash in the name I/S. To fix this, I wrote a function named EscapePath
that takes the path value, the character to look for and escape (if
found), and the character to use as the escape character. In LDAP, the
escape character is the backslash (\). To correct the invalid path you
just saw, simply convert I/S to I\/S, i.e. place a backslash in front of
the forward slash.
If you get table does not exist errors when executing your queries,
this usually means youre not logging into the Active Domain, or
you have another network client installed. In some cases, having
Microsofts NetWare client installed causes the GetObject call to fail.
To get around this, either make sure you are logging into an Active
Directory domain, or change the priority of the network clients.
Other issues arise with Microsofts documentation. Even though
it appears you should be able to access the PrimaryGroupID using
the ADSI interfaces, that simply does not work. Using ADO was
the only way I found to get this to work. However, Microsofts
documentation offers a great deal of helpful information about
field names and their meanings. You can search for ADSI at

The API Calls


{$R *.dfm}
var
UserInfo : TCurrentUserInfo;
procedure TForm1.Button1Click(Sender: TObject);
var
I : Integer;
Group : TGroup;
begin
if not Assigned(UserInfo) then
UserInfo := TCurrentUserInfo.Create;
Memo1.Lines.Clear;
Memo1.Lines.Add('Name');
Memo1.Lines.Add(UserInfo.UserName);
Memo1.Lines.Add('Account Name');
Memo1.Lines.Add(UserInfo.AccountName);
Memo1.Lines.Add('Org Unit name');
Memo1.Lines.Add(UserInfo.OrgUnit);
for I := 0 to UserInfo.Groups.Count-1 do begin
Group := UserInfo.Groups.Items[i];
Memo1.Lines.Add(Group.Name);
end;
end;

Figure 9: Instantiating TCurrentUserInfo and getting its properties.

http://msdn.microsoft.com, view all the properties of each


object class, and see how they can be used. Another useful tool is
ADSIEdit. This support tool (found on the Windows 2000 Server
CD) allows you to view all the users, groups, and computers in
your domain. By selecting a user, you can view all the properties
for that user. This allows you to determine what information you
should be getting back from your queries, and gives you a great
idea of what to query.
Another restriction placed on Active Directory is the limit of 1,000
objects returned per query; if you selected all objects from the Active
Directory at one time, and more than 1,000 objects exist, your result
set will include a maximum of 1,000 objects. A solution to this
problem is to use the IDirectorySearch interface. Instead of coding the
interface however, programmer Marc Scheuner has created a component named TADSISearch, which incorporates the interface. You can
find the component at http://beam.to/mscheuner.

Conclusion
Accessing Active Directory via ADO is a very convenient way of
getting information you need. Through examples on Microsofts site,
you should be able to figure out how to pull any information you
need for any Active Directory object.
The project referenced in this article is available on the Delphi Informant
Magazine Complete Works CD located in INFORM\2002\OCT\
DI200210SM.

Shane Miller works at St. Vincent Hospital in Green Bay, WI, supervising the
development group in the Information Services department. He develops mainly
in Delphi, Kylix, PHP, and C++ and participates in a number of open-source
projects, such as Lazarus (http://lazarus.freepascal.org). Readers may reach Shane
via e-mail at [email protected].

25 October 2002 Delphi Informant Magazine

Greater Delphi
Object Picker / Active Directory / COM / Delphi 6

By Marcel van Brakel

Object Picker Component


Dialog Box Access to Active Directory Made Easy

hen Active Directory was introduced in Windows 2000, Microsoft needed a new
dialog box to allow selections from the Active Directory, similar to the dialog
boxes provided for file selection, printer selection, and so forth. The resulting dialog
box is the object picker.
Most of Microsofts applications, such as the
Active Directory Users and Computers tool, use
this new dialog box in one way or another, so
its likely youve already seen it. The good news
is that the object picker dialog box is exposed
through the Windows API and is documented
fully, so you can and should use it. This
article introduces the object picker dialog box
and demonstrates how you can use it in your
own applications. It assumes youre familiar
with Active Directory and COM programming.

Figure 1: The Select Users or Groups object picker dialog box.


26 October 2002 Delphi Informant Magazine

This article is accompanied by a component Ive


developed that wraps the object picker dialog
box for easy use by Delphi developers (see end
of article for download details).

Object Picker
Object picker is a very versatile dialog box. You
can program it to allow selection of most Active
Directory objects, such as users, computers,
and groups, as well as selections from the local
computer, e.g. locally defined user accounts. In
addition, the object picker has many options
that allow you to fine-tune its behavior. These
options include enabling or disabling multiple
selection, and restricting the scope from which
the user can choose objects. Figure 1 shows the
object picker in action, for selection of users and
groups from my computer.
The Look-in drop-down list allows you to set the
scope from which objects can be selected. You
can program the object picker to allow selections from only a single scope or from multiple
scopes. You can think of a scope as a sub-tree in
the Active Directory object hierarchy, such as
the global catalog, the domain, the workgroup,
or a computer. In Figure 1, the scope is set to
DELPHI, which is the local computer. Therefore, the object picker displays only those objects
that are defined on the local computer.

Greater Delphi
IDsObjectPicker = interface (IUnknown)
['{0c87e64e-3b7a-11d2-b9e0-00c04fd8dbf7}]
// Sets scope, filter, etc. for use with next
// invocation of dialog box.
function Initialize(const pInitInfo: DSOP_INIT_INFO):
HRESULT; stdcall;
// Creates the modal DS Object Picker dialog box.
function InvokeDialog(hwndParent: HWND;
out ppdoSelections: IDataObject): HRESULT; stdcall;
end;

Figure 2: Declaration for the IDsObjectPicker interface.

var
// The Object Picker COM object.
ObjPicker: IDsObjectPicker;
begin
// 1. Initialize COM.
CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
// 2. Create the Object Picker object.
if SUCCEEDED(CoCreateInstance(CLSID_DsObjectPicker, nil,
CLSCTX_INPROC_SERVER, IID_IDsObjectPicker,
ObjPicker)) then begin
// Use the Object Picker here.
end;
// 12. Uninitialize COM.
ObjPicker := nil;
CoUninitialize;
end;

Figure 3: Creation and destruction of the object picker object.

Depending on how the object picker is initialized, you can select


other scopes from the drop-down list, including the domain of
which the computer is a member or the entire directory. For
example, when you select the domain, the list view changes to
display users and groups defined within the domain. An example
of such a group is the Domain Admins group. Thus, the list
view displays the objects that are available from the selected
scope. With filters, you have even more control over which type
of objects appear in the list. For example, you can set a filter to
display only users and universal groups or only computers and
well-known principals. I will explain this in more detail later.
A filter is bound to a scope. That is, you can specify a different filter
for each individual scope. For example, you can allow selection of
users and groups from the domain but only users from the local
computer. Using this combination of scopes and filters allows
complete control over which objects the user can select.
The last element in the dialog box shown in Figure 1 is the list that
displays the selected items. The list is empty because no objects
have been selected yet. This list only appears when the multi-select
flag is set. When this flag is cleared, the list is replaced with an edit
box. Finally, the Check Names button causes the object picker to
verify that the selected objects exist. This is necessary because the
user could enter incorrect names directly into the list.
The screenshot in Figure 1 was taken on a Windows 2000
Server system. The object picker dialog box GUI has changed
significantly in Windows XP and .NET. The object picker in
Windows XP is focused on searching more than on selection.
This is bad because it makes the object picker harder and less
intuitive to use. On the other hand, the XP version does have
some powerful searching features. Fortunately, the programming
interface and exposed functionality hasnt changed.
27 October 2002 Delphi Informant Magazine

TDsOpInitInfo = record
pwzTargetComputer: PWSTR;
cDsScopeInfos: ULONG;
aDsScopeInfos: PDSOP_SCOPE_INIT_INFO;
flOptions: ULONG;
end;
1..N

TDsOpScopeInitInfo = record
flType: ULONG;
flScope: ULONG;
FilterFlags: DSOP_FILTER_FLAGS;
end;
TDsOpFilterFlags = record
Uplevel: DSOP_UPLEVEL_FILTER_FLAGS;
flDownlevel: ULONG;
end;
TDsOpUpLevelFilterFlags = record
flBothModes: ULONG:
flMixedModeOnly: ULONG;
flNativeModeOnly: ULONG;
end;

Figure 4: The relationship between the TDsOpInitInfo record


and its nested records.

Programming Object Picker


Unlike the Win32 common dialog boxes, but like many other
things added to the Windows API lately, the object picker dialog
box is exposed as a COM object. This means you manipulate
and invoke the dialog box through its IDsObjectPicker interface,
shown in Figure 2.
The interface, related types, and constants are declared in the
ObjSel.pas interface unit. This interface unit isnt included with
Delphi 6, so I had to create it. Its included online with this article,
and you can always get the latest version from the Project JEDI
Web site or my Web page (see end of article for details).
Programming the object picker is a four-step process:
1) Creation and destruction of the COM object.
2) Initialization (setting scopes, filters, etc.).
3) Invocation (displaying the dialog box).
4) Processing the selection.

Creation and Destruction


To use the interface, you must create the object picker object
and then query it for the desired interface. This is typical for
a COM object. To create the object, you use CoCreateInstance,
as demonstrated in Figure 3. The COM subsystem must be
initialized first. Depending on your application, Delphi may
do this for you transparently, but I prefer to call CoInitializeEx
explicitly.

Initialization
Now that you have an interface for the object picker object, you
must initialize it. In this context, initialization is the means by
which you tell the object picker about the scopes, filters, and
various other options. Guess what? You use the Initialize method
to do this. The Initialize method takes only a single parameter
of type TDsOpInitInfo. This record contains various levels of
nested records and is rather complex. To help you understand
the big picture before diving into the details, Figure 4 outlines
the relationship between all these records. The declaration of
the TDsOpInitInfo record is shown in Figure 5. As we examine
TDsOpInitInfo, Ill explain the declaration of the nested records.

Greater Delphi
type
PDSOP_INIT_INFO = ^DSOP_INIT_INFO;
_DSOP_INIT_INFO = record
cbSize: ULONG;
pwzTargetComputer: PWSTR;
cDsScopeInfos: ULONG;
aDsScopeInfos: PDSOP_SCOPE_INIT_INFO;
flOptions: ULONG;
cAttributesToFetch: ULONG;
apwzAttributeNames: PWSTR;
end;
DSOP_INIT_INFO = _DSOP_INIT_INFO;
PCDSOP_INIT_INFO = PDSOP_INIT_INFO;
TDsOpInitInfo = DSOP_INIT_INFO;
PDsOpInitInfo = PDSOP_INIT_INFO;

Figure 5: The TDsOpInitInfo type declaration from ObjSel.pas.


PDSOP_SCOPE_INIT_INFO = ^DSOP_SCOPE_INIT_INFO;
_DSOP_SCOPE_INIT_INFO = record
cbSize: ULONG;
flType: ULONG;
flScope: ULONG;
FilterFlags: DSOP_FILTER_FLAGS;
pwzDcName: PWSTR; // OPTIONAL
pwzADsPath: PWSTR; // OPTIONAL
hr: HRESULT;
end;
DSOP_SCOPE_INIT_INFO = _DSOP_SCOPE_INIT_INFO;
PCDSOP_SCOPE_INIT_INFO = PDSOP_SCOPE_INIT_INFO;
TDsOpScopeInitInfo = DSOP_SCOPE_INIT_INFO;
PDsOpScopeInitInfo = PDSOP_SCOPE_INIT_INFO;

Figure 6: The TDsOpScopeInitInfo structure.

The cbSize member is used for versioning and should be set to the size,
in bytes, of the TDsOpInitInfo structure, using the SizeOf function.
Usually, you will set the pwzTargetComputer to nil, in which case the
object picker defaults to the local computer. However, you can set
it to the name of any computer in the Active Directory. In the latter
case, the object picker dialog box behaves as if it was invoked on the
specified computer to determine the joined domain and enterprise.
This is especially useful when creating a remote administration utility.
The flOptions member can be set to a combination of the following flags:
DSOP_FLAG_MULTISELECT If this flag is set, the user
can select multiple objects. If this flag is cleared, the user can
select only a single object.
DSOP_FLAG_SKIP_TARGET_COMPUTER_DC This
flag determines whether the target computer is included in
the Look-in drop-down list. You should set this flag only if the
target computer is not a domain controller.
I am going to ignore the cAttributesToFetch and apwzAttributeNames
members for now. Ill have more to say about them later.
The two remaining members, cDsScopeInfos and aDsScopeInfos,
are used to specify the scopes and their associated filters. The
aDsScopeInfos field is a pointer to an array of TDsOpScopeInitInfo
records, and cDsScopeInfo specifies the number of elements in
this array. Each element in the array specifies one or more scopes
that appear in the Look-in drop-down list (again, see Figure 1)
and the filter that applies to those scopes. So, each element
describes a location from which objects can be selected, and the
type of objects that can be selected from it. Figure 6 shows the
declaration of the TDsOpScopeInitInfo structure.
28 October 2002 Delphi Informant Magazine

The cbSize member should be set to the size, in bytes, of the


TDsOpScopeInitInfo record. The flType member indicates the
scope type that this entry describes. You can specify a single
flag, or you can combine several of them using the or operator.
Following are the valid scope-type flags:
DSOP_SCOPE_TYPE_DOWNLEVEL_JOINED_
DOMAIN A down-level domain joined by the target
computer.
DSOP_SCOPE_TYPE_ENTERPRISE_DOMAIN All
Windows 2000 domains to which the target computer belongs.
DSOP_SCOPE_TYPE_EXTERNAL_DOWNLEVEL_
DOMAIN All down-level domains external to the
enterprise, but trusted by the domain of which the target
computer is a member.
DSOP_SCOPE_TYPE_EXTERNAL_UPLEVEL_DOMAIN
All up-level domains external to the enterprise, but trusted
by the domain of which the target computer is a member.
DSOP_SCOPE_TYPE_GLOBAL_CATALOG All
domains in the enterprise.
DSOP_SCOPE_TYPE_TARGET_COMPUTER The
target computer.
DSOP_SCOPE_TYPE_UPLEVEL_JOINED_DOMAIN
An up-level domain joined by the target computer.
DSOP_SCOPE_TYPE_USER_ENTERED_DOWNLEVEL_
SCOPE Enables the user to enter a down-level scope.
DSOP_SCOPE_TYPE_USER_ENTERED_UPLEVEL_
SCOPE Enables the user to enter an up-level scope.
DSOP_SCOPE_TYPE_WORKGROUP The work group
joined by the target computer.
Each of the scopes included will be available in the Look-in dropdown list, and the filters and options specified in the remainder
of this record are applied to determine which objects to display
in the list view when the scope is selected. The ability to combine
several scope types together is really handy when the same filter
and options apply to different scopes. Without this, you would
have to specify multiple TDsOpScopeInitInfo entries, one for each
scope. (This is possible but tedious.)
The flScope field is used for two purposes. First, you use it to
specify the scope type that should be selected in the Look-in dropdown list initially. Second, you use it to specify the ADsPath
format of the selected objects from the scope type. Ill return to
this topic later, when I describe how you retrieve the selected
objects from the object picker dialog box. For now, these are the
possible flags and their meanings:
DSOP_SCOPE_FLAG_STARTING_SCOPE Scope is
initially selected in the Look-in drop-down list. Only one scope
can have this flag set.
DSOP_SCOPE_FLAG_WANT_DOWNLEVEL_BUILTIN_
PATH Unless this flag is specified, ADsPaths for downlevel, well-known objects are an empty string.
DSOP_SCOPE_FLAG_WANT_PROVIDER_GC
ADsPaths are converted to use the GC provider.
DSOP_SCOPE_FLAG_WANT_PROVIDER_LDAP
ADsPaths are converted to use the LDAP provider.
DSOP_SCOPE_FLAG_WANT_PROVIDER_WINNT
ADsPaths are converted to use the WinNT provider.
DSOP_SCOPE_FLAG_WANT_SID_PATH ADsPaths
with an objectSID attribute are converted to the form
LDAP://<SID=x>, with x being the hexadecimal digits of
the objectSID attribute value.

Greater Delphi
type
_DSOP_UPLEVEL_FILTER_FLAGS = record
flBothModes: ULONG;
flMixedModeOnly: ULONG;
flNativeModeOnly: ULONG;
end;
DSOP_UPLEVEL_FILTER_FLAGS = _DSOP_UPLEVEL_FILTER_FLAGS;
TDsOpUpLevelFilterFlags = DSOP_UPLEVEL_FILTER_FLAGS;
PDsOpUpLevelFilterFlags = ^DSOP_UPLEVEL_FILTER_FLAGS;
_DSOP_FILTER_FLAGS = record
Uplevel: DSOP_UPLEVEL_FILTER_FLAGS;
flDownlevel: ULONG;
end;
DSOP_FILTER_FLAGS = _DSOP_FILTER_FLAGS;
TDsOpFilterFlags = DSOP_FILTER_FLAGS;
PDsOpFilterFlags = ^DSOP_FILTER_FLAGS;

Figure 7: TDsOpFilterFlags record definition.

The pwDcName field is always set to nil unless youve specified


the DSOP_SCOPE_TYPE_UPLEVEL_JOINED_DOMAIN flag
in the flType field. The pwzADsPath field is reserved and must be
set to nil.
The hr field is set by the Initialize method to indicate whether the
scope could be created. If the Initialize method returns S_OK,
then each scope will have its hr member set to S_OK as well. If the
Initialize method fails, however, the scopes that failed to initialize
have this field set to an error code.
The remaining field, FilterFlags, is used to specify which objects
should be displayed for this scope type. Examples of objects you
can choose are users, computers, and groups. FilterFlags is a record
of type TDsOpFilterFlags that allows you to specify separate filters
for four different modes (its definition appears in Figure 7). First,
theres a distinction between up-level scopes and down-level
scopes. An up-level scope is a global catalog or a Windows 2000
domain that supports the Active Directory Services Interface
(ADSI) Lightweight Directory Access Protocol (LDAP) provider.
A down-level scope is universal, ranging from a stand-alone
workstation to work groups and NT 4.0 style domains. The uplevel scopes are further separated into modes in which the domain
is operating: mixed mode, native mode, or both modes. Figure 8
shows a few of the available flags. For the full list, see the Platform
SDK documentation (see end of article for details).
To make all this information more concrete, Ill show you how to
initialize the dialog box to allow selection of users from the entire
enterprise, or selection of groups from the domain. You start by
initializing COM and then creating the object picker object, as
shown in Figure 9.
Next, initialize the TDsOpInitInfo record to allow multiple
selection, and indicate that youre specifying two scopes:
// 3. Initialize scopes.
FillChar(InitInfo, SizeOf(InitInfo), 0);
InitInfo.cbSize := SizeOf(InitInfo);
InitInfo.flOptions := DSOP_FLAG_MULTISELECT;
InitInfo.cDsScopeInfos := 2;
InitInfo.aDsScopeInfos := @ScopeInitInfo[0];

Next, initialize the two scopes. The first scope adds the domain to
the Look-in drop-down list and sets a filter to display just users. The
29 October 2002 Delphi Informant Magazine

second scope adds the global catalog (that is, the entire directory)
to the Look-in drop-down list, and sets a filter to display only
security groups. Note that youre specifying all three up-level filters,
so this code will work for all up-level type domains (see Figure 10).
So far, youve set up the scopes. What remains is calling the
Initialize method:
if SUCCEEDED(ObjPicker.Initialize(InitInfo)) then begin
// Display dialog box and process selection.
end;
end;
ObjPicker := nil;
CoUninitialize();
end;

The Initialize method returns an HRESULT, with which you


should be familiar if youve done any COM programming. If
the method succeeds, it returns S_OK. If the method fails,
it returns a COM error code. As usual, you should not test
directly against S_OK. Instead, use the SUCCEEDED function.
The most common error is when one or more of the specified
scopes could not be initialized, in which case the return code is
E_INVALIDARG (defined as $80070057 in Windows.pas). This
happens, for example, when you specify one of the domain scopes
and either your computer is not a member of a domain, or the
domain controller couldnt be reached. As I mentioned earlier,
when Initialize fails, the hr member of each TDsOpInitInfo record
that couldnt be initialized is set to S_FALSE. A rather annoying
behavior of the object picker dialog box is that in cases like this, it
displays the error dialog box shown in Figure 11, and there seems
to be no way to suppress this dialog box.
One final note about the Initialize method: You dont have to call
this method each time before invoking the dialog box (described
next). If the dialog box settings dont change between invocations,
theres no need to call Initialize again. On the other hand, if you
do call Initialize again to change the dialog box settings, be aware
that each call completely overwrites the previous one. Initialize is
not cumulative.

Displaying the Dialog Box


Now we have an object picker that is fully initialized and ready to
display. You display the dialog box through the second method of
IDsObjectPicker, InvokeDialog. This method takes two parameters,
a window handle that indicates modality (the dialog box is modal
relative to the specified window) and an IDataObject interface. Ill
ignore this second parameter until the next section, in which I will
describe how to retrieve the selected objects from the object picker.
The InvokeDialog method can return one of three results: S_OK if
the user selected one or more objects and clicked the OK button,
S_FALSE if the user cancelled or closed the dialog box, or a
COM error code if an error occurred. Figure 12 demonstrates
calling the InvokeDialog method and testing the result.

Processing the Selection


Weve seen how to initialize and display the object picker, but
how do you know which objects the user has selected? Well, thats
what the second parameter of InvokeDialog is for. When the user
selects one or more objects and clicks the OK button,
the ppdoSelections parameter returns an interface to an
IDataObject object. This is a standard interface type that pops
up frequently when working with COM. (Truthfully, I cant

Greater Delphi
Up-level flag

Description

DSOP_FILTER_COMPUTERS

Includes
computer objects

DSOP_FILTER_USERS

Includes user
objects

DSOP_FILTER_CONTACTS

Includes contact
objects

Down-level flag

Description

DSOP_DOWNLEVEL_FILTER_COMPUTERS

Includes
computer objects

DSOP_DOWNLEVEL_FILTER_LOCAL_GROUPS

Includes all local


groups

DSOP_DOWNLEVEL_FILTER_ANONYMOUS

Includes the
well-known
security principal
Anonymous

Figure 8: Selected up-level and down-level filter flags.

var
ObjPicker: IDsObjectPicker;
ScopeInitInfo: array [0..1] of TDsOpScopeInitInfo;
InitInfo: TDsOpInitInfo;
DataObj: IDataObject;
begin
CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
if SUCCEEDED(CoCreateInstance(CLSID_DsObjectPicker, nil,
CLSCTX_INPROC_SERVER, IID_IDsObjectPicker,
ObjPicker)) then begin
...

Figure 9: Initializing COM and creating the object picker object.

const
ALL_SECURITY_GROUPS =
DSOP_FILTER_DOMAIN_LOCAL_GROUPS_SE or
DSOP_FILTER_BUILTIN_GROUPS
DSOP_FILTER_GLOBAL_GROUPS_SE or
DSOP_FILTER_UNIVERSAL_GROUPS_SE;
// 1st scope, users from enterprise domain.
FillChar(ScopeInitInfo[0], SizeOf(TDsOpScopeInitInfo),0);
ScopeInitInfo[0].cbSize := SizeOf(TDsOpScopeInitInfo);
ScopeInitInfo[0].flType :=
DSOP_SCOPE_TYPE_ENTERPRISE_DOMAIN;
ScopeInitInfo[0].FilterFlags.Uplevel.flMixedModeOnly :=
DSOP_FILTER_USERS;
ScopeInitInfo[0].FilterFlags.Uplevel.flBothModes :=
DSOP_FILTER_USERS;
ScopeInitInfo[0].FilterFlags.Uplevel.flNativeModeOnly :=
DSOP_FILTER_USERS;
// 2nd scope, security accounts from global catalog.
FillChar(ScopeInitInfo[1], SizeOf(TDsOpScopeInitInfo),0);
ScopeInitInfo[1].cbSize := SizeOf(TDsOpScopeInitInfo);
ScopeInitInfo[1].flType :=
DSOP_SCOPE_TYPE_GLOBAL_CATALOG;
ScopeInitInfo[1].FilterFlags.Uplevel.flMixedModeOnly :=
ALL_SECURITY_GROUPS;
ScopeInitInfo[1].FilterFlags.Uplevel.flBothModes :=
ALL_SECURITY_GROUPS;
ScopeInitInfo[1].FilterFlags.Uplevel.flNativeModeOnly :=
ALL_SECURITY_GROUPS;

Figure 10: Initialization of the scopes.


30 October 2002 Delphi Informant Magazine

Figure 11: Error dialog box displayed by the object picker.

figure out why Microsoft chose to return the data through


this interface instead of directly returning a pointer to the
data). A full discussion is outside the scope of this article, so,
if youre unfamiliar with it, I recommend searching MSDN
for more details. Basically, the IDataObject interface wraps a
TDsSelectionList record. The goal is to get to that record, and
the code in Figure 13 shows how this is done.
The selection list (the TDsSelectionList record) contains an array
of TDsSelection records one for each selected object. Figure 14
shows the declaration of both these records.
The aDsSelection field is an array of TDsSelection records that
contain information identifying each selected object. The
ANYSIZE_ARRAY constant is defined as one. This type of
array is frequently used in the Win32 API and designates an
array that can have any number of entries. To learn the length
of the array (and thus the number of entries it contains), you
use the cItems field.
The cFetchedAttributes field is the number of elements returned
in the avarFetchedAttributes field of the individual TDsSelection
records. We will discuss this in the next section.
The pwzName field contains the relative distinguished name
(RDN) of the selected object. The pwzADsPath field contains the
ADsPath of the object, the exact format of which depends on the
value of TDsOpScopeInitInfo.flScope you set when you initialized
the object picker.
The pwzClass field contains the class name of the object (the
value of its objectClass attribute), and pwzUPN contains the user
principal name (UPN) of the object, or an empty string if the
object doesnt have a UPN.
Ill ignore the pvarFetchedAttributes field for now. That leaves us
with the flScopeType field that indicates from which scope this
object was selected. (Its one of the DSOP_SCOPE_TYPE_*
flags; again, see Figure 8.) Figure 15 demonstrates how to
enumerate the items in the selection list, and print the properties
of each selected object.
Figure 16 shows what the output looks like when I run the code
and select user Marcel van Brakel from the global catalog, and the
Domain Admins group from the domain scope. (The sensur.org
domain is just an unregistered test domain. It doesnt exist outside
my network.)
So far, youve learned how to initialize the object picker, display
the dialog box, and retrieve information about the selected
objects. What you do from here is highly application-specific, but
it probably will entail using ADSI to bind to the selected objects,
and performing some action on them, such as querying them for

Greater Delphi
// Code to create and initialize the object picker omitted.
if SUCCEEDED(ObjPicker.Initialize(InitInfo)) then
begin
// 6. display dialog box
HR := ObjPicker.InvokeDialog(0, DataObj);
if HR = S_OK then
begin
// 7. User selected one or more objects and clicked
// OK; process the selection here.
end
else if HR = S_FALSE then
begin
// User cancelled the dialog box, no selection.
ShowMessage('Dialog cancelled');
end
else
begin
// An error occured, the user never saw the dialog box;
// raise an exception.
OleCheck(HR);
end;
end;

Figure 12: Invoking the object picker dialog box and testing
the result.

var
Format: TFormatEtc;
Medium: TStgMedium;
SelectionList: PDsSelectionList;
...
Format.cfFormat :=
RegisterClipboardFormat(CFSTR_DSOP_DS_SELECTION_LIST);
Format.ptd := nil;
Format.dwAspect := DVASPECT_CONTENT;
Format.lindex := -1;
Format.tymed := TYMED_HGLOBAL;
FillChar(Medium, SizeOf(Medium), 0);
Medium.tymed := TYMED_HGLOBAL;
if SUCCEEDED(DataObj.GetData(Format, Medium)) then begin
SelectionList := GlobalLock(Medium.hGlobal);
try
// Do something with the selection list here...
finally
GlobalUnlock(Medium.hGlobal);
ReleaseStgMedium(Medium);
end;
end;

Figure 13: Getting to the TDsSelectionList record.

some attributes. Thats something the object picker doesnt help


you with. Or does it? As it turns out, maybe it can.

Retrieving Attributes
One of the most common things youll do with the selected
objects is simply retrieve information about them by reading a
number of attributes. The people at Microsoft recognized this
and allow you to perform this action automatically with the
object picker. That means theres no need for additional code
that uses ADSI or LDAP to bind to the objects and query the
desired attributes manually. To see how this works, you need to
reexamine the declaration of the TDsOpInitInfo record (shown
in Figure 5), and look at the two fields we ignored previously:
cAttributesToFetch and apwzAttributeNames.
The apwzAttributeNames field is a pointer to an array of nullterminated Unicode strings that name the attributes to retrieve
for each selected object. The cAttributesToFetch field indicates
31 October 2002 Delphi Informant Magazine

type
PDS_SELECTION = ^DS_SELECTION;
_DS_SELECTION = record
pwzName: PWSTR;
pwzADsPath: PWSTR;
pwzClass: PWSTR;
pwzUPN: PWSTR;
pvarFetchedAttributes: POleVariant;
flScopeType: ULONG;
end;
DS_SELECTION = _DS_SELECTION;
TDsSelection = DS_SELECTION;
PDsSelection = PDS_SELECTION;
PDS_SELECTION_LIST = ^DS_SELECTION_LIST;
_DS_SELECTION_LIST = record
cItems: ULONG;
cFetchedAttributes: ULONG;
aDsSelection: array [0..ANYSIZE_ARRAY-1]
of DS_SELECTION;
end;
DS_SELECTION_LIST = _DS_SELECTION_LIST;
TDsSelectionList = DS_SELECTION_LIST;
PDsSelectionList = PDS_SELECTION_LIST;

Figure 14: The selection-list records.


var
SelectionList: TDsSelectionList;
Selection: TDsSelection;
...
// 9. For each selected object...
for I := 0 to SelectionList.cItems - 1 do begin
{$R-}Selection := SelectionList.aDsSelection[I];{$R+}
// 10. ...display the object fields.
Memo1.Lines.Add(
'Name: ' + string(WideString(Selection.pwzName)));
Memo1.Lines.Add(
'ADSPath: ' + string(WideString(Selection.pwzADsPath)));
Memo1.Lines.Add(
'Class: ' + string(WideString(Selection.pwzClass)));
Memo1.Lines.Add(
'UPN: ' + string(WideString(Selection.pwzUPN)));
Memo1.Lines.Add('');
end;

Figure 15: Enumerating a selection list.

how many attribute names youve specified in the array. After


InvokeDialog returns, you retrieve the selected objects through the
selection list as usual. The requested attributes can be retrieved
from the selected objects through the pvarFetchedAttributes field of
the TDsSelection record. This field contains a pointer to an array of
OleVariants, one element for each requested attribute. The order of
elements in this array is the same as the order you specified for the
apwzAttributeName field in the TDsOpInitInfo record.
Heres an example to make all of this concrete. Say you have an
application that allows the user to send an e-mail to selected users
within the company. To do so, you need the e-mail address of
the selected users, available as the mail attribute. To personalize
each e-mail, you also need the users full name, available as the
displayName attribute. Figure 17 shows how you can use the
object picker to retrieve these attributes.
If you look closely at Figure 17, youll see that I explicitly test
whether the attributes contain a value using the VarIsEmpty
function. This is necessary because the object picker might not
be able to retrieve the requested attributes. This can happen

Greater Delphi
Name: Domain Admins
ADSPath: GC://sensur.org:3268/CN=Domain Admins,CN=Users,DC=sensur,DC=org
Class: group
UPN:
Name: Marcel van Brakel
ADSPath: LDAP://sensur.org/CN=Marcel van Brakel,CN=Users,DC=sensur,DC=org
Class: user
UPN: [email protected]

Figure 16: Output from the demo project.

const
MaxVariantArray = (MaxInt div SizeOf(OleVariant)) - 1;
type
TOleVariantArray =
array [0..MaxVariantArray] of OleVariant;
POleVariantArray = ^TOleVariantArray;
var
Attributes: array [0..1] of WideString;
DisplayName, EmailAddress: OleVariant;
...
begin
...
// 4. Initialize attributes, request displayname
// and email address.
Attributes[0] := WideString('displayName');
Attributes[1] := WideString('mail');
InitInfo.cAttributesToFetch := 2;
InitInfo.apwzAttributeNames := @Attributes[0];
...
ObjPicker.Initialize(InitInfo);
ObjPicker.InvokeDialog(Handle, DataObj);
...
Assert(SelectionList.cFetchedAttributes = 2);
for I := 0 to SelectionList.cItems - 1 do begin
{$R-}Selection := SelectionList.aDsSelection[I];{$R+}
DisplayName := POleVariantArray(
Selection.pvarFetchedAttributes)^[0];
EmailAddress := POleVariantArray(
Selection.pvarFetchedAttributes)^[1];
if VarIsEmpty(DisplayName) or
VarIsEmpty(EmailAddress) then
Error;
// Call imaginary SendMail function.
SendEmail(DisplayName, EmailAddress);
end;

Figure 17: Requesting additional attributes and retrieving


their values.

32 October 2002 Delphi Informant Magazine

for a number of reasons, such as misspelling


the attribute name or the attribute having no
value. When the object picker cant retrieve an
attribute, it simply sets the corresponding array
element to an empty variant instead of raising
an error.

Conclusion

With its many options, the object picker may


seem daunting at first, but I hope this article has
shed some light on how you can use it in your
own applications. I think youll find that after youve worked with
it for a while, the object picker is really straightforward to use and
is very powerful. Nonetheless, I wouldnt dare call myself a Delphi
programmer if I hadnt wrapped the interface in a nice component
that hides some of the intricate details. Youll find just such a
component in the files accompanying this article. It allows you to
completely configure the object picker at design time by specifying
the scopes, filters, and other options through the Object Inspector.
In addition, the component provides methods that handle the
retrieval of the selection set, including additional attributes. I hope
you find it useful.

References
For more information, the following URLs may be helpful:
Project JEDI http://www.delphi-jedi.org
My Web site http://members.chello.nl/m.vanbrakel2
Microsoft Platform SDK http://msdn.microsoft.com
The component referenced in this article is available on the Delphi
Informant Magazine Complete Works CD located in INFORM\
2002\OCT\DI200210MB.

Marcel van Brakel is a freelance programmer specializing in system-level


development for Windows, primarily using Delphi. He can be reached at
[email protected].

New & Used


By Alan C. Moore, Ph.D.

CodeRush 5 and 6
RADified Editor of Your Dreams

everal years ago, I had the pleasure of reviewing the initial CodeRush beta (code-named
Raptor). A lot has changed since those Delphi 3 days. Building on a solid foundation, the
folks at Eagle Software have added some amazing new tools to this product, particularly in
the area of form design. Best of all, CodeRushs open architecture allows you to add your
own tools, transforming Delphi in amazing ways. When I reviewed the beta, I wrote that
CodeRush RADifies Delphi so much that it seems as if youve jumped forward at least one
Delphi generation simply by adding CodeRush to the development environment. That hasnt
gone unnoticed in the Delphi community: CodeRush has won the Best Add-in category of
Delphi Informant Magazines Readers Choice Awards for three years running.
When I began working on this review, I was working with a mature, full-featured version of CodeRush
5 under Delphi 5. (I know many developers are still
working with Delphi 5, so Ill address CodeRush 5
features for that version of Delphi.) As with earlier
CodeRush incarnations, I had to pinch myself to make

sure I was not working with a new version of Delphi.


As I write this, CodeRush 6 has just been released.
Welcome to Delphis next generation, or Delphi 6++.
In this review, Ill discuss both versions of CodeRush.
If youre going to be working with Delphi 5 for the
foreseeable future, you need CodeRush 5. Delphi 6
users will want the many new enhancements available
in CodeRush 6. Ill review the essential features and
explore some of the newer ones.
CodeRush is built on two main components: a
greatly enhanced Object Pascal editor and a series
of powerful plug-ins (many in the form of dockable
panels) that provide powerful utilities. CodeRush
includes sophisticated form-editing capabilities.
Figure 1 shows the Delphi editor with CodeRush
installed. In the gutter to the left of the editor are
code-modification indicators (newly added or modified lines of code). Also there are three of CodeRushs
many special bookmarks. The toolbar is completely
customizable; you can add, remove, and group
buttons as you wish. This screen shot shows several
custom controls Ive added, including my own Project Identifier plug-in. (See Eagle Softwares Web site,
http://www.eagle-software.com, for my article on this.
Look under Resources | White Papers.)

The Editor of Your Dreams

Figure 1: With its keyboard templates, configurable toolbar, navigation tools, and
more, CodeRush may be the editor of your dreams.
33 October 2002 Delphi Informant Magazine

If you could design a custom Delphi editor, what


features would it have? For me, features would
include complete integration with the IDE, keyboard
templates, bookmarks, other code-navigation tools, a
means of keeping track of code changes, and a clipboard with recent entries (history) to paste into your
code. Each of these capabilities was part of the first
version of CodeRush. There are many more now.

New & Used

Figure 2: The Form Layout Manager allows you to automatically


position controls on the form.

Figure 4: Setting tab order with the Form Layout Manager.

Beyond Code Completion


Many would agree that code completion is one of the most helpful
additions to the Delphi code editor. CodeRushs AutoFill Plug-in (new
in version 5) extends code completion to include recent clipboard
operations, in addition to identifiers within a tightly-focused scope,
accelerating coding speed while helping to prevent typing errors. If
you have several identifiers that begin similarly, you can scroll through
all of them (right in the code, using the up and down arrows) and
select the identifier you want. Its just like those auto-complete combo
boxes, but its happening in the code, without any pop-up window to
distract you. In another improvement of Delphis code completion,
CodeRushs AutoFill works even while youre debugging. The AutoFill
plug-in is very special; its architecture allows you to create plug-ins
that extend its functionality (see my File | New column on plug-in
culture in the February issue of Delphi Informant Magazine). Ive done
this myself and plan to release that plug-in soon.

Figure 3: The Form Layout Manager also allows you to name


controls according to your naming convention.

CodeRush 3 provided more than 600 keyboard templates; CodeRush 5 includes nearly 1,000. These templates are shortcuts to
Delphi keywords, Visual Component Library (VCL) identifiers, and
Object Pascal structures. You wont believe how much they speed
up the editing process until youve used them for a while. And these
templates are so intuitive, I often can figure things out without
checking the reference. Further, they are grouped logically within
template rules that provide a strong conceptual framework. For
example, the Assignment Operations group lets you use the equals
sign with various letter combinations to assign values to common
variables (e.g. "=f" expands to ":= False;"); the Code Blocks
group allows you to work with all the standard blocks: begin..end,
try..except, try..finally, if, for loops, and others. Theres even a Template Coach Plug-in that watches what you type in the editor and
makes suggestions about templates you may not know exist.
The editor of your dreams includes the clipboard of your dreams. The
CodeRush clipboard keeps the last nine items cut or copied to the Windows Clipboard. You can browse this clipboard, paste the contents of any
pane into the Delphi editor, and even edit or lock the contents of a pane.
34 October 2002 Delphi Informant Magazine

But theres much more. Versions 5 and 6 include a powerful macro


capability, a feature Ive heard users request. Macros are saved in text files,
so you can edit them if you wish. You also can bind often-used macros
to keystrokes. With a plethora of templates, powerful commands, and
macros, CodeRush truly transforms the Delphi IDE into the editor of
your dreams. However, in addition to writing code, you need to be able
to move through large source files without getting lost.

Superb Navigation and Editing Tools


CodeRush includes many useful navigation tools that allow you to move
around the editor. Bookmarks, which have always been persistent, have
expanded considerably to include a wide range of styles. Similar to bookmarks, the stack-based markers (also persistent) provide an even easier
means of laying a trail through your code. Markers let you jump through
your code in the order in which they were dropped, toggle between locations, or retrace your steps.
This sophisticated collection of navigation tools also includes the
following: a quick-search command that lets you jump to the next
occurrence of the current word, the Method Find dialog box that
transports you to the method you want (even if you only remember
a portion of its name), and the Delphi Drill-down plug-in. The
latter (available for download at Eagle Softwares Web site) enhances
Delphis C+click identifier drill-down feature, and allows you to
bind keystrokes to the drill down and browse buttons.

New & Used


CodeRush includes a number
of built-in panels to address
programming needs: Multiple File
Search, File Manager, Bookmarks,
Hot Files, and Statistics. Other
panels, such as the File Manager
and Hot Files panels, provide
quick access to files, custom
organization of file groups, and
easy importing of files into your
current project. Best of all, CodeRushs open architecture allows
you to create your own panels and
other types of plug-ins.
Figure 5: Surface designers in CodeRush 6.

Text Tools and Panels


CodeRush includes a sophisticated set of text-formatting,
embedding, and translating tools. If youve selected a block of
text, you can perform a number of common operations quickly,
including turning indenting on or off, turning commenting on or
off (in any of the styles), changing case in all of the usual ways, and
even sorting the block. There are also tools to embed the selected
code within various types of blocks, such as begin..end, try..finally,
and so on. Finally, CodeRush includes powerful selectiontranslation commands, such as a smart selection inversion, which
can swap assignment statements, change registry read operations
to write operations, and more; and selection rotation, which can
convert all instances of Top to Left, Right to Bottom, and
vice versa. The former is useful in just about any code you write,
and the latter is useful when your development enters the twodimensional world of graphical programming.

CodeRush remains the ultimate Delphi add-on. It enhances Delphis


editor with powerful navigational tools and a comprehensive set of
1,000 customizable and extendable keyboard templates that provide
quick and easy access to common Object Pascal structures. CodeRush
offers powerful navigational tools, a multi-pane super clipboard, macro
recording and playback, and other editor enhancements. It also includes
powerful plug-ins, some of which are panels that can surround the
main editor window. The latter include a hot list to access commonly
used files, a file-search panel, a file-manager panel, and so on. With
the new Form Layout Manager and form-editing tools, CodeRush
enhances form editing as much as code editing. The Rush API enables
you to write your own plug-ins and customize Delphis IDE.
Eagle Software
12021 Wilshire Blvd., Suite 655
Los Angeles, CA 90025
Phone: (310) 441-4096
E-Mail: [email protected]
Web Site: http://www.eagle-software.com
Price: CodeRush 5 Standard (for Delphi 5) US$249, CodeRush 5
Professional (for Delphi 5) US$449, CodeRush 6 Standard (for Delphi 6)
US$249, and CodeRush 6 Professional (for Delphi 6) US$449.

35 October 2002 Delphi Informant Magazine

Forms and More


The latest versions of CodeRush enter new territory, RADifying
form design as well as code editing. In CodeRush 5, the Form
Layout Manager (Figures 2, 3, and 4) gives you the opportunity
to work with a forms dimensions, placement of controls, tab
order, and much more. You can arrange controls by columns and
rows easily (Figure 2), name controls according to your own or
your companys naming convention (Figure 3), set the tab order
(Figure 4), and much more. Note the graphical aids CodeRush
adds to the form editor. You can enable or disable any of these
graphical elements as needed.
In CodeRush 6, it gets even more exciting (see Figure 5). Delphis
form editor becomes more intelligent, with tab order and component relationships shown visually. Note the buttons above the form
on the right. From left to right, these are Align Palette (opening the
align palette at the lower right), Anchor Palette (opening the anchor
palette at the lower left), Coordinate Checker (note the controls I
still need to align), Lock/Unlock Controls, Magnifier, Quick Connect
(to create connections between controls), Reveal Links, Show Active
Data Sets, Show Unnamed Components, the Super Palette (shown on
the right), and Tab Order (note the numbers on the form).
CodeRush Professional has other useful features. Many developers
are now working with design patterns and the Unified Modeling Language (UML). With CodeRush Professional, you can add
flow charts, UML diagrams, design graphics, and even your own
bitmaps, directly to your source code. All versions can display and
print these graphics, but only the Professional version can create
and edit the diagrams. If youve developed your own graphical representations, you can add them to CodeRushs Diagram
Repository. The Professional edition also includes a Flow Evaluator, which visually highlights breaks in program flow (again, see
Figure 1). This plug-in reveals keywords (Exit, Break, Continue,
etc.) that could change or interrupt the flow of the program at
run time. If you click on the flow-jump icon, the cursor will
jump to the location where the instruction pointer would go if it
encountered that keyword at run time. For a complete solution
matrix, including the differences between CodeRush Standard
and Professional, see the Eagle Software Web site.
CodeRush 5 includes more than 225 commands you can access
with keyboard shortcuts or buttons, or that you can trigger
from within your own plug-ins. CodeRush 6 introduces an
exciting new architecture that implements two-key shortcuts,
thereby expanding exponentially the commands available from
the keyboard. These commands cover the entire spectrum, from
code writing and navigation to standard Delphi commands and

New & Used


bringing up a DOS prompt. There are project- and file-managing functions and some hidden gems, such as the File Managers
Touch command that allows you to change date and time stamps
for selected files.
CodeRush comes with an excellent manual (download it from
the Eagle Software site and judge for yourself ), an online tutorial,
and strong support (especially through the newsgroups). I would
like to see more tutorials on the keyboard templates, in which
users would follow keying instructions and witness the substantial
amount of code they can produce with just a few keystrokes. One
of the strongest areas of CodeRush is the ease with which new
functionality can be produced through plug-ins.

Conclusion
Who needs CodeRush? It might make more sense to ask who
doesnt need it. I think any Delphi developer could benefit from
this feature-rich tool. With the new form-design features, I cant
imagine a single Delphi developer who wouldnt derive immediate benefit. For any developer who writes a significant amount of
Object Pascal code, coding without CodeRush is like coding with
one hand tied behind your back.
I honestly dont think theres a downside to CodeRush. However,
there is one area I should mention. As with any full-featured
tool, expect a considerable learning curve; if you want to receive
its full benefits, you must be prepared to invest some time to
learn CodeRushs features. But dont worry. An excellent tutorial
explains every feature in detail and includes many helpful links, so
you can be productive quickly. You dont need to learn everything
about CodeRush to begin to derive a significant productivity
boost. Ten minutes spent with the tutorial every day will pay huge
dividends in increased productivity as you learn additional ways
of using this multi-dimensional tool.
CodeRushs goal since the start has been to allow you to code at the
speed of thought. This goal may seem unattainable, but I think youll
be amazed at how dramatically CodeRush improves your productivity, and it keeps getting better. No one is more attuned to customer
desires than the folks at Eagle Software. So, if you do much coding
in Delphi, CodeRush should be at the top of your list of add-ons.
Once youve become accustomed to using it, I think youll come to
the same conclusion so many of the rest of us have: Using CodeRush
with Delphi is the only way to code.

Alan Moore is a professor at Kentucky State University, where he teaches music theory
and humanities. He was named Distinguished Professor for 2001-2002. He has been
named the Project JEDI director for 2002-2003. He has developed education-related
applications with the Borland languages for more than 15 years. Hes the author of The
Tomes of Delphi: Win32 Multimedia API (Wordware Publishing, 2000) and co-author
(with John Penman) of an upcoming book in the Tomes series about communications
APIs. He also has published a number of articles in various technical journals. Using
Delphi, he specializes in writing custom components and implementing multimedia
capabilities in applications, particularly sound and music. You can reach Alan on the
Internet at [email protected].

36 October 2002 Delphi Informant Magazine

New & Used


By Alan C. Moore, Ph.D.

Abbrevia 3
A Complete Compression Solution

ile compression has been around for a long time, since the arc files of the 1980s.
Its purpose was the same then as today: to allow quicker transfer of files over the
Internet. By the mid-to-late 1990s, Phil Katzs PKZIP tool had become the de facto
standard, having replaced earlier and slower algorithms.
But what do you need if you want to add
support to your Delphi or Kylix applications for

Class

Description

TAbZipBrowser

Provides read-only access to a PKZIPcompatible archive.

TAbUnzipper

Extends TAbZipBrowsers capabilities and allows


for extraction of files from an archive.

TAbZipper

Extends TAbZipBrowsers capabilities and


provides the ability to add, freshen, or move
files in an archive.

TAbZipKit

Combines the functionality of TAbZipBrowser,


TAbUnzipper, and TAbZipper.

TAbCabBrowser

Provides read-only access to a cabinet archive.

TAbCabExtractor

Extends TAbCabBrowsers capabilities to


allowing for extraction of files from a cabinet
archive.

TAbMakeCab

Extends the capabilities of TAbCabBrowser to


provide the ability to create a cabinet archive
and add files to it.

TAbCabKit

Extends the capabilities of TAbMakeCab to


provide the ability to open an existing cabinet
archive and extract files from it.

Figure 1: Abbrevia 3s main non-visual components.


37 October 2002 Delphi Informant Magazine

file compressing and decompressing? Abbrevia,


a set of components and low-level routines,
provides the complete solution. Winner of
the 2000 Delphi Informant Magazine Readers
Choice Award for Best VCL Component,
Abbrevia supports all the popular datacompression algorithms and types. The first
version of Abbrevia provided support for PKZIP,
and the second version added support for
Microsofts cabinet archives, which are used with
32-bit Windows. The latest version, Abbrevia 3,
adds support for TAR and gzip archives, which
are popular on Linux. Abbrevia 3 also extends
PKZIP functionality to include support for all
the compression methods, including PKZIP
4.0s new Deflate64 algorithm.
As youll see, Abbrevia 3 contains much more
than simple support for compression. This new
version has helper routines and components that
allow you to view archives, select display colors,
and show the progress of extended extraction
operations. Abbrevia 3 also adds support for
Compressed Compound Files, a technology that
lets your users store multiple files in a single file.
The structure of this library is similar to many of
TurboPowers other libraries. It includes both low-

New & Used

Figure 2: This example program, running under Kylix, demonstrates tools for viewing and manipulating a PKZIP archive.

level routines that provide maximum control, and convenient classes


and components to ease the development process. Several base classes
provide a foundation, including TAbBaseComponent, from which
most of the other components are descended; and TAbBaseBrowser,
from which all of Abbrevias non-visual components are derived.
Those non-visual components provide complete support for PKZIP
and cabinet archives (see Figure 1).
The various components provide much of the important
functionality beyond simply browsing an archive. Figure 2 shows
an example program running under Kylix that uses the ZipKit
component. Figure 3, running under Delphi 6, shows another
example program demonstrating support for cabinet files. The
submenu items selected in the screen shot show some of the available
features, including viewing the attributes of cabinet files.
In addition to these non-visual components, the library includes
several visual ones. These include the TAbZipOutline component,
which provides read and write access to a PKZIP-compatible file;
the TAbZipView component, which provides a grid display of
various archive types (PKZIP, gzip, TAR, or gzipped TAR); and the
TAbCabView component, which provides a grid display of a cabinet
archive. Figure 4 describes other key classes and components.
If youve used earlier versions of Abbrevia, youll be pleased to
learn that this latest version not only adds support for new archive
types, it adds new functionality. Much of that functionality is
implemented using the classes and components shown in Figure 4.
In addition to the classes listed, the new TAbCompoundFile class
provides the ability to store multiple, compressed files as part of a
single file. That emulates the functionality of Microsofts Structured
Storage mechanism, without relying on COM interfaces.
You may want to give users a way to save data on multiple
floppies. Abbrevia provides functionality compatible with PKZIPs
disk spanning, automatically prompting the user for another disk
when needed. Of course, it can extract files from spanned disk
archives as well. It also supports the creation of self-extracting
ZIP files, on-the-fly compression with streaming classes, and
background compression. Visit TurboPowers Web site to see code
examples implementing these last two tasks.

Documentation and Support


As with other TurboPower libraries, Abbrevia 3s documentation
(the help file and the nearly 370-page manual) is excellent. The
38 October 2002 Delphi Informant Magazine

Figure 3: An example program demonstrating tools for working


with Windows cabinet archives.

Class or Component

Description

TAbArchiveItem

Describes a single item in an archive.

TAbArchiveStreamHelper

Specifies abstract behaviors of an


abstract archives stream-style interface
with methods for extracting or adding
archived data and interpreting or
setting item-specific header data.

TAbZipItem

Describes a single item in a zip archive.

TAbZipArchive

Describes an entire zip archive.

TAbGZipItem

Describes a single item in a gzip


archive.

TAbGZipArchive

Describes an entire gzip archive.

TAbGZipStreamHelper

Implements the methods of


TAbArchiveStreamHelper for working
with gzip archives.

TAbTarItem

Describes a single item in a zip archive.

TAbTarArchive

Describes an entire TAR archive.

TAbCabItem

Describes a single file in a cabinet.

TAbCabArchive

Low-level class that encapsulates a


cabinet archive and interfaces with
the cabinet.dll to compress and
extract files.

TAbMeter

Displays a specialized progress bar to


monitor an archives progress event
and display the state of the archive
operation as it progresses.

TAbMakeSelfExe

Provides a mechanism that simplifies


the process of building a self-extracting
zip archive.

TAbDirDlg

Provides a dialog box for selecting a


directory.

Figure 4: Other key classes and components add considerable


functionality to this library.

New & Used

Abbrevia 3 is the complete solution for Delphi and Kylix file


compression. It provides high- and low-level support for the
most popular archive types, including PKZIP, cabinet, gzip,
TAR, and gzipped TAR. It includes example programs and full
source code. The manual is well organized, comprehensive, and
very well written. In sum, Abbrevia 3 is the data-compression
solution for Delphi, C++Builder, and Kylix development.
TurboPower Software Company
15 North Nevada Avenue
Colorado Springs, CO 80903-1708
Phone: (800) 333-4160 within the United States; (719) 471-3898
outside the United States.
Fax: (719) 471-9091
E-Mail: [email protected]
Web Site: http://www.turbopower.com
Price: US$229; upgrade from version 1.x, US$149; upgrade
from version 2.x, US$99.
manual has been expanded considerably and includes all the key
properties and methods of the classes, along with information on
using the low-level routines. The manual has many helpful code
fragments, and excellent example projects demonstrate features
and capabilities. Excellent support is provided by e-mail, online
newsgroups, and phone. Questions posted to the newsgroups
usually generate responses the same day. Minor upgrades are
available as free downloads.

Conclusion
Abbrevia 3 is a powerful and flexible encryption and decryption
library. For adding data compression to your applications,
Abbrevia 3 provides the complete solution. It supports all the
most popular methods of compression and decompression and
provides both low-level routines and high-level classes and
components to access these operations. You can learn more
about this library by visiting the TurboPower Web site. You can
download a trial version from the site. As with the companys
other products, TurboPower provides a 60-day money-back
guarantee, the most generous in the industry.

Alan Moore is a professor at Kentucky State University, where he teaches


music theory and humanities. He was named Distinguished Professor for
2001-2002. He has been named the Project JEDI director for 2002-2003.
He has developed education-related applications with the Borland languages
for more than 15 years. Hes the author of The Tomes of Delphi: Win32
Multimedia API (Wordware Publishing, 2000) and co-author (with John
Penman) of an upcoming book in the Tomes series about communications
APIs. He also has published a number of articles in various technical
journals. Using Delphi, he specializes in writing custom components and
implementing multimedia capabilities in applications, particularly sound and
music. You can reach Alan on the Internet at [email protected].

39 October 2002 Delphi Informant Magazine

File | New
Directions / Commentary

Delphi Job Search

he genesis of this article was the 2001 Borland Conference in Long Beach, California. I was in a large corridor
reading a bulletin board, and noticed a particular announcement from a placement firm specializing in Delphi
programmers. I thought: This is encouraging; a little respect for Delphi for a change. Then something unexpected
happened. A guy walked up to me and asked me what I thought about his ad. I spent the next half hour or so
talking with Aram Janigian, learning about his company and the Delphi job market.
The focus of this article is on the Delphi job market and the various
resources available for job seekers. Since Ive had only limited personal
experience (my Delphi work is mainly in research and education) I have
drawn from two Internet lists to which I subscribe. One is the Delphi
Advocacy Group (TDAG) at [email protected]. The other is one
of the best sources of information on Delphi jobs, the Delphi Job List at
http://www.elists.org/mailman/listinfo/delphi-jobs. People post a variety
of messages to this list, from resumes to recruitment announcements,
relocation hopes, and general information about a new job.
Recruiting Delphi developers. We can get some clues about the
Delphi job market by considering the job notices themselves. Such
notices can be simple or detailed. For example, heres a brief notice
for a short-term contract: Creating WWW and desktop-based
applications in Delphi, ASP and SQL Server, experience in COM,
MTS would be beneficial. Good communication and problem
solving skills, with the ability to rapidly master new environments.
Most notices offer more specifics. Heres one that Ive abbreviated
slightly. It begins with the position title (Senior Programmer Analyst),
a salary range, company location, and information on job benefits. Its
followed by the Job Description and Required Experiences.
JOB DESCRIPTION:
Understand user and system requirements.
Develop program specifications.
Perform program development.
Strong application development experience is the key.
The ideal candidate would be someone who can perform object-oriented
programming using Delphi or JAVA running on W/NT System.
REQUIRED EXPERIENCES:
Manufacturing or data collection experience.
Application design.
Application development.
Program testing.
It concludes with additional experience that would be considered
pluses, and contact information.
40 October 2002 Delphi Informant Magazine

The recruiter for the company that wrote the second, more
detailed, notice shared with me some of the typical approaches.
She pointed out that most recruiters use the actual posting the
company sends them with a few minor changes to disguise the
company its from. [Our company] uses a format we call the
dimensional search to help us write our postings ... although I
have to admit sometimes it is difficult to get all the information
you want from a client. But thats why you see such a variety of
postings and information. All postings have job title, description,
and requirements (depending on the job board). When you leave
a posting vague, you get a wide response of resumes some
recruiters prefer this. Ive found that in this market, being as
descriptive as possible causes a better flood of resumes. Not
everyone agrees on this of course.
The world of Delphi jobs. What level of developers are
companies looking for? Regarding active Delphi developers, Mr.
Janigian provides the following breakdown of PDA placements:
Our permanent placements are broken down into the following
mix: 75% are Senior Delphi Developers with 3 to 7 years of
Delphi development experience, strong RDBMS experience and
Web deployment skills, 10% are Intermediate Delphi Developers
with 2 to 3 years of experience, 10% are Junior Delphi Developers
with 1 to 2 years of experience, 5% are Team Leaders and Project
Managers with 7 years of functional and technical experience.
Online resources. In recent years the number of Internet sites that
include Delphi job information has mushroomed. Ill mention
just a few. delphiworld.com (http://www.delphiworld.com) is a
free site for companies looking for developers. If youre looking
for a site with links to Delphi and IT job sites, go to the UNDU
job list at http://www.undu.com/deljobs.htm. Delphi Sites and
Resource Links (http://calitzbros.com/dpsites.htm) has links to
Delphi job sites and many other Delphi-related sites (a number
of these links no longer work). Borland has a rather active
newsgroup devoted to Delphi jobs (news://forums.inprise.com/
borland.public.delphi.jobs). And there are a number of
Delphi sites that have job pages; for example, the Informant

File | New
Communications Group page at http://careers.informant.com.
There are many more sites out there; a Google search for Delphi
Jobs produced over 3,000 references.
Personal Delphi Agents. Originally known as permIT, Personal
Delphi Agents (PDA) specializes in placing Delphi developers of
all levels with clients throughout the United States. The company
was started in response to the need to provide something beyond
temporary consulting permanent IT staffing. Their first
involvement with the Delphi community took place in mid-1998
in a big way: they recruited 80 Delphi developers for a Fortune 500
financial company in Cincinnati, Ohio that was converting Visual
Basic applications into Web-enabled Delphi applications. By March
2001, PDA had 1,500 Delphi developers in their resume database.
PDA continues to grow and is now offering Delphi Certification
Examinations. I contributed a series of questions along with
other writers such as Marco Cant and Cary Jensen. Naturally,
I had the opportunity to try the exam. Although there was more
emphasis on database issues than I expected, I found the test to
be quite comprehensive. You can learn more about PDA from its
Internet site at http://www.PersonalDelphiAgents.com.
Clearly, Delphi provides an excellent basis for many types of
development, but most projects require the use of additional
technologies. People often put database skills at the top of the
list. Mr. Janigian stresses that Database skills are very important.
70% of our clients and Delphi developers are using Microsoft
SQL Server 7.0/2000, 20% are using Oracle 7.0/8.0/8i and 5%
are using Sybase, Informix, DB2, and everything else.
Although Delphi may suffice as the primary development tool,
other technologies especially database technologies are
essential for any developer who wants to be competitive. I remain
committed to discussing those languages and resources. In past
columns Ive written about many ancillary technologies, including
UML, XML, and HTML. Looking to the future, I plan to write
about .NET later this year. Until next time.
Alan C. Moore, Ph.D.

Alan Moore is a professor at Kentucky State University, where he teaches music


theory and humanities. He was named Distinguished Professor for 20012002. He has been named the Project JEDI Director for 2002-2003. He has
developed education-related applications with the Borland languages for more
than 15 years. Hes the author of The Tomes of Delphi: Win32 Multimedia
API (Wordware Publishing, 2000) and co-author (with John Penman) of an
upcoming book in the Tomes series on Communications APIs. He also has
published a number of articles in various technical journals. Using Delphi,
he specializes in writing custom components and implementing multimedia
capabilities in applications, particularly sound and music. You can reach Alan on
the Internet at [email protected].

41 October 2002 Delphi Informant Magazine

You might also like