Vous êtes sur la page 1sur 37

May

2003

Volume 9 Number 5

INSIDE
O N

T H E

C O V E R

On the Net

IntraWeb Intro

Reviewed: Grebar Systems PrintDAT!; Tools&Comps TUsersCS Security Component

Noel Rice introduces IntraWeb with nothing less than six complete example
projects. Bundled with Delphi 7, the AtoZed tool offers true RAD Web
development that makes building Web apps as easy as creating standard
Windows programs. Be careful, though; Noel warns that IntraWeb is addictive.

Delphi Informant
www.DelphiZine.com

The Complete Monthly Guide to Delphi Development

May 2003 vol. 9, no. 5

F E A T U R E S
On Language

10

After Errors
If you dont already employ exceptions in your Delphi code shame on you!
and read Bill Todds exception primer immediately. And even if you already
regularly use try..finally and try..except blocks, Ill bet there are still some
valuable insights in Bills latest offering.

.NET/Win32 Interop

Building a .NET-ready

The Lighter Side of Software

Using the COM+

Greater Delphi

15

$5.99 US

The Shared Property Manager

Matthew Hess introduces the COM+ Shared Property Manager (SPM),


affectionately known as the spam, and demonstrates how to use it to store
and share information about application configuration and state, and cache
static information thats commonly needed but expensive to obtain.

Employing C#

Bill Todd on Exception Handling

Framework

Development

Shared Property Manager

$8.99 CAN

Cover Art by Arthur A. Dugoni Jr.

Foundation Class

19

.NET/Win32 Interop: Part II


Fernando Vicaria completes his two part series by showing us how to use
managed code from Win32 applications, demonstrating techniques to access
Win32 code from .NET (and vice versa), and demonstrating some of the
language-independent capabilities of .NET. Get ready for some C#!

In Development

25

A .NET-ready Framework
Years in the making, Bob Fleischman and Seth Weiner take advantage of
Delphi interfaces to create a framework that allows Delphi developers to enjoy
the benefits of .NETs data-type object wrappers. They also offer compelling
arguments for the adoption of OOP, and of course example code.

Toward the Light

30

Lighter Side of Writing Software


Hell, I could write a program better than this in two days! Loren Scott reveals
the secrets of software creation, shares rsum dos and donts, offers time
management techniques and more!

R E V I E W S
32 PrintDAT!

Product Review by Clay Shannon

34
1

TUsersCS Security Component

Product Review by Lee Inman

DELPHI INFORMANT MAGAZINE | May 2003

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

T O O L B O X
combit Announces List & Label 9.0

combit recently released List & Label


9.0, a database-independent development tool for report, label, and form
printing functions and Web reporting.
List & Label consists of the print
engine and the Designer. The Designer
is displayed in Office-XP style and
features dockable and floating toolbars
and tool windows. Rarely used tool
windows are quickly available without
using valuable space because multiple
windows can be docked together and
then brought to the foreground.
The Designer offers many other new
features. With the new OLE-Object
Container, OLE-Server documents
(i.e. from WinWord, Excel, Visio, or
MapPoint) can be integrated in List &
Label; the object can be brought to the
foreground for editing with a doubleclick. With the Property Window,
dynamic layouts can be created with
formulas as contents. Also with the
new Property Window, various dialogs
are presented uniformly in outline. The
Property Window allows dynamic font

presentation, color assignment, dynamic table columns, or special property


changes. Also new is the .NET component, with which you can integrate
your own objects and functions into
the Designer.
Version 9.0 includes new export formats in addition to PDF, BMP, EMF,
JPEG, XML, HTML, MHTML, and RTF.
The Excel-exporter operates independently of an Excel installation on the
PC that will perform the printing. Also,
List & Label now offers a file format
commonly used in document management systems, TIFF, and the text export
module can be used as a data converter to change any format into text.
List & Label is available in English
and German and can process nearly all
character sets (including Far Eastern).
The Designer will be available in various
languages (the previous version was
available in 19). The DTP-Designer offers
comprehensive designing tools and a
multitude of filters and layout options
that can be configured via drag and

Raize Components 3 Available


Raize Software announced
Raize Components 3, the latest
release of the companys VCL
component library for Delphi
and C++Builder.
Raize Components is a
user interface design system
focused on simplifying the task
of developing user interfaces.
More than 30 new components
have been introduced to the
new version, bringing the total
to over 115. In addition, all
components in the library have been
enhanced, either in appearance, functionality, or architecture.
Raize Components 3 provides developers with plenty of power and flexibility without sacrificing ease-of-use.
For example, with Raize Components
3 you can create a single user interface
that will look and feel the same under
all versions of Windows (Windows 95
to Windows XP) or you can have the
interface pick up the default style used
in each version of Windows.
Raize Components 3 supports Delphi
5, 6, and 7, and C++Builder 5 and 6.

DELPHI INFORMANT MAGAZINE | May 2003

Documentation is provided through an


extensive context-sensitive online help
system. Raize Components 3 also features One-Step Installation, Automatic
Help Integration, and Dynamic Component Registration. Additional details on
what has changed in the new release
are available from the Raize Software
Web site.
Raize Software, Inc.
Price: US$349 (includes complete source code for
all components, packages, and designers); registered
users of Raize Components 2.x can upgrade to Raize
Components 3 for US$179; team and site licenses
are also available.
Contact: sales@raize.com
Web Site: www.raize.com

drop. The creation of graphic analyses,


diagrams, and statistics is possible with
the extensive charting feature.
combit GmbH
Price: Prices may vary depending on exchange rates;
check the combit Web site for complete details.
Contact: pr@combit.net
Web Site: www.combit.net

FutureWare Releases
Palm Conduit Installer
Object
FutureWare announced Palm Conduit
Installer Object, an object used to examine, install, register, and uninstall Palm
conduits, programs, databases, and notifiers. It is a validator and wrapper for
the various Palm API calls. By hiding
the complexity and inter-dependence of
their use it allows an auto-installer to be
integrated quickly and easily, without
requiring any specialized knowledge.
The object is primarily used to
automatically install a Palm conduit
and the associated Palm programs
(and optional notifier) when the main
program is started, without requiring any user action. Documentation
includes full object description, tips
and tricks, references, and code samples from production products. Palm
Conduit Installer Object is compatible
with Delphi 5, 6, and 7.
For more information, or to download
a free evaluation copy, visit the FutureWare Web site.
FutureWare SCG
Price: US$29
Contact: (714) 446-0765
Web Site: www.futurewaredc.com/pcio

T O O L B O X
ModelMaker Code Explorer 1.14
ModelMaker Tools released ModelMaker synchronizes the editors method decCode Explorer 1.14 for Delphi 5, 6, and
laration header with implementation
7. This code browser and refactoring and header, and vice versa; Extract Methrestructuring tool is a substitute for the
od, which creates a new method in the
standard Code Explorer that ships with
current class using the selected text
Delphi. It shows classes (inheritance)
as main body; Extract Class, Interface,
and members (fields, methods, properwhich inserts a new class and converts
ties) in two views. It features InstantEdit- the selected members; Add field, mething and IntelliReplace; editing is as simod and property, which adds a new
ple as selecting options in dialog boxes
member and takes the word at the curand performing a drag and drop.
rent IDE editor position as input; Copy
There are many new features in
to/Move to, which copies the selected
this updated version. Most notably is
members to another class or interface;
IntelliReplace, which acts like a smart Create Factory Method, which creates
and instant whole words search
a new class method returning a new
and replace. For example, class and
instance of the class; Introduce Paramfield name changes are propagated in
eter Object, which converts method
source code.
parameters to a new class with one
Many new refactorings have
property for each parameter; Rename
been added, including Synchronize
Parameter, which renames a methods
Implementation/Declaration, which
parameter and propagates the new

name in the methods source and


declaration; and Rename Local, which
renames an identifier within a method
or procedures source.
Most commands can now also be
invoked from the IDE editor and toolbars and then operate on the entity
at the current editor position rather
than the selected entity in the explorer.
Also, a user-definable Default Sorting
Mode has been added. A single click
will now sort class interface and implementation according to these settings.
A free fully functional demonstration
is available at the ModelMaker Tools
Web site. Registered customers can
receive a free update.
ModelMaker Tools BV
Price: US$99 (single-user license)
Contact: info@modelmakertools.com
Web Site: www.modelmakertools.com

/n software Releases IP*Works! CC for the Microsoft .NET Framework


/n software released IP*Works! CC for
Microsoft .NET, a comprehensive suite
of programmable credit card processing
components that provide secure real-time
validation, authorization, and transaction
settlement.
IP*Works! CC contains components for
communicating with major Internet payment gateways, as well as components
that are certified by Visa International for
compliance with the Visa Internet Payment Gateway System (Visa IPGS) and
Visa 3-Domain Secure (Visa 3-D Secure).
IP*Works! CC is new to /n softwares
IP*Works! product line. IP*Works! CC
includes components for clearing transactions directly with Visa IPGS, a com-

ponent for authorizing Verified By Visa


cards through Visa 3-D Secure, as well
as components for processing transactions with every major Internet payment
gateway.
Visa IPGS is an IP-based solution for
credit card processing that communicates
directly with Visa Servers for authorization and settlement of credit card transactions. IPGS provides trusted Visa security and reliability and supports all major
card brands, including Visa, MasterCard,
American Express, Novus/Discover Card,
Diners Club, and JCB.
Built to reduce fraudulent purchases
by verifying purchaser identity during
online transactions, Visa 3-D Secure

is a credit card authorization program


implemented by Visa International. The
primary benefits include a reduction in
disputed transactions and charge backs
and their resulting financial expense.
IP*Works! CC for Microsoft .NET is
available for purchase or download from
the company Web site or from authorized resellers worldwide. IP*Works!
Subscription customers will automatically receive free development licenses for
IP*Works! CC as part of their 1st Quarter
2003 update.
/n software inc.
Price: Visit Web site for complete pricing options.
Contact: info@nsoftware.com
Web Site: www.nsoftware.com

Programming .NET
Web Services

C++Builder 6
Developers Guide

Programming
ASP.NET

UML in a
Nutshell

Alex Ferrara and Matthew MacDonald


OReilly & Assoc.
ISBN: 0-596-00250-5
Cover Price: US$39.95
(396 pages)
www.oreilly.com

Satya Sai Kolachina


Wordware Publishing
ISBN: 1-55622-960-7
Cover Price: US$49.95
(507 pages, CD-ROM)
www.wordware.com

Jesse Liberty and Dan Hurwitz


OReilly & Assoc.
ISBN: 0-596-00171-1
Cover Price: US$49.95
(944 pages)
www.oreilly.com

Sinan Si Alhir
OReilly & Assoc.
ISBN: 1-56592-448-7
Cover Price: US$24.95
(273 pages)
www.oreilly.com

DELPHI INFORMANT MAGAZINE | May 2003

O N
INTRAWEB

T H E

N E T

DELPHI 7

By Noel Rice

IntraWeb Intro
Truly RAD Web Development

P
A)
B)
C)
D)
E)

op quiz time! Dont worry, though; theres only


one question: Which of these tools makes Web
development RAD?

ISAPI Web Modules


ASP Objects
WebSnap
IntraWeb
All of the above

If you answered All of the above, you get partial credit.


Each of these technologies helps develop Web applications more rapidly, but what were really after is something very Delphi-like, a drag-and-drop, componentbased ease that IntraWeb handles nicely (option D for
those of you still scoring).

Try this: Select File | New | IntraWeb | Stand Alone Application,


then supply a directory path for the application to live in.
Now look at the Project Manager (View | Project Manager)
and double-click the IWUnit1 unit to bring up the
designer.
Say! This looks familiar; it looks like a form. Hey! It is a
form. Place a TIWButton from the IntraWeb Standard tab
of the Component palette on the form as shown in Figure 1
(I feel a Hello world coming on!). Then double-click the
button and code the event handler as follows:
procedure TformMain.IWButton1Click(Sender: TObject);
begin
WebApplication.ShowMessage('IntraWeb actually IS RAD!');
end;

Youre probably saying to yourself, Now if this is Web


development, I cant just hit 9 and run the thing
can I? Go ahead and indulge, because a standalone
application has a Web server built-in. Standalone Web
projects can be run and debugged like applications,
because they are applications. (And dont worry that
youll be locked in; you can change the executable into
an ISAPI DLL using two lines of code).
The first window that appears after hitting 9 is the
built-in application server (see Figure 2). Click the Launch
button to see the new application running in the browser
(see Figure 3). Note: These examples are based on the 5.0x
version of IntraWeb that ships with Delphi 7. The 5.1x
version (available from www.AtoZedSoftware.com) goes
further to making IntraWeb intuitive and simple to use.

Figure 1: Working in the form designer.

DELPHI INFORMANT MAGAZINE | May 2003

I suppose the worry now is that while that was easy,


everything from this point on will have you seeking therapy. My experience is that, with few exceptions, IntraWeb
is addictive and gets better with use, reminiscent of first
discovering Delphi. As proof, I offer six complete example
IntraWeb projects that accompany this article (see end of
article for details).

On

the

N e t

IntraWeb Intro

Figure 2: The standalone application server.

Its Not Traditional Windows


Development
Web development, however, is different from Windows
forms development, so keep these points in mind:
Events dont run in parallel with the display of the Web
form. The very nature of Web forms dictates that events
are completed before the page is displayed.
Only one Web page is shown at a time.
You dont free forms, you release them. Its up to
IntraWeb to actually free form instances.
IntraWeb maintains an internal list of forms so that as
forms are hidden, previous forms on the list are revealed.
Session management is handled by IntraWeb and is
transparent for you as the developer. To paraphrase
the IntraWeb manual, How do you manage state in
a Windows app? Exactly you dont. What you do
want to keep in mind is that each user that hits the
Web server gets their very own session; while Web
servers dont maintain state information regarding sessions, IntraWeb does.
IntraWeb components seem to be visual and act like
visual controls programmatically, but they are in fact
non-visual components visually represented at design
time, and rendered to the nearest browser-compatible
equivalent at run time.
IntraWeb Forms: Navigation and
Communication
How does IntraWeb handle navigation between multiple
forms? Using the previous example as a starting point,
add a second form from File | New | Other | IntraWeb | Application Form. Name the form formSecond and save the unit as
USecond.pas. Add a TIWButton, double-click it, and add the
following code:
procedure TformSecond.btnHideClick(Sender: TObject);
begin
Hide;
end;

To the main form, add USecond to the uses clause. Name


the button btnShow, and add the code shown here:
procedure TformMain.btnShowClick(Sender: TObject);
begin
with TformSecond.Create(WebApplication) do
Show;
end;

DELPHI INFORMANT MAGAZINE | May 2003

Figure 3: Running the standalone IntraWeb application in the browser.

Test this by running the application, click the main


forms Show button, and finally click the Hide button from
the second form.
Now Toto, I get a distinct not-in-Kansas feeling about this
last set of code. True, but its not that different. In the previous button-click event handler, we created the form instance
with WebApplication as the owner. WebApplication, as you
might have worked out, is the IntraWeb application object
and must be passed as owner in form creation so that
WebApplication can maintain its internal list of forms.
How can you communicate between forms? Access the
Response object? Pass a query string? IntraWeb allows you
to access a TWebResponse object if you really want to, but
the key here is to forget ASP objects, forget Web modules,
and forget WebSnap. Think Delphi, because the application
simply reads and writes to available properties on the second form. Place a TIWLabel on the second form, and name
it lblEmail. On the main form, add a TIWEdit control, and
code the button-click event handler as follows:
procedure TformMain.btnShowClick(Sender: TObject);
begin
with TformSecond.Create(WebApplication) do begin
lblEmail.Text := 'Your email address: '+edtEmail.Text;
Show;
end;
end;

Run the application, enter an e-mail address, and click the


OK button (see Figure 4). The button click will display the
second form, complete with text originating from the first
form (see Figure 5).
Note: The automatic form and session management discussed so far comprise what IntraWeb calls Application
Mode. Using Page Mode, IntraWeb can adapt to other systems like WebBroker and WebSnap applications. This article
focuses on Application Mode.

On

the

N e t

IntraWeb Intro

IntraWeb Components
Use a sampling of IntraWeb Standard components to get
a feel for the flow of IntraWeb development. For example,
the TIWRegion component is a substitute for TPanel. Set
the Align and Anchor properties to manage real estate on
the form. A favorite technique is to set all four anchors
to False, which causes the region to stay centered as
the browser window is sized. Setting the region color to
clGray, or another light, neutral color, provides a clean
entry area on the form. When displayed in the browser
the region resolves to a standard HTML table.

What technique do you use to place a menu on a Web page?


Again, think Delphi and simply add a standard TMainMenu
to the form and define menu items as if programming a standard Windows application. Its okay to add menu subitems.
When the menu design is finished, add a TIWMenu and associate it with the TMainMenu using the AttachedMenu property. Use the Orientation property to toggle between vertical
menus placed along the left side of a Web page, or horizontal
for placement along the top of the page. Edit the MenuStyle
and MenuItemStyle properties to tailor the behavior of the
title-level menu titles and menu items.

What Does Version 5.1 Offer?

Figure A: Creating a new Web application in IntraWeb 5.1.

Figure B: Filling the application details on the IntraWeb Application Wizard.

Does version 5.1 make things any easier? In version 5.0 you
must select from a slew of icons, then save all of your files
before you can even start. In version 5.1 you get a wizard to
simplify the process. Just choose File | New | Other | IntraWeb |
IntraWeb Application Wizard (see Figure A).

Digital Assistants handheld devices such as cell


phones and palmtops). The last option, Use ISAPIThreadPool,
automatically includes a unit that creates and maintains a
pool of threads for better performance in ISAPI DLLs only.

The IntraWeb Application Wizard in 5.1 also does a better


job of organizing the available choices (see Figure B). You
can create:
A standalone application like the 5.0 examples in this
article.
A Windows Service capable of running a Web server
unattended.
An ISAPI DLL.
Apache 1.x and 2.x shared modules.

Certainly there are many differences in project and unit


code, and some differences in component implementation,
as well. Aside from that, the principal difference between
versions is that in 5.1 you can run the application without
saving your files,
just as you can in a
straight Windows
application.
To add forms select
File | New | Other |

If you want a data module in your project, definitely


use the Create DataModule checkbox in the Options group;
additional code is written for you automatically so that
DataModules work correctly in a threaded environment.
Likewise, selecting the Create User Session checkbox will
automatically code a thread-safe TUserSession object
where you may add global variables. You may be
wondering, What is the Create Main form as 3.2 checkbox
doing there? Did I miss a version somewhere? 3.2 refers
to the HTML 3.2 support in IntraWeb 5.1 for PDA (Personal

DELPHI INFORMANT MAGAZINE | May 2003

IntraWeb | New Form

(see Figure C). From


here you can create
forms for page mode
integration with other
products, or forms
to use in IntraWebmanaged applications
for both standard
HTML and 3.2.

Figure C: Adding a new form to an IntraWeb


application.

On

the

N e t

IntraWeb Intro

Figure 4: Running formMain in the browser.

Figure 6: A Web page running in the browser incorporating IntraWeb Standard


components.
procedure TformMain.IWButton1Click(Sender: TObject);
var
I: Integer;
begin
with TformSecond.Create(WebApplication) do begin
with rgnBottom do
// Iterate only controls on the region.
for I := 0 to ControlCount - 1 do
// Look only at TIWCheckbox.
if Controls[I] is TIWCheckBox then
// Cast the control element as a TIWCheckBox.
with Controls[I] as TIWCheckBox do
// Add text in each of the checked items to
// TIWText lines displayed on second page.
if Checked then
txtResults.Lines.Add(Caption);
Show;
end;
end;

Figure 5: Showing the data from the main form in formSecond.

Figure 7: Iterate the controls array for a region looking for checkboxes.

Images are displayed on the form using TIWImage or


TIWImageFile. TIWImage works like a standard TImage and
is saved as part of the form. TIWImageFile has an ImageFile
property that stores a path to a local file name or external URL.
TIWLink and TIWURL look nearly the same, so what are they
for? TIWLink is used to navigate between Web pages in your
application; its essentially a button that looks like a hyperlink.
Delete the button from the main form and add TIWLink, set
its OnClick handler to use the old btnShow OnClick handler,
and set the Caption property to something amusing like My
Second Page. TIWURL also looks like a hyperlink, but has no
OnClick event. Instead, a URL property can be set to an external Web site address, www.borland.com for example, which
will automatically navigate to that site when clicked.
Figure 6 displays a sampling of IntraWeb Standard components: TIWImage, TIWMenu, TIWRegion, TIWButton, TIWURL,
TIWLabel, and TIWCheckbox.
Getting the output from most IntraWeb Standard components is
fairly intuitive, and they map to their Windows form counterparts: Edit boxes have text, memo components have lines, and
menu items have captions. Checkboxes have Boolean Checked
7

DELPHI INFORMANT MAGAZINE | May 2003

Figure 8: The results of iterating IntraWeb checkboxes on a region.

properties, but it may not be clear how to examine them as a


group. Checkbox selections can be collected off the page using
the venerable controls array in exactly the same manner as you
might do for a Windows application. After creating the form,
iterate the controls array for a region looking for checkboxes
and display the captions in the TIWText control on the second
page (see Figures 7 and 8).
You may have noticed the purpose and relationship of the
components dealing with text on the IntraWeb Standard tab

On

the

N e t

IntraWeb Intro

<H1>TIWText Properties</H1>
Set <i>RawText</i> to <b>True</b> to pass formatted HTML to the browser.
Set <i>WantReturns</i> to <b>True</b> so that a line break in the Lines
property will show in the browser. Set WantReturns to <b>False</b>
if you want the browser to manage the line breaks.

Figure 9: Sample HTML.

Lines property of a TIWText component, and


set WantReturns and RawText to True. The
resulting output is shown in Figure 10.

IntraWeb also ships with three other component tabs. Components on the IntraWeb Control tab fall loosely into three categories:
Components that integrate with other non-IntraWeb
systems like WebBroker and WebSnap.
Components called Layout Managers can extend the
IntraWeb UI. For example, you could write an XML/XSL
Layout Manager and use it instead of the default layout.
Client side datasets hold lists of information in JavaScript
arrays on the client to prevent trips back to the server. The
two components implemented are IWClientSideDataSet and
IWClientSideDataSetDBLink.
IntraWeb Client Side components contain label, navigator, grid,
and charting components that read from IWClientSideDataSet
and IWClientSideDataSetDBLink. Data-aware components are
grouped on the IntraWeb Data tab of the Components palette,
and have counterparts for many of the standard Data Controls, including the database navigator, grid, label, edit, image,
checkbox, lookup lists, and combo boxes.

Figure 10: HTML formatting in a TIWText component.

You dont have to write code to use the data-aware controls.


To get started using TIWDBGrid, simply assign the DataSource
property (the DataSource component should reference an
active dataset). The grid shown in Figure 11 displays data
from the Events.db table located in the DBDemos database.
The appearance of each column can be dialed in by clicking
the Columns property ellipses and setting properties for title,
background color, and alignment for cell contents and title. You
can even embed data-aware edit and lookup controls to your
grid using the Control property for the column. For the grid as
a whole, set UseFrame to True if you want the grid to appear in
a scrollable HTML frame area, and RowAlternateColor for that
bank-report-statement look.
Although coding isnt necessary to work with the grid, you
can customize behavior for a given cell, row, or column
using the OnRenderCell event. This event handler gets the
last word on how each cell is displayed. A cell reference is
passed as a parameter to the event so you can manipulate font,
background, caption, etc. Row and column index parameters
provide the context. In the code snippet shown in Figure 12,
tweak the Price column font color and bold the title row.

Figure 11: IntraWeb data-aware controls displaying live data in the browser.

isnt clear. The components TIWLabel, TIWEdit, and TIWMemo


all have clearly defined counterparts in a standard Windows
form, but TIWText muddies the water. Use TIWText to insert
blocks of formatted HTML onto a page. Set the RawText
property to True if you want to pass HTML formatting to the
browser; RawText should be False if you want to show the
HTML tags as part of the text. WantReturns lets you determine
how line breaks are handled. Set WantReturns to False if you
want the browser to decide where line breaks go, or to True
if the breaks in the Lines property are to be taken literally. An
example may help. Assign the text shown in Figure 9 to the
8

DELPHI INFORMANT MAGAZINE | May 2003

Note the highlighted links in the Name column of the grid in


Figure 11. Clicking these links will navigate the dataset to that
record, synchronizing the detail information on the right side.
The link becomes operational by coding the OnClick event
handler of the first row in the Columns property and setting the
column LinkName property to Event_Name. In the event handler, use the tables Locate method to search on Event_Name
for the AValue parameter passed in, as shown in Figure 13.
Whats this HTTPDecode? AValue contains some HTTP
encoded portions: Mens Track and Field arrives
in the event handler as Men%27s Track and Field.
Add HTTPApp to the uses clause of the unit, and use
HTTPDecode to translate %27 to an apostrophe.

On

the

N e t

IntraWeb Intro

procedure TformMain.IWDBGrid1RenderCell(ACell: TIWGridCell;


const ARow, AColumn: Integer);
begin
// If the "price" title cell, format specifically.
if (AColumn = 3) and (ARow = 0) then
ACell.Font.Color := clGreen;
// Bold the title row.
if ARow = 0 then
ACell.Font.Style := ACell.Font.Style + [fsBold];
end;

Figure 12: Control appearance with the OnRenderCell event.

procedure TformMain.IWDBGrid1Columns0nClick(
ASender: TObject; const AValue: string);
var
SDecodedValue: string;
begin
SDecodedValue := HTTPDecode(AValue);
if SDecodedValue <> '' then
tblEvents.Locate('Event_Name', SDecodedValue, []);
end;

Figure 13: Using the columns OnClick event handler for navigation.

The database-aware navigator component also requires that


the DataSource property be set. The VisibleButtons property
is used here to only show buttons for first, last, prior, and
next. The CustomImages property gives you an opportunity
to replace the rather unattractive stock images displayed in
the browser with better artwork. The confirmation property

DELPHI INFORMANT MAGAZINE | May 2003

is a collection of strings, one to appear automatically in


a confirmation dialog box for each possible operation. If
you leave a confirmation string blank, the warning wont
appear for that operation. The data-aware label, image, and
memo controls require DataSource and DataField property
assignments but no coding.
Conclusion
This article is accompanied by six complete example
IntraWeb projects that you can download. These
examples show a development environment that parallels
that of standard Delphi Windows applications. There are
many important topics we havent discussed, such as
login/authentication, deployment, exception handling,
templates, IntraWeb component writing, and scripting,
but I hope this brief fly-over of IntraWeb has you
thinking about the possibilities of creating Web sites in
true RAD style.
The six example IntraWeb projects referenced in this article
are available for download on the Delphi Informant
Magazine Complete Works CD located in INFORM\2003\
MAY\DI200305NR.
Noel Rice is senior architect at Kazoo Software where he has been
instrumental in architecting several Fortune 500-based projects and just
completed the authorized IntraWeb courseware written in collaboration with
Lino Tadros (www.kazoosoft.com/IW5Course.aspx). He can be reached at
nrice@kazoosoft.com.

O N

L A N G U A G E

ERROR HANDLING

EXCEPTIONS

DELPHI 1-7

By Bill Todd

After Errors
Using and Handling Exceptions

ets get right to it. When a run-time error occurs


in a Delphi application, Delphi creates an
exception object. The function that caused the

error stops executing on the line that caused the error,


and returns control to its caller.
The exception object is passed back to the caller on the
stack, so the caller knows an exception has occurred.
The caller stops execution on the call to the function that
caused the error, and returns control to its caller, passing
the exception object along. This process is repeated until
the entire function call chain returns, and the exception
object bubbles up to the Application object, which displays
an appropriate error message.

execute the code between finally and end. If the code


between try and finally executes without raising an
exception, all the code between finally and end executes
as if the try..finally block were not there.
However, if an exception occurs in the code between try and
finally, execution of the code in the try block stops with the
statement that caused the exception, and execution jumps to
the code in the finally block. After executing the code in the
finally block, the function exits back to its caller, passing
the exception object along.
The Component Exclusion
Its important to understand that you dont always need a
try..finally block when you create an object. In Delphi, an
object is a component if it descends
from the TComponent class.
always need
Components have an Owner property
that you can set when you create an
block when
instance of the component.

You dont
There are two reasons you need
to interrupt and take control of
a try..finally
this process. First, you may need
you create an object.
to take action to return your
application to a safe and stable
Figure 3 shows a snippet of
state. Second, you may want to handle the exception in
code that creates an instance of a TEdit component.
some way other than letting the call stack unwind and an
The owner, a form in this case, is passed as a paramerror message appear.
eter to the constructor. This means that when MainForm
is destroyed, it will first free all the components that it
Ensuring That Code Executes
owns. Because you know that the TEdit instance to which
Figure 1 shows a code snippet that creates an instance
MyEdit points will be freed when its owner is destroyed,
of the TStringList class, does some stuff, then frees
you dont need a try..finally block to ensure it will be
the instance pointed to by the MyList variable. If the
freed. You can use a try..finally block if you wish, but
code that does some stuff raises an exception, your
theres no potential for a memory leak if you dont.
application will have a memory leak, because the function
that contains this code will return without executing the
var
MyList: TStringList;
call to MyList.Free.
Figure 2 shows the same snippet with a try..finally block
added. The try..finally construct tells Delphi to try to
execute the code between the try keyword and the finally
keyword, and no matter what happens always
10

DELPHI INFORMANT MAGAZINE | May 2003

begin
MyList := TStringList.Create;
...do some stuff...
MyList.Free;

Figure 1: A memory leak in the making.

On

Language

After Errors

var
MyList: TStringList;
begin
MyList := TStringList.Create;
try
...do some stuff...
finally
MyList.Free;
end;

Figure 2: Protecting allocated memory.

var
NameEdit: TEdit;
begin
NameEdit := TEdit.Create(MainForm);
NameEdit.Parent := MainForm;
NameEdit.Top := 10;
NameEdit.Left := 10;

Figure 3: Components are cleaned up automatically by their owners.

var
Buff: PChar;
begin
GetMem(Buff, 512);
try
...do some stuff...
finally
FreeMem(Buff);
end;

Figure 4: Allocating memory in a null-terminated string.

Explicit Memory Allocation


If you allocate memory using the GetMem or New procedures, you should always protect the memory allocation
with a try..finally block, as shown in Figure 4. This code
snippet dynamically allocates memory for a PChar, a data
type that is a pointer to an array of Char. In this example,
Buff points to a 512-byte block of memory. The try..finally
block ensures that FreeMem is called to free the memory
allocated by GetMem.
There are other situations that require try..finally blocks:
If you open a file, use a try..finally block to make sure
the file is closed.
If you call the TDataSet.DisableControls method to temporarily disconnect the user interface from the dataset, use a
try..finally block to ensure that EnableControls is called.
Use a try..finally block in any procedure or function that
contains code that must execute, even if an exception is
raised by code that executes earlier.
Trapping Exceptions
Because exceptions are objects, they like all objects
have a hierarchy. All exception objects are descendants of
the Exception class. While the names of all other classes in
Delphi start with the letter T, the names of all exception
classes start with E. (They are, after all, exceptions.)
As an example of the exception hierarchy, consider the integer
math exceptions EDivByZero, ERangeError, and EIntOverflow.
All these exceptions descend from EIntError, which descends
11

DELPHI INFORMANT MAGAZINE | May 2003

procedure TForm1.MathErrorWithMessageClick(
Sender: TObject);
var
i, j, k, l, m: Integer;
begin
i := 23;
j := 0;
l := 2;
m := 4;
try
k := i div j * (l div m);
except
on EDivByZero do begin
k := 0;
MessageDlg('Divide by zero error',
mtError, [mbOK], 0);
end;
on EIntError do begin
k := 0;
MessageDlg('Integer math error.',
mtError, [mbOK], 0);
end;
end;
{ Display the result. }
Result.Caption := IntToStr(k);
end;

Figure 5: Trapping an exception with a try..finally block.

from EExternal, which descends from Exception. This is


important, because, as youll see in the following examples, it
means that if you want to trap all integer math errors you dont
have to watch for EDivByZero, ERangeError, and EIntOverflow
individually. Instead, you can watch for EIntError, which will
catch EIntError and all of its descendants.
Figure 5 shows a code snippet that raises an EDivByZero
exception, which is trapped within a try..except block.
The try..except block works much like a try..finally
block. If an exception is raised by any of the code in
the try block, control jumps to the except block. In the
except block, each on clause is checked to see if the
exception that was raised is of the type identified in the
on clause, or is a descendant of the type in the on clause.
If the exception matches the on clause, the code in the on
clause is executed, the exception object is destroyed, and
execution continues with the first statement after the end
that terminates the try..except block.
If an EDivByZero exception occurs in the code in Figure 5, the
result variable, k, is set to zero and a message is displayed.
If any other integer math exception occurs the on EIntError
block will execute and display a different message. Note
that this code will not work if the on EIntError clause is
placed before the on EDivByZero clause because EDivByZero
descends from EIntError and will, therefore, be caught by the
on EIntError clause.
You dont have to handle an exception where it occurs.
Assume function A calls function B, which calls function
C. If function C raises an exception, you can trap it with a
try..except block in function C that contains the code that
raises the exception. You can also trap the exception in
function B with a try..except block that contains the call to
function C, or in function A with a try..except block that
contains the call to function B.

On

Language

After Errors

Suppose both function C and function B need to take


some corrective action if an exception is raised. The problem is that if the exception is trapped by a try..except
block in function C, the exception object is destroyed and
function B will not see it. The solution is to use the raise
keyword to re-raise the exception, as shown in Figure 6.
The code in Figure 6 is identical to the code in Figure 5,
except that the raise command has been added at the
end of both on clauses. The raise command causes the
exception to be re-raised and passed back to the calling
function, so it too can trap and handle the exception.
This allows you to detect and respond to an exception
at any level in the call chain, or at several levels in the
call chain.
Raising Exceptions
You can raise any exception any time you wish using the
following syntax:
raise Exception.Create('Something bad has happened.');

In this example, Exception is the class of exception to


raise, and the string passed as a parameter to the constructor is the error message you want displayed. There
may be cases where you want to raise one of the predefined Delphi exception types, but more often you will
want to use a custom exception class, so you can distinguish your exception from one raised by Delphi.
Figure 7 shows how to declare custom exception classes.
This example creates a hierarchy of custom exceptions.
The first line declares the new class EMyCustomException,
which descends from the Exception class. The next two
lines declare two additional custom exceptions that
descend from the EMyCustomException class. After youve
declared a custom exception, you can raise it using the
raise command (as shown earlier).
Blocking Actions
The Delphi dataset components have many events whose
names start with the word Before. Examples include
BeforeEdit, BeforeInsert, BeforePost, and BeforeDelete. If you
raise an exception in the event handler for one of these
events, the action will be blocked.
Figure 8 shows a BeforeDelete event handler that raises
an exception to prevent the user from deleting the current
record. The exception type doesnt matter; raising any
exception will prevent the action from taking place. This
applies to any event of any component, as long as the
event occurs before the action takes place.
If you want to raise an exception, but not display a message,
use a silent exception. There are two ways to raise a silent
exception. The first is to use the raise keyword, as shown here:
raise EAbort.Create('');

Note that the constructor still requires an error message


parameter, even though the error message wont be displayed. To make silent exceptions even easier to use, the
12

DELPHI INFORMANT MAGAZINE | May 2003

procedure TForm1.MathErrorWithMessageClick(
Sender: TObject);
var
i, j, k, l, m: Integer;
begin
i := 23;
j := 0;
l := 2;
m := 4;
try
k := i div j * (l div m);
except
on EDivByZero do begin
k := 0;
MessageDlg('Divide by zero error',
mtError, [mbOK], 0);
raise;
end;
on EIntError do begin
k := 0;
MessageDlg('Integer math error.',
mtError, [mbOK], 0);
raise;
end;
end;
{ Display the result. }
Result.Caption := IntToStr(k);
end;

Figure 6: Re-raising an exception.

type
EMyCustomException = class(Exception);
EDeleteNotAllowedException = class(EMyCustomException);
EInsertNotAllowedException = class(EMyCustomException);

Figure 7: Creating custom exceptions.

procedure TForm1.Table1BeforeDelete(DataSet: TDataSet);


begin
raise EDeleteNotAllowedException.Create(
'You cannot delete this record.');
end;

Figure 8: Blocking the action in a BeforeDelete event handler.

Abort procedure in the SysUtils unit raises one for you;


simply call SysUtils.Abort. There are components that
have methods named Abort, so you should always include
the SysUtils unit name when raising a silent exception, to
make sure the correct Abort procedure is called.
Using the Exception Object
You can access the properties of the exception object, as
shown in Figure 9. The e following the on keyword is a
variable that refers to the exception object. Although the
example uses e, you can name the variable anything. This
exception reference variable doesnt have to be declared
anywhere in your application. In this example, the code
first sets the Message property of the exception object to
a custom message string. Next, it sets the HelpContext
property to the number of the help topic you want
displayed if the user presses 1 while viewing the error
message dialog box.
All exception classes inherit the Message and HelpContext
properties from the Exception base class. Some exception

On

Language

After Errors

try
k := i div j * (l div m);
except
on e: EDivByZero do begin
k := 0;
e.Message = Both j and m must be greater than zero.;
e.HelpContext = 123;
raise;
end;
end;

Figure 9: Using the exception objects properties.

procedure Table1PostError(DataSet: TDataSet;


E: EDatabaseError; var Action: TDataAction);
var
ErrCode: DBIResult;
begin
ErrCode := 0;
if E is EDBEngineError then
with EDBEngineError(E) do
ErrCode := Errors[ErrorCount-1].ErrorCode;
if ErrCode = DBIERR_KEYVIOL then
begin
ShowMessage('That Customer Number is in use.');
Action := daAbort;
end;

procedure TForm1.AppOnException(Sender: TObject;


E: Exception);
var
Addr: string[9];
ErrorLog: System.Text;
begin
AssignFile(ErrorLog, 'errorlog.txt');
{ Open the file for appending. If it doesnt exist
then create it. }
try
System.Append(ErrorLog);
except
on EInOutError do
Rewrite(ErrorLog);
end;
{ Write the date, time, user name, error message,
and address to the error log file. }
Addr := IntToHex(Seg(ErrorAddr),4) + ':' +
IntToHex(Ofs(ErrorAddr),4);
Writeln(ErrorLog, format('%s [%s] %s %s',
[DateTimeToStr(Now),GetNetUserName,E.Message,Addr]));
{ Close the error log file. }
System.Close(ErrorLog);
{ Display the error message dialog. }
MessageDlg(E.Message + '. Occurred at: ' +
Addr, mtError, [mbOK], 0);
end;

Figure 11: Replacing the default exception handler.

Figure 10: An OnPostError event handler.

classes contain additional information. For example, the


EDBEngineError class is used to report errors raised by
the Borland Database Engine (BDE). In addition to the
Message and HelpContext properties, it has ErrorCount
and Errors properties. The Errors property is an array of
records that contains all the errors returned by the BDE,
including error code, message, native database server error
code, and subcode. ErrorCount tells you the number of
entries in Errors. Check the online help to see which properties a particular exception class provides.

retry the operation if you were able to correct the


problem.
The example in Figure 10 declares a variable of type
DBIResult, and sets it to zero. If the exception object is
of type EDBEngineError, the last error code is retrieved
from the Errors property. If the code matches the constant
DBIERR_KEYVIOL, the custom message is displayed,
and Action is set to daAbort. You can use similar code to
handle exceptions in the other On..Error event handlers.

Handling Data Editing Exceptions


Replacing the Default Exception Handler
Of course, exceptions can occur when users are editing
If an exception occurs, and you dont trap it with a
data in a database table. However, you cannot trap
try..except block, the Application objects default excepthese exceptions using a try..except block, simply
tion handler displays a dialog box with the error message
because theres no place to put the block. To solve this
from the exception object. If you want something else to
problem, all components that descend from TDataSet
happen, just write an event handler for the Application
provide the OnPostError, OnEditError, and OnDeleteError
objects OnException event.
events. As their names suggest, OnPostError occurs if
an attempt to post a new or changed record raises an
Figure 11 shows an event handler for OnException that
exception, OnEditError occurs if placing the dataset in
logs the error message to a text file that then displays the
Edit mode raises an exception, and
message. The exception object is
OnDeleteError fires when deleting a
passed to the event handler as a
Delphis exception
record raises an exception.
parameter, so you have access to
mechanism provides a
all its properties.
Figure 10 shows an OnPostError
flexible way to deal with
event handler in an application that
The only trick to using a custom
run-time errors.
uses the BDE. The purpose of this
event handler for OnException is
code is to display a user-friendly
in connecting the event handler
error message when a key violation occurs. The event
to the event. There are two ways you can do this. The
handler takes three parameters: the dataset and exception
first is to drop an ApplicationEvents component from the
objects, and one var parameter, Action. By default, Action Additional page of the Component palette on your main
is set to daFail, which causes the normal exception error
form, and create the event handler in the usual way using
dialog box to be displayed. Setting Action to daAbort
the Object Inspector. The second method is to assign the
turns the exception into a silent exception, so no error
event handler to the event in code using the following
message is displayed. The third option, daRetry, lets you
statement:

13

DELPHI INFORMANT MAGAZINE | May 2003

On

Language

After Errors

Application.OnException := AppOnException;

The main forms OnCreate event handler is a good place


to do this.
Conclusion
Delphis exception mechanism provides a flexible way to
deal with run-time errors. You can trap an exception at
one or more points in the call chain to let your program
react appropriately to the error. You can let the exception
bubble to the top of the call chain to be handled by
the Application objects default exception handler, or
your own custom handler for the Application objects
OnException event. You can also define custom exception
classes and raise your own exceptions at any time. And
you can block events by raising exceptions in any of the
Before... event handlers.
Make no mistake Delphi exceptions make writing
robust code exceptionally easy.

Bill Todd is president of The Database Group, Inc., a database consulting


and development firm based near Phoenix. He is co-author of four database
programming books, author of more than 100 articles, a contributing editor
to Delphi Informant Magazine, and a member of Team B, which provides
technical support on the Borland Internet newsgroups. Bill is an internationally
known trainer and is a frequent speaker at Borland Developer Conferences in
the United States and Europe. Readers may reach him at bill@dbginc.com.

14

DELPHI INFORMANT MAGAZINE | May 2003

G R E A T E R
COM+

SHARED PROPERTY MANAGER

D E L P H I

INFORMATION SHARING

DLLS

DELPHI 5

By Matthew Hess

The Shared Property Manager


Store Configuration and State Information to Share among Your Apps

ou probably know spam as an interesting


lunch meat, or as those annoying unsolicited e-mails cluttering your in-box. But

theres another kind of spam that might interest


you: the COM+ Shared Property Manager (SPM),
affectionately known as the SPM (pronounced
the spam). In this article, well look at the
COM+ SPM, discuss when you should use it, and
develop some simple functions that effectively
manage COM+ SPM access.

What Is the SPM


(and When Should You Use It)?
To understand the SPM, we must take a step back and look
at the COM+ environment. A COM+ application is really
a named, configurable container for COM components. The
application itself doesnt do anything. Its the components
inside that define the applications behavior.
Before going any further, lets clear up some terminology.
Theres always some confusion in terminology when talking

S
P
M

15

Figure 1: Objects in a COM+


application have access to
the common memory space
provided by the Shared
Property Manager.

DELPHI INFORMANT MAGAZINE | May 2003

about COM components. Is a component an object/class


definition, or an object/class instance? For this article, Ill
use component to refer to the object/class definition,
and Ill use the word object to refer to an in-memory
instantiation of an object/class. This is consistent at least
with the COM+ MMC Snap-In, which shows that each
application has a components collection, which refers not
to the in-memory instances of COM objects, but rather to
the registered, configured classes that can be instantiated.
As the application runs, instances of COM components are
created in a session of dllhost.exe. Each of these objects is an
autonomous entity with its own thread and call stack. The
various objects in a COM+ application may not even share
the same Windows context. How then, can these separate
mini-programs share information and get information about
their common environment? This is where the SPM comes in.
The COM+ SPM provides shared memory space that all the
objects in a COM+ application can access (see Figure 1).
There are many ways you can use the SPM. From my
experience I think it works well for these kinds of tasks:
Store and share information about application
configuration. Most applications keep configuration
information (versions, identities, and database
connection information) in a shared place such as
an .ini file, the registry, in a database, or perhaps
in the configuration of the COM+ application itself.
In all cases, the information is needed repeatedly.
Caching it in the SPM is an easy way to speed up your
application and reduce resource conflicts.
Store and share information about application state.
Most applications keep track of state information such
as whether a database is online, what is the total
number of active objects, or which store-and-forward
MSMQ servers are available. The SPM provides a
convenient place to track this sort of state and make it
available to other objects.

Greater

Delphi

The Shared Property Manager

Cache static information that is commonly needed but


expensive to obtain. Theres certain information that is
commonly needed by an application, but is expensive
to retrieve. An excellent example of this is information
stored in Active Directory. Instead of doing an LDAP
query every time you need a users e-mail address, query
LDAP once, and cache the value in the SPM.
Need proof? I recently did some simple benchmarking during
the development of a COM+ application to see if the SPM
really did speed things up. The table in Figure 2 shows the
results for five common tasks. The second column shows the
milliseconds needed to compute the value from scratch. The
third column shows the milliseconds for subsequent SPM
access. These tests were done on a dual PIII 1GHz 512MB
machine running Windows 2000 Server and serving as an
Active Directory PDC and Exchange Server. Needless to say, I
immediately began building an SPM caching architecture.

Task

Compute SPM

Read single value from registry

Query Domain from Active Directory Services

15

Query DNC from Active Directory Services

47

Query user e-mail from LDAP

172

Bind user mailbox from Exchange to get MBX path

547

Figure 2: Does the SPM really expedite common tasks?

The various objects in a COM+


application may not even share
the same Windows context. How
then, can these separate miniprograms share information and get
information about their common
environment? This is where the
SPM comes in.

Is there anything for which you shouldnt use the SPM? Its
important to understand that the SPM is not a replacement for a
database. The SPM has a simple locking scheme that is fast and
well suited for quick reads, but its not really up to database
standards. Second, the SPM isnt transactional. If something
goes wrong, you have no way to roll back a change to the SPM.
Most importantly, the SPM isnt persistent. When your COM+
application shuts down, the contents of the SPM are lost.
I should mention in this context that the default timeout of
a COM+ application is three minutes. This means that three
minutes after your last object is freed from memory, your
COM+ application will shut down and the SPM will be reset.
You may want to change the default timeout, or take other
measures (such as guaranteeing that at least one object will
be created every three minutes) to make sure your application
doesnt timeout. Conversely, you may want to take advantage
of this behavior to force the SPM to refresh.
Setting Up
Enough with the theory, lets dig into the details. The code
in this article references the COM+ Services 1.0 type library.
To import the type library in Delphi 5, go to Project | Import Type
Library, and select COM+ Services Type Library (Version 1.0), which is
based on the COMSVCS.DLL. Create a unit somewhere (see
Figure 3), add the resulting unit (COMSVCSLib_TLB) to your
projects and units that use the SPM and other COM+ services.
Getting a Shared Property
A piece of information in the SPM is called a shared property.
Properties are organized into shared property groups for locking
16

DELPHI INFORMANT MAGAZINE | May 2003

Figure 3: Importing the COM+ Services Type Library.

purposes, and to prevent naming conflicts. If you want to


access a SPM property, you must first access the group that its
in. For example, lets say were storing database connectivity
information in the SPM. For simplicitys sake, imagine we
need to keep track of a database name, user ID, and password
(encrypted, of course!) and that were going to put these three
properties together in a property group named Database. To
access any of the properties, we must first access the Database
property group. This is done through an object called a shared
property group manager, which implements the ISharedProperty
GroupManager interface (see Figure 4).
A shared property group manager provides two ways for
us to access a shared property group: we can call either
Get_Group or CreatePropertyGroup; both return a reference
to an ISharedPropertyGroup interface. Get_Group (or its
associated property, Group) returns a reference to the group,
if it exists, and raises an exception if it doesnt, so well need
to handle the exception and test for existence. Once we have
the group, we can use the Property_ property of the Group
to get a shared property. Like Get_Group, Property_ returns a
reference or raises an exception (see Figure 5).

Greater

Delphi

The Shared Property Manager

ISharedPropertyGroupManager = interface(IDispatch)
['{2A005C0D-A5DE-11CF-9E66-00AA00A3F464}']
function CreatePropertyGroup(const name: WideString;
var dwIsoMode: Integer; var dwRelMode: Integer;
out fExists: WordBool): ISharedPropertyGroup; safecall;
function Get_Group(const name: WideString):
ISharedPropertyGroup; safecall;
function Get__NewEnum: IUnknown; safecall;
property Group[const name: WideString]:
ISharedPropertyGroup read Get_Group;
property _NewEnum: IUnknown read Get__NewEnum;
end;

Figure 4: The ISharedPropertyGroupManager interface.

function GetSharedProperty(const sGroup, sProp: WideString;


vDefault: Variant): Variant;
var
pSPGM: SharedPropertyGroupManager;
pGroup: ISharedPropertyGroup;
pProp: ISharedProperty;
begin
try
pSPGM := CoSharedPropertyGroupManager.Create;
// Get the group.
try
pGroup := pSPGM.Group[sGroup];
except
end;
// If that worked, get the property.
if Assigned(pGroup) then
try
pProp := pGroup.Property_[sProp];
except
end;
// Return the value or the default.
if Assigned(pProp) then
Result := pProp.Value
else
Result := vDefault;
finally
if pProp <> nil then pProp := nil;
if pGroup <> nil then pGroup := nil;
if pSPGM <> nil then pSPGM := nil;
end;
end;

Figure 5: Like Get_Group, Property_ returns a reference or raises an exception.

Notice also that this function requires that the caller provide a
default value to use, in case the group or the property is
not found. This is convenient, because it saves the caller
from having to test the result for an unassigned condition.
Setting a Shared Property
Not surprisingly, to set a shared property we must
also first reference its group. In this case, well use
the CreatePropertyGroup function, which is a bit
more complicated. CreatePropertyGroup expects four
parameters, some of which deserve a little explanation.
First is the in parameter of the name of the group. Last
is a Boolean out parameter which will be True if the
property group exists, and False if it doesnt. In between
come two interesting in-out integer parameters,
dwIsoMode and dwRelMode.
dwIsoMode sets the isolation or locking behavior for the
group. It has two values, LockSetGet, which is the default,
and LockMethod. LockSetGet indicates that locking happens
17

DELPHI INFORMANT MAGAZINE | May 2003

Database

Database

DBName

DBName
UserID
Password

x UserID
Password

LockMethod

LockGetSet

Figure 6: The dwIsoMode settings. LockMethod locks all properties in


the group for the duration of the method call when any object accesses a
property in the group for either reading or writing. LockGetSet only locks a
single property for the duration of a read or write. Other objects can access
other properties in the group concurrently.

only at the property level. While a method is accessing


a property, either to read or write, no other method can
access that property. However, other objects and methods
can access other properties in the same group.
LockMethod indicates that the entire group is locked for
exclusive access by the caller for the duration of the
method call. This is the locking behavior to use when
the various properties in a group are interdependent. For
example, in our Database property group, the UserID and
Password properties are conceptually linked. We probably
would not change one without the other, so we might
want to use the LockMethod setting to avoid a situation
where a client reads a mismatched UserID and Password
(see Figure 6).
dwRelMode sets the release mode for the property group.
This also has two values, Standard and Process. Standard
means the property group is destroyed when no more
clients are accessing it. Process means the property group
lives as long as your process (i.e. your COM+ application)
is running. The examples in this article all use Process.
Both dwIsoMode and dwRelMode are set when a property
group is created; they cannot be changed after that.
This means that when you call CreatePropertyGroup and
the group doesnt exist, fExists will come back as False,
and the isolation and release modes will be set with the
values you passed in. If the group exists, fExists will
come back as True and dwIsoMode and dwRelMode will
reflect the values set when the group was created.
After youve created or referenced a property group, you
can access a property using Property_, as we did earlier, or
you can use CreateProperty, which initializes the property
if it doesnt exist (see Figure 7).
Self-initializing Properties
In my experience, many properties that I end up putting in the
SPM are solo properties that dont need to be grouped with
any other properties. For example, the name of the current
domain and server are values that stand on their own. For these
kinds of shared properties, I like to use a single, all-purpose
function named GetSimpleSharedProperty that uses the same
name for the group and the property and that accepts a function
pointer so that it can self-initialize if needed. The beauty of
this is that you dont need separate getters and setters, SPM
access is optimized because theres never any group locking,
and client code is completely insulated from the details of both

Greater

Delphi

The Shared Property Manager

procedure SetSharedProperty(
const sGroup, sProp: WideString; vValue: Variant);
var
pSPGM: SharedPropertyGroupManager;
pGroup: ISharedPropertyGroup;
pProp: ISharedProperty;
lExists: WordBool;
i, j: Integer;
begin
try
pSPGM := CoSharedPropertyGroupManager.Create;
i := LockMethod; // Accessing group will lock it.
j := Process;
pGroup :=
pSPGM.CreatePropertyGroup(sGroup, i, j, lExists);
pProp := pGroup.CreateProperty(sProp, lExists);
pProp.Value := vValue;
finally
if pProp <> nil then pProp := nil;
if pGroup <> nil then pGroup := nil;
if pSPGM <> nil then pSPGM := nil;
end;
end;

Figure 7: Use CreateProperty to initialize a property if it doesnt exist.

the SPM and the various functions that are used to calculate the
shared properties in the first place (see Figure 8).
Note that no client will ever call GetDomain,
GetSimpleSharedProperty, or QueryDomainfromAD. These
functions are all hidden behind the Domain property.
Conclusion
As weve seen, the COM+ SPM is fairly easy to use and
can provide significant performance gains for your COM+
applications. Download the accompanying sample project
to see it in action. Like the sample project from last
months article on COM+ Queued Components, this one
requires that you set up a COM+ application and launch
it from a simple ASP page. Please follow the directions
from last month for creating the application, installing the
components, running your code in the Delphi debugger,
and launching it all from an ASP script. I think youll
find that the COM+ SPM is an indispensable part of your
COM programmers toolkit.
The demonstration project and DLL referenced in this
article is available for download on the Delphi Informant
Magazine Complete Works CD located in INFORM\2003\
MAY\DI200305MH.

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


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

18

DELPHI INFORMANT MAGAZINE | May 2003

property Domain string read GetDomain;


type TVarFunct = function: Variant;
function GetSimpleSharedProperty(const sProp: string;
F: TVarFunct): Variant;
var
pSPGM: SharedPropertyGroupManager;
pGroup: ISharedPropertyGroup;
pProp: ISharedProperty;
lExists: WordBool;
i, j: Integer;
begin
try
pSPGM := CoSharedPropertyGroupManager.Create;
i := LockSetGet; // Group not locked.
j := Process;
pGroup := pSPGM.CreatePropertyGroup(sProp,i,j,lExists);
pProp := pGroup.CreateProperty(sProp, lExists);
// If the property didn't exist, initialize it.
if not lExists then
pProp.Value := F;
Result := pProp.Value;
finally
if pProp <> nil then pProp := nil;
if pGroup <> nil then pGroup := nil;
if pSPGM <> nil then pSPGM := nil;
end;
end;
function QueryDomainfromAD: Variant;
begin
// Code here to actually get the domain from AD.
// Because of SPM, we only ever do this once.
end;
function GetDomain: string;
var
F: TVarFunct;
begin
F := QueryDomainfromAD;
Result := VarToStr(GetSimpleSharedProperty('Domain', F));
end;

Figure 8: Using the GetSimpleSharedProperty function.

F O U N D A T I O N
C# ASSEMBLIES

C L A S S

PINVOKE DELPHI FOR .NET COMPILER PREVIEW (MORPHEUS) DELPHI 7

By Fernando Vicaria

.NET/Win32 Interop
Part II: Clearing Common (and Uncommon) Interoperability Hurdles

his is the second article in a two-part series


aimed at those of you experimenting with
the Delphi for .NET Compiler Preview thats

bundled with Delphi 7. In Part I we looked into the


basics of PInvoke and how to implement it from
the straightforward case of calling an uncomplicated

Win32 DLLs one using Reflection, and the other using


the more traditional LoadLibrary API function.
The examples presented in each of the following sections
will be as simple as possible; the only intention is to demonstrate a particular interoperability technique without adding
unnecessary code. The source code for the examples accompanies this article; see end of article for download details.

API function (with a simple return type) inside

Just call it Morpheus. For readability purposes I will refer to


the new Delphi for .NET Preview Compiler as Morpheus (Boran unmanaged DLL, to marshaling data structures
lands internal code name for the project). Also keep in mind
backward and forward through the managedthat because of the Preview nature
of this project at the time of writing,
unmanaged boundaries. We
I will refer to the new
some of the code shown here might
also saw how to implement
not compile with the latest releases of
Delphi for .NET Preview
callback routines in your
the compiler without some modificaCompiler as Morpheus
tion. To get the latest information on
.NET application, and how to
(Borlands internal code
whats changed in the compiler, visit
pass them to a Win32 API.
one of Borlands public newsgroups.
name for the project).

In this final installment, well discuss some of the more frequent issues concerning platform and development language
interoperability. Well also continue with more examples of how
to dynamically load and use an unmanaged DLL in your .NET
apps, i.e. Dynamic PInvoke. Well also learn how to use managed code from your Win32 applications (Inverse PInvoke),
demonstrate a few techniques to access Win32 code from .NET
(and vice versa), demonstrate some of the language-independent capabilities of .NET, and show how Delphi developers
can take advantage of the enormous amount of code thats
already out there for the .NET platform.
Lets start with the simplest case possible: We need to consume some type residing in an assembly (DLL) designed in
C#. Then well do the opposite and have C# use code from
an assembly designed in Delphi for .NET. Well also see
how to make Win32 applications (designed with Delphi 7)
consume assemblies designed for the .NET platform. Finally, well see two different techniques to dynamically load
19

DELPHI INFORMANT MAGAZINE | May 2003

Consuming C# Assemblies from


Morpheus
In this first example well create a couple of C# files that
each contain a single class with only one method each.
Relax! You dont have to know C# to follow this example;
besides, the code is very simple. All we want to do here is
compile the two C# files into one assembly (a DLL in this
namespace Functions
{
using System;
public class Factorial
{
public static int Calc(int i)
{
return ((i <= 1) ? 1 : (i * Calc(i-1)));
}
}
}

Figure 1: Factorial.cs.

Foundation

Class

.NET/Win32 Interop

program UseCSharpDll;
uses Functions;
{$APPTYPE CONSOLE}
var
temp: string;
i, j: Integer;
begin
Writeln;
WriteLn('Enter an integer: ');
Read(temp);
i := StrToInt(Temp);
j := Functions.Factorial.Calc(i);
WriteLn('Factorial of ' + IntToStr(i) + ' is: ' +
IntToStr(j));
Writeln;
end.

Figure 2: UseCSharpDll.dpr.

not going to use any of its types in our Morpheus example. I included it to show you how to combine multiple
C# files into a single namespace inside an assembly. Both
files (Factorial.cs and DigitCounter.cs) contribute to the
same namespace (Functions) and are linked into the same
assembly (Functions.dll).
Now lets take a look at the code for our project; its shown
in Figure 2. As you can see, this example is extremely simple. It asks you to enter an integer as input, then outputs the
factorial of that integer. The project uses the Functions DLL,
so the compiler needs to be able to find it at compile time.
To compile the DLL we use the C# compiler that comes free
with the .NET SDK; simply type the following at the DOS
prompt (without the line break of course):
csc /target:library /out:Functions.dll Factorial.cs
DigitCounter.cs

This will compile the two C# source files into a DLL


named Functions. After successfully compiling the C#
DLL, you can build and run the Morpheus project with
the following command:
dccil -luFunctions UseCSharpDll.dpr

Figure 3: Compiling and running the UseCSharpDll project.

library Functions;
type
TFactorial = class(TObject)
public
class function Calc(i: Integer): Integer; static;
end;
{ TFactorial }
class function TFactorial.Calc(i: Integer): Integer;
begin
Result := 1;
if i > 1 then
Result := i*Calc(i -1);
end;
begin
end.

Figure 4: A Morpheus DLL.

case) and have a Morpheus console application consume


one of the classes it defines.
Figure 1 shows the code for the Factorial class. This
class has a single static method that takes an integer as
a parameter, and returns another integer representing the
factorial of the parameter entered.
The other C# file included in the accompanying download
(DigitCounter.cs) isnt strictly necessary, because were
20

DELPHI INFORMANT MAGAZINE | May 2003

Figure 3 shows the entire process, from compiling the


C# DLL to running the Morpheus project. Note: You can
compile the DLL and the project in one go using a batch file.
I included an example of a batch file for this project with
the source code for this article.
Consuming Morpheus Assemblies
from C#
Our second example will invert the roles of the languages
used in the first example to generate the application
and the DLL it used. In other words, this time C# will
be consuming the type (and code) generated by the
Morpheus compiler.
This example is an exact copy of the first one, except now
we will design the DLL using Morpheus and the main
program using C#. We will make the C# application call a
class defined inside the Morpheus DLL. The code for the
DLL is shown in Figure 4.
Use the following commands to compile the C# application
and the Morpheus DLL:
dccil Functions.dpr
csc "UseDelphiDll.cs" /r:Functions.dll

The first line generates the Morpheus DLL, and the second
the C# application. I also added a batch file for this example,
so you dont need to open the command prompt.
Consuming .NET Assemblies from
Win32 Applications
Now lets look into the situation where we have a Win32
application that needs to call code inside a .NET assembly.
Its unlikely that youll run into this, but Ive decided to
cover it for the sake of completeness.

Foundation

Class

.NET/Win32 Interop

Well cover two techniques: one using Variants, and


another using late binding via IDispatch. Both methods
require us to generate a type library for the assembly we
want to expose to the Win32 application. Fortunately,
this can be done easily by first registering the assembly
with the Assembly Registration Tool (or regasm.exe) that
comes with the .NET SDK.
Regasm reads the metadata within an assembly and adds
the necessary entries to the registry, which allows COM
clients to create .NET Framework classes transparently.
After a class is registered, any COM client can use it as
though the class were a COM class. The class is registered
only once, when the assembly is installed. Instances of
classes within the assembly cannot be created from COM
until they are actually registered.

Member Name
AutoDispatch

AutoDual

Relax! You dont have to know


C# to follow this example...

After the assembly has been registered in your system


with Regasm, you can use either the IDE (by selecting
Project | Import Type Library), or the command prompt to
directly call tlibimp.exe and generate the type librarys
declaration in a Pascal file. For a more detailed view on
this subject, search the online Help for tlibimp.
Again, our .NET assembly will be written in C#, but it could
be written in any other language, including Pascal. Part of
the code for this example is shown in Listing One (on page
24). As you can see from the code, ClassA doesnt expose an
interface. Therefore, it can only be accessed via Variants. The
advantage of this method is that you dont need to recompile
your application whenever ClassA is modified.
The other three classes have ClassInterfaceAttribute applied to
them. This attribute controls whether a class interface is generated for the attribute class. While class interfaces can be useful
for testing purposes, their use in production applications is
strongly discouraged because they present versioning problems.
The table in Figure 5 presents the three class interface types
available for ClassInterfaceAttribute. The Delphi side of this
example will represent the COM client for all four classes.
The code includes a few comments that explain the mechanism used in each case. Figure 6 shows the user interface
for the Delphi client application.
Dynamically Loading Win32 DLLs in
.NET Applications
In this section, well see two techniques that cover a situation where a DLL name or location, or an API function
name, isnt known at compile time. As we saw in Part I,
the prescribed way to call native functions from the CLR
is through PInvoke, using the DllImport attribute but
this must be declared at compile time.
The first technique uses the API functions LoadLibrary and
GetProcAddress as we used to do in Win32. But this isnt
possible without some mechanism capable of translating
an unmanaged function pointer into something mean21

DELPHI INFORMANT MAGAZINE | May 2003

None

Description
Indicates that the class only supports late
binding for COM clients. A dispinterface
for the class is automatically exposed to
COM clients upon request. To prevent
clients from caching the dispid of the
interface, the type library produced by
TlbExp doesnt contain type information
for the dispinterface. The dispinterface
doesnt exhibit the versioning problems
described in ClassInterfaceAttribute,
because clients can only late-bind to the
interface. This is the default setting for
ClassInterfaceAttribute.
Indicates that a dual class interface is
automatically generated for the class
and exposed to COM. Type information
is produced for the class interface and
published in the type library. The use of
AutoDual is strongly discouraged, because
of the versioning limitations described in
ClassInterfaceAttribute.
Indicates that no class interface is
generated for the class. If no interfaces
are implemented explicitly, the class will
only provide late bound access through
IDispatch. Users are expected to expose
functionality through interfaces that
are explicitly implemented by the class.
This is the recommended setting for
ClassInterfaceAttribute.

Figure 5: Interface types available for ClassInterfaceAttribute.

ingful to our managed


applications in .NET. The
second technique uses
Reflection.Emit to generate the necessary code for
calling the requested API
function on the fly.
With LoadLibrary. As
mentioned, theres nothing
stopping us from calling
LoadLibrary from our .NET
applications to load the
Figure 6: Delphi client for .NET classes
DLL into the applications
exposed to COM.
address space. The problem
comes when you try to call
a function in the DLL using the returned value from a call to
GetProcAddress. In Win32, GetProcAddress returns a pointer to
the memory location of a function exported in a DLL.
This memory address in .NET is nothing more than an
integer value without any meaning to our .NET application. The CLR provides no means for jumping to that
memory location.
However, the CLR does allow us to do the reverse, and
pass a pointer to a managed function to a DLL using the

Foundation

Class

.NET/Win32 Interop
any additional
parameter that
might be on the
stack.

library Invoke;
procedure Call(FuncPtr: Longword); stdcall;
asm
pop ebp
pop edx
pop ecx
push edx
jmp ecx
end;
exports
Call;
begin
end.

Figure 7: Exporting a function named Call.

program UseDll;
uses
System.Runtime.InteropServices, Invoke;
//
//
//
//
//

You will need to create one proxy-function for each


signature, much like funtion pointers in Delphi. The
advantage here is that you don't need to know the name
or location of the DLL, or the name of the function at
compile time.

[DllImport('Invoke', CharSet=CharSet.Unicode,
EntryPoint = 'Call')]
function ProxyFunc(funcptr: IntPtr; hWnd: Integer;
lpText, lpCaption: string; uType: Integer): Integer;
external;
var
hDLL, FuncAddr: IntPtr;
begin
// Dynamically linking to an API.
hDLL := LoadLibrary('user32');
FuncAddr := GetProcAddress(hDLL, 'MessageBox');
ProxyFunc(FuncAddr, 0,
'Unmanaged call to MessageBox inside User32.dll',
'.NET -> Win32', 0);
FreeLibrary(hDLL);
end.

Figure 8: Calling an API function using the Invoke DLL.

delegate keyword. But theres no way to specify that a


value returned from an unmanaged API call should be
treated as a delegate.
The solution is to create a proxy-function (or stub) that
will reside in a separate (and known) Win32 DLL that we
can call, passing the memory address of the API function
we need.
We will still need one proxy-function for each function
signature and return type, pretty much like a function
pointer in Delphi. The difference now is that the actual
function call isnt known to the CLR until run time, when
the stub routine is called. Figure 7 shows the source code
for the Invoke DLL.
All this DLL does is export a function named Call, which
will be used to redirect all calls to the Call function to
the address passed to the FuncPtr parameter, forwarding
22

DELPHI INFORMANT MAGAZINE | May 2003

Figure 8 shows
a Morpheus
application that
uses the Invoke DLL to forward a call to an API function at
run time. I used a console application to demonstrate this
technique, but you could easily create a GUI application,
and pass the DLL and function name via a TOpenDialog and
a TEdit control.

Figure 9: The result of the code shown in Figure 8.

As you can see, ProxyFunc is declared with the DLLImport


attribute. Because the result value from GetProcAddress
cannot be cast into anything useful (as in Win32),
ProxyFunc serves the equivalent role of a function pointer.
This example will call MessageBox to display a simple
message on the screen and then terminate (see Figure 9).
By the way, Sun Microsystems uses something very
similar to what weve just done here with Java and the
msjava.dll, which can be found in most Windows OSes
(except for Windows XP because of a recent MicrosoftSun agreement on Java Technology).

Theres nothing stopping us


from calling LoadLibrary from
our .NET applications to load
the DLL into the applications
address space.

Using Reflection (Dynamic PInvoke). Now lets try


to do the same thing we did in the previous section by
getting the memory address for an API function using
LoadLibrary and GetProcAddress. The difference this time
is that we will use what the CLR makes available to us
via the Reflection.Emit namespace.
The Reflection.Emit namespace contains classes that allow a
compiler or tool to emit metadata and Microsoft intermediate
language (MSIL), and optionally generate a PE (portable
executable) file on disk. The primary clients of these classes
are script engines and compilers. I wont go through the details
of the code here, but will leave it to you to get to know the
classes used in this example.
In short, this example uses the AssemblyBuilder and
ModuleBuilder classes to generate the intermediate
language code for a temporary assembly built on the fly.
This technique does not require a unique wrapper for each
function signature, but it does require a deeper knowledge
of the CLR and how each parameter in the API function you
wish to call would translate into MSIL.
In this example, Ive built a general wrapper (see Figure 10)
capable of dynamically building the method (or API function)
we want to call, passing all the information necessary, such as
signature, return type, its visibility attributes, and calling con-

Foundation

Class

.NET/Win32 Interop

program APIWrapper;
{$APPTYPE CONSOLE}
uses
APIWrap;
var
RetValue: Integer;
Args: array of TObject;
begin
// Prepare agruments array...
SetLength(Args, 4);
Args[0] := TObject(LongWord(0));
Args[1] := string(
'Unmanaged call to MessageBoxW inside User32.dll');
Args[2] := string('.NET -> Win32');
Args[3] := TObject(LongWord(0));
// Call API...
RetValue := Integer(InvokeWin32API('MessageBox',
'user32.dll', TypeOf(Integer), Args));
Console.WriteLine(
'MessageBox returned: ' + IntToStr(RetValue));
end.

Figure 10: Using Reflection.Emit to call Win32 APIs at run time.

vention. The module built will exist only in memory as long as


our application is running, and will be automatically destroyed
when it closes.
The real workhorse of this example is the InvokeWin32API
function in the APIWrap.pas file. Heres its function signature:
function InvokeWin32API(Name, Module: string;
RetType: System.Type; Args: array of TObject): TObject;

As you can see, InvokeWin32API takes strings as the first


two parameters: one for the API function name, and one for
the library name. The third parameter, RetType, is the type
of the return value of the function. As the fourth parameter,
we pass an array of TObject, which contains the parameters
for the API function we want to invoke. We can see how
to populate the Args array parameter, before passing it to
InvokeWin32API, in Figure 10.
The last statement of the program is only necessary if
youre interested in the value returned by the API call. Of
the last two techniques, this is my favorite. Its a bit slower
than the previous one, but its simpler and more generic.
This is also a 100 percent .NET solution, because it doesnt
resort to a Win32 DLL to act as a stub.
Emitting IL Code in Your .NET
Application
Our last example shows how to emit IL code on the fly using
the ILGenerator class, and its Emit and EmitCall methods to put
MSIL instructions into a stream for the just-in-time (JIT) compiler to execute. Figure 11 shows a possible implementation of
this technique for a function with only one parameter.
Although this particular implementation deals only with functions with one parameter and of a specific type you can
generalize it to emit code for any number of parameters of any
type using the OpCodes class series of static fields.
23

DELPHI INFORMANT MAGAZINE | May 2003

function InvokeFunctionWithOneParam(FuncPtr: IntPtr;


Param1: TObject; RetType: System.Type): TObject;
var
AsmName: AssemblyName;
DynamicAsm: AssemblyBuilder;
DynamicMod: ModuleBuilder;
DynamicMethod: MethodBuilder;
MetAttr: MethodAttributes;
Generator: ILGenerator;
Types: array of System.Type;
Params: array of TObject;
mi: MethodInfo;
begin
Types := nil;
Params := nil;
// Create a dynamic assembly and a dynamic module.
AsmName := AssemblyName.Create;
AsmName.Name := 'tempAssembly';
DynamicAsm :=
AppDomain.CurrentDomain.DefineDynamicAssembly(
AsmName, AssemblyBuilderAccess.Run);
DynamicMod :=
DynamicAsm.DefineDynamicModule('tempModule');
Setlength(Types, 1);
Types[0] := TypeOf(Integer);
// Create global method capable of invoking
// function pointer. (Sets aren't implemented yet.)
MetAttr := MethodAttributes(Integer(
MethodAttributes.Public) + Integer(
MethodAttributes.Static));
DynamicMethod := DynamicMod.DefineGlobalMethod(
'DoTheDirtyWork', MetAttr, RetType, Types);
// Generate the MSIL for this method.
Generator := DynamicMethod.GetILGenerator;
// If the method had parameters, we'd push them on the
// stack first. You can add more parameters here if
// necessary.
Generator.Emit(OpCodes.Ldc_I4, Integer(Param1));
if (IntPtr.Size = 4) then
Generator.Emit(OpCodes.Ldc_I4, FuncPtr.ToInt32)
else if (IntPtr.Size = 8) then
Generator.Emit(OpCodes.Ldc_I8, FuncPtr.ToInt64)
else
raise PlatformNotSupportedException.Create;
Generator.EmitCalli(OpCodes.Calli,
CallingConvention.StdCall, RetType, Types);
Generator.Emit(OpCodes.Ret);
// This global method is now complete.
DynamicMod.CreateGlobalFunctions;
// Call the method we just created and return whatever
// it returns.
mi := DynamicMod.GetMethod('DoTheDirtyWork');
Setlength(Params, 1);
Params[0] := Param1;
Result := mi.Invoke(nil, Params);
end;

Figure 11: Dynamically generating and calling IL code in your applications.

Summary
Concluding our two-part series delving into .NET/Win32
interoperability, we looked into some of the techniques
available to get hold of existing Win32 code from inside
a .NET application, as well as how to design a Win32
application capable of calling code that resides inside a
.NET assembly. We also saw how Morpheus users can use
the available code base written in a variety of languages
that also target the .NET platform.
Hopefully youve seen enough here to pique your interest. If
you havent already, pick up a copy of Delphi 7 and dive in.

Foundation

Class

.NET/Win32 Interop

Further Reading
.NET Interoperability: .NET Win32 by Brian Long,
http://www.blong.com/Conferences/BorConUK2002/
Interop1/Win32AndDotNetInterop.htm#InversePInvoke
Introducing Microsoft .NET by David Platt (ISBN 0-73561571-3).
Microsoft .NET/COM Migration and Interoperability,
http://msdn.microsoft.com/library/default.asp?url=/
library/en-us/dnbda/html/cominterop.asp

Begin Listing One .NET Assembly


Exposing Classes to Win32 Apps
namespace Delphi.Informant.Interop
{
public class ClassA
{
public ClassA() { }
public string Hello()
{
return "Hello from C# 1!";
}

Eight sample projects accompany this article and are available for download on the Delphi Informant Magazine Complete Works CD located in INFORM\2003\MAY\DI200305FV.

}
// Indicates that a dual class interface is automatically
// generated for the class and exposed to COM. Type
// information is produced for the class interface and
// published in the type library. The use of AutoDual is
// strongly discouraged because of the versioning
// limitations described in ClassInterfaceAttribute.
[ClassInterface(ClassInterfaceType.AutoDual)]
public class ClassB
{
public ClassB() { }

Fernando Vicaria is a QA Engineer at Borland Software Corporation in Scotts


Valley, CA. He is also a freelance technical author for Delphi and C++Builder
issues. Fernando is specialized in VCL and CLX frameworks and when hes not at
work hes probably surfing at some secret spot in Northern California. He can be
reached via e-mail at fvicaria@borland.com.

public string Hello()


{
return "Hello from C# 2!";
}
}
// If no interfaces are implemented explicitly, the class
// will only provide late bound access through IDispatch.
// Users are expected to expose functionality through
// interfaces that are explicitly implemented by the
// class. This is the recommended setting for
// ClassInterfaceAttribute.
public interface IClassC
{
string Hello();
}
[ClassInterface(ClassInterfaceType.None)]
public class ClassC: IClassC
{
public ClassC() { }
public string Hello()
{
return "Hello from C# 3!";
}
}
public interface IClassD
{
string Hello();
}
// Hides the interface from clients. This avoids the
// versioning problem. This is the default setting for
// the ClassInterfaceAttribute.
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class ClassD: IClassD
{
public ClassD() { }
public string Hello()
{
return "Hello from C# 4!";
}
}
}

End Listing One

24

DELPHI INFORMANT MAGAZINE | May 2003

I N
.NET

D E V E L O P M E N T
OOP

INTERFACES

DELPHI 5-7

By Bob Fleischman and Seth Weiner

A .NET-ready Framework
Forward-looking Classes Embrace OOP

his article presents a set of interfaces to


provide a consistent access methodology
across data types akin to .NETs or Javas type

objects. In particular, we focus on a powerful String


interface that is at the heart of a series of native data
type wrapper interfaces/classes. We demonstrate
how to quickly and easily construct powerful
objects using the interfaces. In addition, we discuss
the inherent value of localizing functionality to
relevant interfaces, as well as in creating readable,
maintainable code. Finally, we suggest building
more complex business objects.
With .NETs arrival, theres no better time for Delphi
developers to fully embrace object-oriented programming
(OOP) practices. The OOP call to arms is certainly not a
new battle cry, but for those developers who would like
to wade into the .NET waters, there is no choice but to
embrace OOP. Java developers have done so for years
now. That is not to say Delphi developers are unable to
develop object-oriented (OO) systems, merely that until
the arrival of .NET, it was not truly necessary.
As with any useful program, a key aspect of OO
development is the manipulation of data. Take a simple
Person object:
TPerson = class
function getName: string;
function getAge: Integer;
function getBirthData: TDateTime;
end;

Each property of this object is of a different data type. If


you wanted to build a simple UI to display the data in
25

DELPHI INFORMANT MAGAZINE | May 2003

an instance object, you would have to do an appropriate


type conversion for each property of the class. A key to
both the Java and .NET frameworks is the recognition that
developers cannot realistically deal with different specific
data types each time they create a new object. Instead,
they provide simple classes that wrap the various native
data types and provide a consistent interface across all
of them. If a new data type comes along say a 128-bit
integer including it in larger objects doesnt involve
writing a lot of data type-specific code. Instead, a standard
set of functions can be used similar to the other types.
In Java this takes the form of the java.lang.Object class
and in .NET its the System.Object class. A somewhat
similar concept can even be found in Delphi with the
TField class. TDatasets can operate over different types of
fields without worrying about the specific data types of

In

Development

A .NET-ready Framework

IADS_Readable = interface(IADS_Comparable)
['{1169DD10-5361-45DA-BC46-35C1E647A702}']
function stringValue: string;
function toString: IADS_String;
function toVariant: Variant;
function hashCode: Integer;
end;

Figure 1: IADS_Readable definition.

the fields. Different classes, TDateTimeField, TStringField,


etc., inherit from TField to deal with the differences in
data types. Many of the most useful and powerful aspects
of the .NET and Java frameworks, like their collections,
are predicated upon these basic building blocks.

IADS_Readable is the ancestor interface to all objects that


intend to be readable. The complete definition is shown in
Figure 1. What exactly is meant by readable? We decided
early on that all data objects must be able to present
themselves as strings, because strings are the basis for any
user interface or even serialization to XML. We wanted to
be able to quickly and easily have an object present itself
in a control or ShowMessage dialog box. This is the basic
interface that larger objects will rely on to abstract the
peculiarities of different data types. It is akin to .NETs
System.Object or Javas java.lang.Object. Examples of the
utility of this interface will be provided later.
The toVariant method is available because it was found
to be necessary for easy persistence and retrieval when
working with TDatasets. Although a hardcore designer
might argue that any data persistence logic should be
separated into another interface, experience dictated that
this was simply going overboard.

A nice side effect of using object wrappers around the


native data types is that object wrappers provide a perfect
place to locate relevant functionality. For example, in
.NET and Java all the string-handling routines can be
found in their respective String classes, rather than an
external file such as StrUtils. Unfortunately, Borland has
The hashCode function is a fabulous tool. It allows for the
not provided similar type-wrapper objects for Delphi.
easy creation of generic collection objects. It also provides
This does not mean, however, that
a nice shortcut for equality
we cannot create them ourselves!
comparisons. Although it may
That is not to say
Besides gaining productivity, using
not be good enough to trust for
Delphi developers are
the objects presented in this article
equality, it is certainly good enough
unable to develop objectwill offer Delphi developers a
to examine for inequality between
oriented (OO) systems,
jump start into the .NET world, as
objects an important concept
well as provide a nice conceptual
when dealing with collections.
merely that until the
bridge toward Java programming.
arrival of .NET, it was not
We debated about adding a
truly necessary.
The Interfaces
toString(OutputMask: string)
Before we demonstrate the
function. This would allow the data
power of these building-block
to be formatted as it was output to
classes, lets examine their interfaces. The genesis of
a string. The more we discussed it, and the more the classes
this work was a project that demanded a much larger
were used, the more apparent it became that such a method
object framework developed over several years. The high
was more trouble than it was worth. Each data type needed
utility of this approach resulted in continuous reuse of
a different formatting and the masks could vary widely. We
these classes throughout that large project, as well as in
decided to pursue a hierarchy of formatting classes that would
numerous other endeavors.
centralize this functionality more effectively. This may seem
like overkill for strings, but when taking numbers, dates, and
The more we used these classes in designs and
localization into account in a large scale system, the need for a
implementations, the more they were refactored until the
hierarchy of formatting objects becomes much more apparent.
mature version of the interfaces presented here came about.
In the process, it became apparent that lifetime management IADS_Comparable is the interface that must be
of objects was a pervasive issue. Therefore, we decided to
implemented for any object that intends to compare
use reference-counted interfaces extensively throughout
itself to another object:
the framework. This article assumes a solid understanding
IADS_Comparable = interface(IUnknown)
of interfaces and reference-count memory management. A
['{F2FA4445-B0B7-4B83-BC94-7C6C3638DA66}']
very good introduction to using interfaces and referencefunction compareTo(const o: IADS_Comparable): Integer;
counting lifetime management in Delphi can be found in
end;
Parts I and II of Dr. Cary Jensens Delphi Informant series,
Interfaces Revisited (in the March and April 2000 issues).
It defines only one method, which returns a 0 (zero)
For a more general review of how and when to effectively
when the two objects are equal. A greater-than-zero and
use interfaces, see Leo Seamans Delphi Informant article,
less-than-zero result have different meanings depending
Everyday Interfaces (in the June 2002 issue).
on the type of objects being compared. For example,
when two TADS_String objects are compared, they
The following three interfaces are fundamental to these
indicate alphabetic sorting. Why use this interface instead
classes (very similar interfaces can be found in both the
of simple comparison operators such as < or >=? Using
Java and .NET SDKs). They are: IADS_Readable,
the compareTo method provides a simple, centralized way
IADS_Comparable, and IADS_String.
to implement potentially complex equality checks.

26

DELPHI INFORMANT MAGAZINE | May 2003

In

Development

A .NET-ready Framework

IADS_String = interface (IADS_Readable)


['{2CD6A7B1-D990-4587-AF4D-5798F3BCA173}']
function compareTo(anotherString: IADS_String):
Integer;
overload;
function native: string;
function charAt(const pIndex: Integer): Char;
function length: Integer;
function compareToIgnoreCase(
const anotherString: IADS_String): Integer;
function toLowerCase: IADS_String;
function toUpperCase: IADS_String;
function subString(const pStartIndex: Integer):
IADS_String; overload;
function subString(const pStartIndex, pEndIndex:
Integer): IADS_String; overload;
function leftString(const pNumberOfChars: Integer):
IADS_String;
function rightString(const pNumberOfChars: Integer):
IADS_String;
function midString(const pStartIndex, pNumberOfChars:
Integer): IADS_String;
function concat(const anotherString: IADS_String):
IADS_String;
function smartAppend(const delim, anotherString:
IADS_String): IADS_String;
function insert(const anotherString: IADS_String;
const pPosition: Integer): IADS_String;
function beginsWith(const anotherString: IADS_String):
Boolean;
function endsWith(const anotherString: IADS_String):
Boolean;
function indexOf(const anotherString: IADS_String):
Integer; overload;
function indexOf(const anotherString: IADS_String;
const pStart: Integer): Integer; overload;
function trim: IADS_String;
function replace(const stringToReplace, replacement:
IADS_String): IADS_String;
function replaceIgnoreCase(const stringToReplace,
replacement: IADS_String): IADS_String;
end;

Figure 2: IADS_Strings abbreviated method signature.

Consider the difference between two simple strings


and two objects that represent an employee. The more
complex object may contain data such as phone numbers,
an employment history, and benefits information, so the
concept of equality between employee objects can have
many definitions. Therefore, there must be an extensible
manner for implementing this comparison. This is exactly
what this interface is meant to achieve. The programmer
can define his or her own compareTo function that accounts
for the definition of equality between employees. This
interface becomes very powerful when using it in any
type of collection that provides some type of ordering.
The collection can remain completely agnostic of the
actual implementation of the method, as well as the class
itself; it simply knows that compareTo will take care of the
comparison between two implementing classes.
Finally, there is the IADS_String interface. This is the
heart of the bigger, stronger String class that has been
created. Excluding inherited functions, its somewhat
abbreviated method signature is shown in Figure 2. Most
of these methods are self-explanatory; .NET and Java
27

DELPHI INFORMANT MAGAZINE | May 2003

person.setPrefix(TADS_String.valueOf(
tblPersons.FieldByName('prefix').Value));
person.setFirstName(TADS_String.valueOf(
tblPersons.FieldByName('first').Value));
person.setMiddleName(TADS_String.valueOf(
tblPersons.FieldByName('middle').Value));
person.setLastName(TADS_String.valueOf(
tblPersons.FieldByName('last').Value));
person.setSuffix(TADS_String.valueOf(
tblPersons.FieldByName('suffix').Value));
person.setSiblingCount(TADS_Integer.valueOf(
tblPersons.FieldByName('siblings').Value));
person.setBirthDate(TADS_DateTime.valueOf(
tblPersons.FieldByName('birth_date').Value));

Figure 3: Loading data from a database.

programmers will find this interface quite familiar (with a


few crucial additions).
Without delving too deeply into the history of the .NET
Framework, or the arguments over how closely it resembles
Java, it is beneficial to simply examine the interfaces of
Javas java.lang.String class and .NETs System.String
class. For easy reference, the Java String class can be found
at http://java.sun.com/j2se/1.4.1/docs/api/java/lang/
String.html and .NETs String class can be found at http://
msdn.microsoft.com/library/default.asp?url=/library/
en-us/cpref/html/frlrfsystemstringmemberstopic.asp. Youll
notice that not only are these two classes very similar, but so
are the interfaces discussed in this article. In addition to the
IADS_String interface, there are similar ones for integers and
floats, as well as in TDateTime in our API, .NET, and Java. For
a look at those interfaces refer to the accompanying sample
code (see end of article for download details).
The Class at Work
Now that weve touched on the interfaces to be used,
lets return to the example of a Person object. Refer to
ADS_Person.pas, which is available for download. Using this
class, it is stunningly simple to load data from a database,
as shown in Figure 3. And after an object is loaded, its a
piece of cake to plug the values regardless of data type
into a GUI (see Figure 4).
Notice here that when setting the Text property of the
various edit boxes, each member of the Person class uses
its stringValue function. The developer no longer has to
worry about pesky type conversions. Theyre completely
transparent! A good next step would be to generalize the
TADS_Person class so that fields like FirstName, Siblings,
Age, etc. are registered with the object, rather than defined
explicitly with the class. One could imagine then easily
iterating through fields, setting them from a data source, or
getting the values for display or reporting.
Multiple Benefits
Above and beyond the ubiquity of use that a standard access
methodology across data types offers, there are a few other
important benefits of this strategy. The first is localization of
function. Consider the TADS_String class; all the commonly
used string-handling functions are immediately accessible
through this class. This is a key principle of sound OOP:

In

Development

A .NET-ready Framework

txtFullName.Text := person.getFullName.stringValue;
txtFirstName.Text := person.getFirstName.stringValue;
txtHasSiblings.Text := person.getHasSiblings.stringValue;
txtSiblings.Text := person.getSiblingCount.stringValue;
txtAge.Text := person.getAge.stringValue;
txtBirthDate.Text := person.getBirthDate.stringValue;
txtLastFirst.Text :=
person.getFullNameLastFirst.stringValue;
Memo1.Lines.Text := pPerson.asXML.stringValue;

Those who sympathize with Turbo Pete could argue: Hey!


Im a Delphi programmer who cares about writing code
thats similar to other languages? Well, thats a fair point,
but there are larger issues at hand. Upon closer review of
Turbo Petes line of code, youll find that he has made two
important but subtle assumptions.

First, to use string handling functions like CompareText


or MidStr, Turbo Pete must not only know to include
StrUtils in his unit, but also must have access to it.
Keep related functionality in sensibly organized components.
Remember, StrUtils has not always existed and there is
Adhering to this principle often yields cleaner and more
no guarantee that it always will. The functions could be
readily understandable code.
moved to another unit requiring every instance of Turbo
Petes code that uses
Imagine two different Delphi
them to be modified to
The genesis of this work was account for the change.
programmers; well call them
Turbo Pete and Javadot Netsky. a project that demanded a much
Changes such as these
Each has his own programlarger object framework developed are often the most painful
ming style. Turbo Pete typically
in migrating between
over several years.
writes code such as this:
versions of Delphi. Even
if StrUtils were to hang
if CompareText(MidStr(stringOne,2,2), 'hi') = 0 then
around, the implementation of MidStr could change to take
a beginIndex and endIndex as opposed to a beginIndex
Javadot Netsky prefers the following style:
and length. This would require that Turbo Pete find every
instance of MidStr and adjust it.
Figure 4: Assigning data to a GUI.

if stringOne.subString(2,4).compareToIgnoreCase(
TADS_String.valueOf('hi')) = 0 then

First, note that both Turbo Pete and Javadot have


accomplished the same thing with their two different lines
of code. Given this, who has a better style, and why? In
an isolated world where code never has to be seen again
after its compiled the first time, there probably isnt any
difference between the two styles. However, in a world
where Turbo Pete and Javadot have to constantly switch
between languages and reuse their previous work to
facilitate newer projects, Javadot has a genuine edge.

Second, because the comparison between the strings should


be case-insensitive, Turbo Pete must know to use CompareText
instead of CompareStr. There really is nothing intuitive
about these function names that would lead a programmer
to immediately know the difference between them. Simply
reading Turbo Petes line of code would definitely not impart
to the reader that a case-insensitive comparison was occurring,
whereas it is quite obvious from Javadots line of code.
Beyond these assumptions, theres a productivity issue.
Javadot is making use of the IADS_String interface, so

Whats Up with TADS_String.valueOf?


Were advocating a coding style that is very Java or .NET-esque, but some might point out that it depends heavily on
garbage collection for objects instantiated inline. As briefly mentioned, we leave memory management to referencecounted interfaces. For consistency, our API uses our own interfaces throughout, so parameters to string-handling
functions must be of type IADS_String. But what if you want to pass in a constant, such as in the line below?
if stringOne.subString(2,4).compareToIgnoreCase(
TADS_String.Create('hi')) = 0

You must instantiate a TADS_String with that value to pass the parameter. If youre not paying close attention, however, you
could miss that this line of code causes a memory leak! Theres no handle to the instance created by TADS_String.Create,
so theres no way to free it! Therefore, all our objects hide their constructors by making the methods protected. We then
implement valueOf functions, which return an interface to a newly instantiated object rather than the object itself. Using
the line below, the temporary hi object is cleaned up as soon as there are no more references to it, which is immediately
after the compareToIgnoreCase function returns:
if stringOne.subString(2,4).compareToIgnoreCase(
TADS_String.valueOf('hi')) = 0

A similar issue exists with the string-handling routine return values. Because these objects are immutable, each of these
methods returns a new instance of the relevant object. If the subString function were to return a TADS_String object, for example,
there would again be no way to free it. Thus, it is necessary for these functions to return reference-counted interfaces as well.

28

DELPHI INFORMANT MAGAZINE | May 2003

In

Development

A .NET-ready Framework

function TADS_Person.getFullName: IADS_String;


begin
Result := FPrefix.smartAppend(TADS_String.valueOf('. '),
FFirstName.smartAppend(TADS_String.valueOf(' '),
FMiddleName.smartAppend(TADS_String.valueOf(' '),
FLastName.smartAppend(TADS_String.valueOf(', '),
FSuffix))));
end;

Figure 5: IADS_Strings abbreviated method signature.

all the string-handling routines are quickly accessible to


him as member functions of the interface. There is no
Help file searching necessary; he simply hits the period
after stringOne and voil! the list of string-handling
functions appears in his Code Insight menu.
Heres one more similar example to consider in the context of
these arguments. First, Turbo Pete:
articleString := StringReplace(articleString,
'java', 'Java and .NET', [rfReplaceAll, rfIgnoreCase]);

Then Javadot Netsky:


articleString := articleString.replaceIgnoreCase(
TADS_String.valueOf('java'),
TADS_String.valueOf('Java and .NET'));

If you were a potential client and Turbo Pete and Javadot


were two consultants bidding for a project, whos code
would you rather purchase?
Weve talked about one advantage of using these objects,
but what else does a developer stand to gain? Consider the
example function from TADS_Person shown in Figure 5.
This snippet demonstrates the use of a powerful concatenation operation. Smart append will only append the specified
value and delimiter if the value passed to the function is
not an empty string. In addition, if the value that is being
appended to is empty, the delimiter will not be appended,
but the new value will.
Notice that using this function does not require any external
units such as StrUtils to be included, nor does the developer
have to search through the documentation of IADS_String to
look up the function smartAppend. Instead, its a member
function of the IADS_String interface.
The second benefit is that its possible to later go back
and painlessly refine the implementation of these typewrapper classes and their functions. What does painlessly
mean? Well, for the purposes of this article, the classes
such as TADS_String are all implemented by using strictly
Borland Delphi code. However, a simple modification of
the TADS_String.smartAppend function implementation
to make use of a library such as Hyperstring would
immediately yield performance increases for all code that
uses the smartAppend function. After a simple recompile
of the TADS_Person class, its getFullName function is
automatically faster because the implementation of
smartAppend would be improved through the use of
Hyperstrings highly optimized string handling functions.
Theres no need for massive search and replace, no
29

DELPHI INFORMANT MAGAZINE | May 2003

modifying uses clauses, and no worrying about method


name collisions.
Additionally, changes to StrUtils would be completely
transparent to code that uses IADS_String. Therefore, if such
a change occurs, it would be necessary to again modify only
the class that implements IADS_String, and there would be
no changes to any code that uses it. If the implementation
existed in an external library, such as a DLL, the original
code wouldnt even need to be recompiled.
Conclusion and Next Steps
There are a few key concepts from this article that should
really strike a chord. First, momentum is clearly building
in the business and Delphi communities toward the .NET
platform. One key .NET concept that differs substantially
from Delphi is the use of data-type object wrappers. A
consistent interface across data types can enable much
more rapid creation of powerful objects. These objects can
then easily be used to create powerful user interfaces. The
basic framework outlined in this article will allow Delphi
developers to jump on the bandwagon now, as well as
greatly reduce the learning curve in moving to .NET.
Second, object-oriented design techniques yield more
understandable and maintainable systems. Localization of
functionality is a fundamental aspect of object-oriented
design that will make your code more valuable to your
clients or employers.
Finally, the pervasive use of interfaces allows the
developer to optimize the performance of the classes
incrementally, without having to modify all the code that
depends on them. Standard interfaces have allowed Sun
to continually improve on the implementation of the JDK.
This effectively results in free performance gains for
Java developers as they upgrade to higher versions of the
JDK. They also remove the pain of inevitable changes in
various Delphi units.
As the march toward enterprise class systems continues,
and with .NET right around the corner, there has never
been a better time for Delphi programmers to embrace
essential object-oriented concepts.
The examples referenced in this article are available for
download on the Delphi Informant Magazine Complete
Works CD located in INFORM\2003\MAY\DI200305BF.

Bob Fleischman is the president of Automated Document Systems, Inc.,


a Delphi and Java consulting firm in Philadelphia, PA that has provided
services to corporations and law firms since in 1989. He has been writing
Pascal code since getting his first copy of Turbo Pascal for CPM in 1983.
Seth Weiner is a recent graduate of Duke University, majoring in
Computer Science. Seth has worked for Automated Document Systems,
Inc. for the past five years providing Delphi and Java consulting and
programming services. He jointly developed with Bob a complete business
object framework using the extended classes discussed in this article.

T O W A R D
HUMOR

T H E

L I G H T

DELPHI 1-7

By Loren Scott

The Lighter Side of...


Writing Software

he art of writing software is commonly referred


to by programmers as coding or writing
code. This is because most programming

languages are designed to prevent 99.9% of the


population from being able to understand them.
This is all part of the master plan. If everyone knew how to
program, then programmers would only earn about a $1.98
an hour. So, as soon as the average Joe starts to figure out
a particular programming language, they just toss a ++
at the end of it, invent new terms such as OOP, inheritance,
and polymorphism, and that slows them down for awhile.
Cmon! You dont think C++ was invented because it
was really better than plain old C, do you? On the contrary; the average Joe started dabbling in C, and that
messed it up for the rest of us.
Thinking of New Software Ideas
Ideas for new software products sometimes start like this:

Selecting a Programming Language


Some programmers specialize in one particular language or
development environment, while others pride themselves
on speaking many digital tongues. Which language is best?
The one that will make you the most money, of course!
Sometimes, the best language is simply whichever one
looks best on your rsum at that moment.
By the way, before editing your rsum, or applying for
a job using a particular language that you may only be
slightly familiar with, take the time to find out how long
the language has been in existence. I once interviewed
a potential tech support job applicant who professed to
having used Borland Delphi about seven years ago, but
Im not too familiar with it now.
Ill say. At the time of that interview, Delphi had only been
around a little over a year.
Another candidate claimed not only to have had experience
developing in Clipper, but also in Clipper++. Problem is,
there is no such thing as Clipper++.
Needless to say, neither candidate got the gig.

Gee, I wish someone made a program that could compute


the best financial investments in the fledgling pork tongue
futures market.
And, then someone writes it.
New software programs that compete with existing products
are usually conceived with the following statement a
statement familiar to most software developers:

Time Management
As a software developer, the concept of time takes on an
entirely different dimension from that of a normal person
including all non-programmer spouses.
Its a safe bet that in every household that is currently occupied by a programmer and their non-coding significant other,
the following short sentence has been spoken more than once
usually between the hours of midnight and 2:00 am:

This program is a piece of crap! Hell, I could write a program better than this in two days!

Five more minutes, dear!

And, 47 weeks later, a new competitive product is ready to


enter beta testing.

This is then followed by five minutes of fierce typing followed by either: A) a shout of Yes! and the sound of the

30

DELPHI INFORMANT MAGAZINE | May 2003

To w a r d

the

Light

The Lighter Side of ...

PC being clicked off; or B) a shout of Damn! and the


sound of more fierce typing.
When the programmer says five more minutes it isnt an
outright, deliberate lie. It just indicates how long it should
take until the next compile. If everything works as expected
at that point, the task is completed. If not ...
Five more minutes, dear!
To assist those programmers who have a spouse or loved
one who shares your living space but not your concept of
time the following handy diagram is provided.

Start Coding

Complile

Continue Coding

Link

Run

GO TO BED

NO

Does it
work?

YES

This cycle is known as recursive programming. In a nutshell, it


works like this: As long as the damn thing wont do what you
want, your ass isnt moving from that chair.
Time-saving Techniques
There are a variety of coding techniques that can save you
many hours of programming. Here are few of my favorites:

Tip 3. After following Tips 1 and 2, some people may find


your code tough to follow. In an effort to make the code easier
to read, you should write it all in uppercase. In fact, I suggest
you press c one last time, then grab that friendly flathead screwdriver again. Pry c off the keyboard and toss
it in the trash alongside the T.
COBOL programmers have recognized the benefits of all
uppercase programming for years. However, this technique
may cause a problem for most C compilers, as almost all of
their supplied library functions are in lowercase. This is the
ANSI Standards Committees fault. They didnt foresee the
advantages of coding in all uppercase to assist the readability
of non-commented, non-indented code. Simply write your
favorite C compiler manufacturer to request that they supply
new libraries with all functions and keywords in uppercase.
Documentation
If you dont include decent documentation with your product,
people will complain about it. Yet, if you spend months preparing the perfect manual, chances are excellent that not a single
customer will use it for anything other than a footrest.
Get real. Most programmers dont take the time to glance at the
README file that comes with their newly purchased product.
Why should they? Theyre programmers for Petes sake! They
could probably write your product themselves in a weekend
anyway (wink wink, nudge nudge).
Try this simple experiment. Dont include any documentation,
printed or electronic. Include a completely blank README.TXT
file. If you sell 10,000 copies of your product, you might get 10
calls from people who notice the missing docs.

Tip 1. Dont waste time writing a lot of comments in your


code. In fact, eliminate all comments.

Alternatively, when someone calls for technical support, ask if


they had already tried finding the answer in the documentation.
Most will swear that they had, in fact, spent the last two hours
pouring through the documentation before calling tech support.

Tip 2. Dont indent your code. This superfluous pressing


of T and s just wastes time. In fact, if you have a
flat-head screwdriver handy, just wedge it under the T
key and pop the sucker right off of the keyboard; you wont
need it any more. The s, however, is still required for
certain other tasks, so you should keep it for now.

Loren Scott is the co-author of Delphi Database Development (M&T Books, 1996)
and has served in the trenches in software tech support, development, marketing,
consulting, and management since 1990. Currently serving time as a Senior
Software Engineer for a large Southern California company, Loren can be reached
via e-mail at loren@WillowRocks.com.

31

DELPHI INFORMANT MAGAZINE | May 2003

N E W

&

U S E D
By Clay Shannon

PrintDAT!
Simple Printing Tool Delivers Quick Solutions

he debate over which report writer is the


best to use with Delphi has raged ever since
Delphi was released in 1995. Back then

there were few options. The first was ReportSmith


which shipped with Delphi 1, and many users
were dissatisfied with that tool.

QuickReports has shipped with Delphi since version 2


(which also contained ReportSmith). Since that time, many
vendors have entered the fray seemingly ad infinitum
vying for the hearts and pocketbooks of Delphi developers.
These include Ace Reporter, Crystal Reports, R&R Report
Writer, ReportPrinter Pro, Piparti, Shazam Report Wizard,
FastReport, Express Printing System, ReportBuilder, etc.
Most report generation tools have considerable deployment
requirements and/or learning curves. But of all the reporting
tools Ive used with Delphi (ReportSmith, QuickReports,
Actuate, Crystal Reports, Ace Reporter, and PrintDAT!),
PrintDAT! from Grebar Systems is by far the easiest to use.

forgotten by the person asserting the extreme ease-of-use of


the particular technology theyre espousing. In the case of
PrintDAT!, however, the reality is actually easier than what
they claim. Heres the claim:
Drop a TpdtPrintDat component on the form.
Set the TpdtPrintDat components ObjectToPrint property.
Drop a TButton on the form, and add one line of code
to its OnClick event handler:
pdtPrintDat1.Print;

The reality is even simpler; you dont usually need to set


the ObjectToPrint property. If theres a grid on your form,
PrintDAT! will automatically set that value itself.
Note the string grid at run time (see Figure 1) in
particular how not all the data is visible within all the cells
(because of the limited width of some of the columns).

In short, if you just want something simple to print grids, or


the contents of TTable and TQuery descendants, PrintDAT! is
an excellent choice.
Installation
You install the PrintDAT! component in the usual manner,
and with the usual effect. Note the word component, not
components. PrintDAT! is so straightforward that when you
install it, only one component is added to your Component
palette: TpdtPrintDat, on the Grebar palette page.
Development
Grebar Systems motto for PrintDAT! is, If you have a grid,
you have a report (TDBGrid, TStringGrid, etc.). However, a
grid doesnt have to be assigned to TpdtPrintDats ObjectToPrint
property; it can also be a TTable, TQuery, TClientDataSet,
TDecisionQuery, or many other types of components that
contain data. PrintDAT! also works with many of the common
third-party controls and database engines.
PrintDAT!s documentation promises a simple three-step
process for printing a report. Its not uncommon when
such claims are made that certain steps are (conveniently?)
32

DELPHI INFORMANT MAGAZINE | May 2003

Figure 1: The string grid at run time.

New

&

Used

PrintDAT!

Figure 3: The General tab of the Report Options dialog box. There are also tabs
for Output, Style, Titles, and Setup that provide end users enormous opportunity
to customize reports.

auto-sized and, where necessary, word wrapping is done to


accommodate longer values.
PrintDAT! is unique in that the end-user has more options to
set than the developer. TpdtPrintDat only has a handful of
properties, methods, and events, but the built-in previewer
has a Report Options dialog box (see Figure 3) that gives the
user control over a multitude of report settings.

Figure 2: As easy as 1-2-3.

Figure 2 shows some sample output that resulted from


following the simple steps enumerated previously (step
2 was unnecessary as the TStringGrid was discovered by
the TpdtPrintDat component) and then selecting the Print
button at run time. As you can see, data that was hidden
at run time is made visible by PrintDAT! The columns are

Just the Facts


PrintDAT! is an awesome product for Delphi 3-7 and
C++Builder 4 and 5. The deployment requirements and
learning curve are minimal, and the end-user has more
options to set than the developer. If all you need are simple
reports, PrintDAT! is for you.
Grebar Systems Inc.
P.O. Box 2926
Winnipeg, Manitoba
Canada R3C 4B5
Phone: (204) 942-3301
Fax: (204) 942-3301
Web Site: www.grebarsys.com
Price: PrintDAT! Component with full source US$179.

33

DELPHI INFORMANT MAGAZINE | May 2003

Documentation
PrintDAT! is very well documented, especially for such
an intuitive tool. It comes with an Acrobat tutorial
(TUTORIAL.pdf) and a developer help file (PD_DEV.hlp), as
well as an end-user help file (PD_USER.hlp), which provides
context-sensitive help from within the built-in previewer. Of
additional benefit to the developer are the comprehensive
demonstration programs that ship with PrintDAT!
Deployment
If you want to set the report users report settings,
deploy the .pxt file, which PrintDAT! automatically
creates when you modify any of the report settings as
different from their default values (as long as you answer
in the affirmative when asked if you want to save the
modified settings). These settings are not global, or even
application-specific, but are report-specific, so each report
can retain unique settings.
The Bottom Line
PrintDAT! is an awesome product. Its not as flexible
and full-featured as some other reporting tools, such as
ReportBuilder and Ace Reporter, but if all you need are
simple reports, and dont require full control over every
aspect of visual presentation, dont hesitate to use PrintDAT!

Clay Shannon is a Borland and PDA-certified Delphi 5 developer and the author
of The Tomes of Delphi: Developers Guide to Troubleshooting (Wordware,
2001) as well as the novel he claims is the strangest one ever written, The
Wacky Misadventures of Warble McGorkle (see http://www.winsite.com/bin/
Info?12500000036639 for more information about this and other novels he has
written). You can contact him at BClayShannon@aol.com.

N E W

&

U S E D

By Lee Inman

TUsersCS Security Component


Access Control for Delphi Applications

he first thing you notice when you


visit the Tools&Comps Web site
(www.toolsandcomps.com) is the poor job

someone has done with the Portuguese to English


translation. Now, this isnt necessarily a showstopper, but it sure caused me to think about
what I would have to read and then re-read in
order to understand and use the product.

Additionally, the main Web page, rather than introduce


products, presents a history listing the many releases and
patches. Even though we all know that software goes
through a revision process, placing that information where
it is one of the first things the potential customer
sees should probably be avoided. If I were
searching for security components, I would
probably have moved on to the next
one in my search list based just
on these first impressions.
Installation
First impressions aside, I
decided to dig into the
package and see what it
had to offer. Installation
went as well as with other
component libraries that
dont bother to provide an
installation program, i.e.
it was painful. Given the
ease and fairly low cost of
installation packages, I prefer
an automated installation.
There is a help directory as
part of the archive, but it
34

DELPHI INFORMANT MAGAZINE | May 2003

contains only the users manual. There are two versions:


one in Portuguese, and one poorly translated to English. I
like a component help system integrated right into Delphi.
Ive done it myself. Its a pain for the developer to put
together and to maintain, but if you want the product to
be easy to use, its a necessity.
After building and installing the packages I found two
new components on my Delphi 5 palette under the
ToolsAndComps tab: TUsersCS and TUsersCSReg.
The Components
The two components work together to provide a combination
of component access control and administrative routines.
TUsersCS implements an innovative approach to protecting
individual components even menu items. When using
the administrative functions, you select items (form, button,
menu, etc.) and assign users rights to them. If a user does
not have rights to use a particular item, its either
disabled or not visible (at run time).
There are about a dozen tables that store data
such as user information, profiles, form and
component information, access history,
and audit information. Theyre
created for each application or
application group, and must be
available to the application
at run time. Even if your
application has no use for
a database, you must still
include and support one.
The Coup de Gras
Besides a slight issue with
startup and shutdown speed,
the component library has
a real problem with security
one of its claimed attributes.
The elaborate collection of

New

&

Used

TUsersCS Security Component

Just the Facts


The TUsersCS security components for Delphi and C++Builder
allow the developer to add password protection and access
control to resources such as buttons, menus, DBGrids, TFields,
etc. However, the database used to control such access is readily available to anyone able to open and edit a table.
Tools&Comps
Rua do Beijo, 9
Novo Mxico
Vila Velha, ES
CEP: 29104-080
Brazil
Phone: 55 27 99602760 and 55 27 33891138
E-Mail: info@toolsandcomps.com
Web Site: www.toolsandcomps.com
Price: Ranges from US$249.95 for a TUsersCS Developer
license up to USD$999.95 for the Enterprise license.
database tables are entirely in plain text with the exception of
the password column. Any user with the capability to open
and edit a table can wreak havoc with security. With a little

35

DELPHI INFORMANT MAGAZINE | May 2003

ingenuity very little I was able to open the database table


used to store user access information and allow myself rights
to anything. I simply copied my encrypted password to the
password field for the administrator (master in the sample
database) and then logged on as master using my password.
Conclusion
If security isnt an issue and you dont mind including
some sort of database engine with your application, then
TUsersCS may be just what you need to provide access
control for forms and individual components. But, if you
need security, you may want to look elsewhere.
Maybe in time and with a few more revisions the issues
Ive raised here will be corrected. At that time, this
product would be a good choice for a simple plug-andplay approach to application access control.

Lee Inman works for Premiere Conferencing in Colorado Springs, CO as


a Senior Research Engineer exploring and designing telephony solutions.
Prior to this, Lee worked at TurboPower Software for a number of years and
played a crucial role in the development of many TurboPower products. Lee
has a bachelors degree in electrical engineering and has been programming
for nearly 20 years. Lee likes to ski, hike, and of course, program. He can
be reached online at leei@att.net.

F I L E

N E W

.NET on the Net

By Alan C. Moore, Ph.D.

n the past two months we have


explored Microsofts .NET initiative
from several points of view. First we
considered initial developer reactions,
including some related to Microsofts
Visual Studio .NET. In our second
installment, we explored .NET technologies in more detail, reviewing printed
resources and a lexicon of important
terms. This month well explore Web
sites devoted to .NET. Be aware that a
Google search returned over 800,000
pages for Microsoft .NET, so the
pages listed here represent a small but
hopefully significant sampling.
Microsoft sites. It shouldnt be
surprising that one of the richest
Internet sources for .NET is hosted
by Microsoft. The main page is
www.microsoft.com/net. This is a
gateway to many other pages that contain information about the usefulness
of .NET and some resources you can
download, but this site seems to target
businesses more than developers.
MSDN, the Microsoft Developer Network, is better suited to
developers needs. Its .NET page
(msdn.microsoft.com/library) is loaded
with useful information and resources.
One link central to this site is Technology Roadmap: Building Smart Client Applications. This page provides
developers with a basic understanding
of .NET technologies, emphasizing
client application development. It
includes many useful links to essential
topics, including XML Web Services.
In addition to articles and tutorials
available on the site, MSDN provides
information on upcoming Webcasts.
Another important Microsoft site is
the .NET Framework home page at
msdn.microsoft.com/netframework
36

DELPHI INFORMANT MAGAZINE | May 2003

where you can download the latest version of the framework.


There are also sites and pages devoted to related technologies. For example, ASP.NET has a site of its own
at www.asp.net. Here you can read
many articles, get up-to-date news,
and download free tools. What about
ADO.NET? The article Introducing
ADO+: Data Access Services for the
Microsoft .NET Framework by Omri
Gazitt on MSDN is a great primer
(msdn.microsoft.com/msdnmag/
issues/1100/adoplus). ADO+ is the
new version of ADO for the .NET
Framework using XML as part of its
foundation. The article discusses
ADO+s design and the various
classes it uses.
Independent sites and platform wars.
There are some valuable independent
sites too. Youll recall that last month
we examined several .NET books. If
you want to further explore printed
resources, visit www.dotnetbooks.com
(this site is devoted completely to
.NET books and provides reviews and
sample chapters of the many books
now available). There are links to
various categories, including .NET
in general, ADO.NET, ASP.NET, C#,
XML, SOAP, and so on. Books are
also organized by title.
There are many sites that explore the
comparison between .NET and J2EE.
Check out OReillys juxtaposition of
the two platforms at java.oreilly.com/
news/farley_0800.html. You could go
a step further and compare the same
code running on each platform by
visiting www.gotdotnet.com/team/
compare/petshop.aspx. This site takes
a famous Java example application

and rewrites it for .NET, with some


rather impressive comparisons. As they
describe it: We implemented Suns
own J2EE best practices blueprint
application, the Java Pet Store, using
C# and Microsoft .NET. The result? The
exact same application functionality
was implemented in just 1/4 the code
using C#/ASP.NET, and it performs
over 10 times faster than the latest
published benchmarks of the J2EE
application in independent tests conducted by VeriTest, a respected leader
in independent software testing.
Impressive, dont you think?
GotDotNet (www.gotdotnet.com)
includes much more than this comparison application. In fact, they boldly
proclaim themselves as The .NET Community Web Site, and justify that claim
by providing useful information, news,
message boards, articles, and links.
The links include .NET downloads,
the .NET Framework and its SDK, and
Visual Studio .NET. There is also information about upgrades, breaking news,
code samples, and tutorials. Youll also
find sections on tools and utilities, Web
Services tools, XML tools, training and
special events, and so on. Its one of
the richest .NET sites Ive encountered.
In a mood to explore, I visited the Featured Site for March
2003, and discovered another
fascinating resource: CodeGuru
(www.codeguru.com). This site
equals GotDotNet in pride, proclaiming itself the number one developer
site! Its devoted mainly to Microsoft
tools, with many articles and links,
as well as strong support for .NET.
One article I found particularly helpful was Running Multiple Versions
of the Framework Side-by-Side.

File

New

You may find, as I did in exploring


SAPI.NET, that a particular .NET
technology with which you want
to work requires a specific version;
youll find answers here.
The Delphi connection. Of course
the main source of information
on Delphi for .NET is Borland. A
good place to begin is John Kasters
article on the Borland Developer
Network entitled Borland reveals
Microsoft .NET platform support
strategy (bdn.borland.com/article/
0,1410,28386,00.html). If you enjoy
Loren Scotts new humor column
Toward the Light in this magazine,
I recommend you read Conspiracy
Theory: MSs .Net IS Borlands
Product (delphi.about.com/library/
weekly/aa112902c.htm). This piece
explores the following theory: Is it
possible that the whole .NET Framework is Borlands idea? These conspiracy fans use certain known similarities, such as Microsofts use of the
Sender parameter in various functions
to get the ball rolling. Of course they
mention the Anders connection, as
well as some of the recent agreements
between Borland and Microsoft. But
then they explore some truly strange

37

DELPHI INFORMANT MAGAZINE | May 2003

speculations regarding confidential


details of the settlement in the lawsuit. I wont spoil the fun by revealing
more; check it out for a laugh or two.
Of course you would expect Dr. Bob
to address the Delphi .NET connection on his Web site, and he
does in a major way. Indeed, his
site (www.drbob42.com/dotnet/
home.htm) includes a gold mine of
articles and links. Topics include
C#, COM, ASP.NET, and others. Of
course, everything is presented with a
Borland/Delphi spin. There are even
examples you can try with the Delphi
.NET preview. This site is a must!
Conclusion. When I started this series
three months ago, I asked if .NET
is the great hope, or a great hype.
Microsoft has certainly been enthusi-

astic about its new platform. To their


delight, even some of its sharpest
critics share that enthusiasm. Among
developers in general, the new .NET
language, C#, is especially popular.
For Delphi developers specifically, .NET
represents some exciting possibilities. For
component developers, one can expand
the potential marketplace from thousands to perhaps millions of developers.
In mixed shops that include Delphi,
C++ (and soon C#), and Visual Basic,
the Borland-Microsoft War can end
once and for all. And the tools available
to Delphi for .NET programmers will
expand exponentially with the release of
this new tool.
So, whats my verdict? .NET offers great
hope to the entire spectrum of Windows
developers, including Delphi developers.

Alan Moore is a professor at Kentucky State University, where he teaches music theory and humanities. He
was named Distinguished Professor for 2001-2002. He has been named the Project JEDI Director for 2002-2003.
He has developed education-related applications with the Borland languages for more than 15 years. Hes the
author of The Tomes of Delphi: Win32 Multimedia API (Wordware Publishing, 2000) and co-author (with
John C. Penman) of The Tomes of Delphi: Basic 32-Bit Communications Programming (Wordware Publishing,
2003). He also has published a number of articles in various technical journals. Using Delphi, he specializes in
writing custom components and implementing multimedia capabilities in applications, particularly sound and
music. You can reach Alan on the Internet at acmdoc@aol.com.

Vous aimerez peut-être aussi