Sei sulla pagina 1di 39

November 2002, Volume 8 Number 11

Delphi Does

.NET
The Delphi for .NET
Compiler Preview

Cover Art By: Arthur A. Dugoni Jr.

ON THE COVER
4

First Look

Delphi Does .NET Alexei Fedorov and Natalia Elmanova


Alexei Fedorov and Natalia Elmanova demonstrate how to create
console applications with the preview version of the Delphi for
.NET compiler, including how to call .NET classes from Delphi and
Delphi-created classes from VB .NET. The ramifications are huge
this is exciting stuff!

FEATURES
9

Greater Delphi

Delphi 7 and XP Themes Corbin Dunn


From the basics to the implementation details, Borland R&D engineer
Corbin Dunn introduces one of Delphi 7s great new features, its native
support for Microsoft Windows XP themes.

12

Dynamic Delphi

22

OP Tech

27

Sound+Vision

Run-time Packages Rick Spence


Rick Spence shows you how to create run-time packages that allow
you to build flexible, modular applications that are easier to deploy,
and perfect for constructing products to be sold in optional sections.

DirectX Audio Plug-ins Ianier Munoz


Ianier Munoz demonstrates how to create DirectShow filters by wrapping DirectX Media Objects (DMO) using a custom COM class factory.
The filters can be used by any audio-capable, DirectX plug-in host.

REVIEWS
31

Pascal Analyzer

Product Review by Clay Shannon

An XP Application Wizard Fernando Vicaria


Borland QA engineer Fernando Vicaria shares the code necessary to use
Delphis Open Tools API to create a wizard that makes it a snap to create Delphi 6 and 7 applications that use Windows XP themes.

34

17

Delphi Tools

38

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

Delphi at Work

XML Building Blocks: Part III Keith Wood


Keith Wood completes his generic-XML-component series by demonstrating how to display DOMs in TreeView, StringList, and Memo
components and from Microsofts Internet Explorer.

1 November 2002 Delphi Informant Magazine

NitroSort

Product Review by Bill Todd

DEPARTMENTS

Delphi
T O O L S

New Products
and Solutions

Book Picks
Mastering Kylix 2
Marco Cant and Uberto Barbini
SYBEX

Hurricane Software Releases WinGREP 4


Hurricane Software announced
WinGREP 4, which features
many additions. WinGREP
enhances the productivity of
developers working on new
projects, maintaining established
programs, or joining on-going
development teams.
New features of WinGREP
4 Standard Edition include
real-time search and replace;
expanded use of regular expressions, including form feed, line
break, carriage return, horizontal tab, literal question mark,
period, asterisk, backslash,
and colon; integration with
virtually all commonly-used
software development environments; a Search Matches pane
that displays file names with
results from search criteria;
and extended IDE configuration, history management, and
enhanced file mask management.
WinGREP 4 Professional
Edition includes binary, archive,

DOC, and PDF searching, as


well as file and directory exclude.
APIs for plug-in IDE, file type
filter, and virtual file system are
also included.

Hurricane Software, Inc.


Price: Standard Edition, US$89;
Professional Edition, US$149.
Contact: mail@hurricanesoft.com
Web Site: www.hurricanesoft.com

Crystal Decisions Releases Crystal Reports 9


ISBN: 0-7821-2873-4
Cover Price: US$49.99
(704 pages, CD-ROM)
www.sybex.com

Delphi/Kylix Database
Development
Eric Harmon
SAMS

ISBN: 0-672-32265-X
Cover Price: US$49.99
(500 pages)
www.samspublishing.com

2 November 2002 Delphi Informant Magazine

Crystal Decisions
announced Crystal
Reports 9. Designed
for use in all three
major development environments
(Java, .NET, and
COM), Crystal
Reports 9 addresses
key requirements
in the developer
and IT community
today: the need
for improved productivity and high
efficiency in report
creation, the need
for tight integration of reporting into enterprise
Web applications, and the need
for dynamic end-user access,
interaction, and modification.
Crystal Reports provides
sophisticated technology for
rapidly transforming data
from virtually any data source
including XML, OLAP,
enterprise, and relational data
sources into interactive
content. Using Crystal Reports
9, developers and IT professionals can tightly integrate content

into .NET, Java, and COM


applications for end-user viewing, interaction, and modification, and can give end-users the
ability to access and navigate
key report elements via wireless
devices, portals, and Microsoft
Office documents.
Crystal Decisions addresses
the growth of the enterprise
Web application development
market by including more
than 50 new features and
enhancements to its reporting

technology, such as Report


Application Server, Report
Parts, Component Re-use, support for Java, and integration
with Crystal Enterprise.
Crystal Decisions
Price: Standard Edition, US$195; Professional Edition, US$495; Developer Edition,
US$595; and Advanced Edition, US$1,995.
Special upgrade pricing is available for
existing customers.
Contact: (800) 877-2340
Web Site: www.crystaldecisions.com

Delphi
T O O L S

New Products
and Solutions

Book Picks
JBuilder 7.0 EJB Programming
Chuck Easttom
WORDWARE

ISBN: 1-55622-874-0
Cover Price: US$49.95
(464 pages)
www.wordware.com

Special Edition Using XML,


Second Edition
David Gulbransen, et al.
QUE

ISBN: 0-7897-2748-X
Cover Price: US$49.99
(864 pages)
www.quepublishing.com

3 November 2002 Delphi Informant Magazine

WiredRed Software Announces e/pop SDK


WiredRed released e/pop RealTime Software Development Kit
(SDK). The SDK will enable
in-house developers and a wide
variety of business partners,
including systems integrators,
ISVs, OEMs, hardware appliance manufacturers, and ASPs,
to create custom client/server
and Web-based instant messaging (IM) applications, based on
WiredReds Real-Time Routing
Architecture. The SDK was
designed to meet the needs of
these organizations to rapidly
develop secure IM applications
that are capable of scaling-up
in complex enterprise network
environments.
The SDK supports Visual
Studio and Delphi 6 development
toolsets, with certified languages
including Visual Basic .NET,
Visual C#, and Delphi 6. The
SDK includes COM and ActiveX
objects and Delphi 6 native components, sample applications with
source code, online documentation, and set up of the development environment.
The source code samples include
a fully functional, secure IM client
with an easy-to-use interface;

a one-way
secure IM
application
for emergency
notifications
and other
broadcast
applications; and a
Web-based
IM interface.
These can
be readily
adapted to
internal and
customerfacing
applications, such as CRM, ERP,
Enterprise Information Portals
(EIP), Web front-ends, line-ofbusiness, and legacy applications.
WiredReds e/pop Enterprise
Server provides the server component for the e/pop SDK RunTime Client. The Server delivers
authentication, security, directory service integration, groups,
profiles, and other central management capabilities to custom
client applications. The Server
supports multi-server topologies,
multi-threading, and symmetric multi-processing (SMP),

enabling secure IM applications


for thousands or tens-of-thousands of users. In addition, it
provides private routing over
LANs, WANs, VPNs, NATs,
proxies, and firewalls.
WiredRed Software
Price: The e/pop Enterprise Server, e/pop
SDK, and run-time client access licenses are
available under site-license agreements.
Pricing ranges from US$15 to US$40 per
user. The e/pop Real-Time SDK is available
for a free 30-day trial.
Contact: (858) 715-0970
Web Site: www.wiredred.com

AidAim Software Introduces CompressionMaster Suite 1.10


AidAim Software released CompressionMaster Suite 1.10, a data
compression library available for
Delphi and C++Builder. The suite
includes four independent data
compression products: FlexCompress, Easy Compression Library,
Single File System, and ZipForge.
CompressionMaster Suite is a
complete solution for developing
applications with data compression and encryption capabilities.

FlexCompress is a high-speed
compression library developed
to provide archive functionality for your applications.
Easy Compression Library is
a toolkit with everything you
need to compress and encrypt
files, streams, strings, data
packets, and even BLOB fields.
Single File System is a library
thats designed to provide an
easy way to work with multiple

files and folders stored as a part


of a single file with advanced
compression and encryption
functionality. ZipForge is a
ZIP compression library with
advanced transaction system.
AidAim Software
Price: US$255 without source;
US$495 with source.
Contact: support@aidaim.com
Web Site: www.aidaim.com

Developer Express Ships ExpressSpreadSheet


Developer Express introduced
ExpressSpreadSheet, a cross-platform spreadsheet control built for
the Windows and Linux operating
systems. With only a few property
settings, you can enable many features and give your end-users the
same control they have come to
expect from full-featured spreadsheet products, such as Microsoft
Excel. ExpressSpreadSheet offers
an array of built-in functions to
run-time customization, from cell

merging to multiple display styles.


The ExpressSpreadSheet suite
supports Delphi 4, 5, and 6;
C++Builder 4, 5, and 6; and
Kylix 2. ExpressSpreadSheet
offers custom draw support,
absolute and relative formula
addressing, load/save data,
built-in operators and
functions, custom function
registration, full cell style
control, cell borders and edge
styles, fill patterns, background

colors, text formatting, intuitive


cell management, merge/split
cells, data sorting, row and
column resizing, lock/unlock
rows and columns, undo/
redo, customizable views, and
full printing support via the
ExpressPrinting System.
Developer Express, Inc.
Price: US$299.99 with full source code.
Contact: info@devexpress.com
Web Site: www.devexpress.com

First Look
Microsoft .NET Framework / Delphi for .NET Compiler Preview / Delphi 7 Studio

By Alexei Fedorov and Natalia Elmanova

Delphi Does .NET


The Delphi for .NET Compiler Preview

he new version of Borlands Delphi product, Delphi 7 Studio, comes with a preview version of Delphi Compiler for .NET. In this article, well begin to discuss
its features and possibilities. However, keep in mind that this material is based on
a pre-release version of the software; the shipping version may differ from what is
described here.

The Framework in Brief


According to Microsoft, the .NET Framework
is a platform for building, deploying, and running Web Services and applications. It provides
a highly productive, standards-based, multilanguage environment for integrating existing
investments with next-generation applications
and services as well as the ability to solve the
challenges of deployment and operation of
Internet-scale applications.
The .NET Framework consists of three main
parts: Common Language Runtime (CLR),
.NET class library, and ASP.NET. The .NET
class library contains classes, interfaces, and
types that are used in the application development process and provide access to system
functionality. The .NET Framework types are
Common Language Specification (CLS) compliant. This makes them possible to be used from
any programming language; all we need is a
compiler that conforms to the CLS.
The CLR is the execution engine for .NET applications. It provides a set of services, including loading
and executing code, application memory isolation,
verification of type safety, converting the IL code
(platform-independent code generated by compilers) to native platform-dependent code, access to
type information (metadata), enforcement of code
access security, exception handling, etc.
Instead of producing platform-dependent
executables, as is typical of compilers, the .NET
4 November 2002 Delphi Informant Magazine

compilers (at this time, there are more than 20)


produce code in the Microsoft Intermediate
Language (MSIL). This IL code is converted to
native platform-dependent code by the CLR when
the application is called the first time. The code
that targets .NET is called managed code. Its also
self-describing, meaning it contains metadata: data
about types, classes, and methods.
.NET applications consist of assemblies: compiled
collections of code and metadata. Every assembly contains a manifest that includes its name,
version, culture, the lists of files, dependencies,
classes, and their methods.
The class libraries of the .NET Framework,
and custom classes that developers create, are
organized in namespaces. These namespaces can
contain classes and other namespaces. Generally,
if an application uses a class from another
assembly, it will need to refer to the namespace
containing that class.

The Delphi Compiler for .NET


As weve just discussed, the Microsoft .NET
Framework can be used by any programming
language. All thats needed is a compiler that
creates an MSIL code. The Delphi Compiler for
.NET does exactly that, so now we can turn our
attention to the compiler and how to use it.
Before we begin to create .NET applications,
youll need to make some preparations.
First, you need to have the Microsoft .NET

First Look

Figure 1: A simple console application at run time.

Figure 2: Directory listing with created executables.

Framework installed. You can get it at http://msdn.microsoft.com/


netframework/downloads/howtoget.asp. Then, you need to install
the Delphi for .NET Preview that comes with Delphi 7 Studio.
The command-line compiler executable is named dccil.exe, and it can
be located in the \Program Files\Borland\Delphi for .NET Preview\bin
directory. Delphi for .NET Preview installer adds this directory to
the %Path% environment variable. Along with the command-line
compiler itself, here you will find the dccil.cfg file that contains a
reference to the directory \Program Files\Borland \Delphi for .NET
Preview\units, where you can find files with the extension .dcuil and
.dcua. The .dcuil files contain compiled Delphi units that are produced
by the compiler for every unit except the main unit, as well imported
CLR namespaces that can contain classes from multiple assemblies.
Theyre stored as .dcuil files to speed compilation. The .dcua files
describe assemblies that contribute to a namespace; theyre used by the
compiler to know whether a .dcuil file should be recompiled.
As with the previous versions of the Delphi command-line compiler, if
you run the dccil.exe without any options, youll get the list of its keys
and parameters. To build your first application, youll use the -CC
parameter to indicate that you want to create a console application.

A Simple Console Application


To create a console application in Delphi you need to place all its
source code in the *.dpr file. Since the Delphi for .NET Preview
doesnt come with an IDE, use Notepad to write the source code.
Heres the console application:
program Hello;
begin
Writeln('Hello, World!');
end.

Save the source to a Hello.dpr file, and call the command-line


compiler with the following options:

Figure 3: Running the Microsoft IL Disassembler on


Hello_Delphi.exe.

Figure 4: An interactive console application at run time.

As youd expect, the resulting application produces the Hello,


World! string in the command-line window (see Figure 1). Note
that you can omit the -CC option if you include the {$APPTYPE
CONSOLE} directive. Also note that, contrary to Microsoft compilers
for .NET (such as the Visual Basic .NET or C# compilers), the default
type of application produced is a GUI, not a console.
Note that the source code for the Hello, World! application contains
no indication that you want to create a .NET application. You use the
same WriteLn Pascal function that is part of Delphi RTL to produce a
string on the console. Behind the scenes, this function is implemented in
terms of .NET Framework classes, and this implementation is used by
the compiler. You can find exactly how this function is implemented in
the Borland.Delphi.System.Pas file thats in the \Program Files\Borland\
Delphi for .NET Preview\source\rtl directory.
As you can see, Borland has done a great job of creating a
compiler that allows you to port existing Delphi applications to
.NET, and therefore save your investment in a Delphi code base.
On the other hand, instead of using Pascal functions, you can use
.NET Framework classes directly. For example, the following code
will produce the same output:
program Hello_Delphi;
begin

dccil -CC hello.dpr

Console.WriteLine('Hello, World!');
end.

5 November 2002 Delphi Informant Magazine

First Look
program Env_Demo;
{$APPTYPE CONSOLE}
uses System;
var
Args : array of string;
Str : string;
I
: Integer;
begin
Str:= System.Environment.CommandLine;
Console.WriteLine('The command line is ' +
System.Environment.CommandLine);
Console.WriteLine('The directory is ' +
System.Environment.CurrentDirectory);
Console.WriteLine('The .NET Framework version is ' +
System.Environment.Version.ToString);
Console.WriteLine('The Path variable is ' + System.
Environment.ExpandEnvironmentVariables('%Path%'));
Args := System.Environment.GetCommandLineArgs;
for I := Low(Args) to High(Args) do
Console.WriteLine('The command line argument number ' +
IntToStr(I) + ' is ' + Args[I]);
end.

Figure 5: This code uses the System.Environment class of the


.NET Framework.

Note that in this example, youve used the WriteLine method


of the Console class of the .NET Framework. Since this class is
implemented in the System namespace, you dont need to specify
it; its referenced automatically by the compiler.
Lets create the same application with Visual Basic .NET. Its code
will look like this:
Imports System
Module Hello_VB
Sub Main()
Console.WriteLine("Hello, World!")
End Sub
End Module

Microsoft has implemented similar classes to support porting of pre.NET VB code in the Microsoft.VisualBasic.Compatibility assembly that
comes as part of the Microsoft .NET Framework. (If youre interested
in Delphi for .NET code internals, the best way to examine them is to
study the IL code generated by the compiler using the ILDASM utility.)
Lets go back to the console application and add some interactivity to
it. For example, you can use the ReadLine method of the Console class:
program Greetings;
var
Name : System.String;
begin
Console.WriteLine('What is your name?');
Name := Console.ReadLine();
Console.WriteLine('Hello, ' + Name + '!');
end.

The result of executing this application is shown in Figure 4.


Now we know how to create console applications using methods of
the System.Console class that are part of the .NET Framework. Now
lets turn to the System.Environment class to access command-line
arguments and environment variables.

Command-line Arguments and Environment Variables


The most common use of console applications is to create utilities with a minimal user interface. Most such utilities need to
access arguments passed in via the command line. The .NET
System.Environment class provides information about the current
environment and platform. The Environment class has methods for
obtaining the path of system folders, names of logical drives, the
operation system version, etc.
Among the Environment class properties are CommandLine, which
returns a string with a command line passed to the application;
CurrentDirectory, which indicates the directory where the process was
started; and Version, which returns the .NET Framework version.
You can also use its ExpandEnvironmentVariables method to obtain
values of different environment variables (it accepts a string value
with the variable name as a parameter), and its GetCommandLineArgs
method to obtain command-line arguments.

To create an appropriate executable, use the following command:


As an example, create the application shown in Figure 5.
vbc hello_vb.vb

If you execute this application with the following command line:


Figure 2 shows the output of the DIR command executed from the
directory where our executables are created. We can see that the size of
the Delphi executable file is almost 80 percent larger than the size of
the VB executable. Why?

env param1 param2 param3

it will appear as shown in Figure 6.

Lets examine the resulting executable using the


Microsoft IL Disassembler (ILDASM). To do this,
enter the following command:
ILDASM hello_delphi.exe

Figure 3 shows the results of such an examination.


We can see the Borland.Delphi.System branch that
contains sets of specific classes and functions that
are designed to provide the compatibility with the
old Delphi code; they contain implementations
of Delphi-specific functions and classes. This creates
Figure 6: The run-time result of the code shown in Figure 5.
the code overhead in the resulting executable.
6 November 2002 Delphi Informant Magazine

First Look
program Ref_Demo;
{$APPTYPE CONSOLE}

program Ref_Demo2;
{$APPTYPE CONSOLE}

uses System.Reflection;

uses System.Reflection;

var
I : Integer;
T : System.Type;
M : System.Reflection.Module;
Types : array of System.Type;
begin
T := Int32.GetType;
M := T.Module;
Console.WriteLine('The module name is ' +
T.Module.Name + chr(10));
Console.WriteLine('The list of types: ');
Types := M.GetTypes;
for I := Low(Types) to High(Types) do
Console.WriteLine(Types[I].FullName);
end.

var
I : Integer;
T : System.Type;
Asmb, LoadedAsmb : System.Reflection.Assembly;
Types : array of System.Type;
begin
LoadedAsmb := Asmb.Load('mscorlib.dll');
Console.WriteLine('The list of types: ');
Types := LoadedAsmb.GetTypes;
for I := Low(Types) to High(Types) do
Console.WriteLine(Types[I].FullName);
end.

Figure 7: Listing types defined in the current module.

Figure 9: Listing the types defined in the mscorlib.dll assembly


using reflection.

Figure 8: The run-time result of the code from Figure 7.

Getting Type Information


One of the useful features of the .NET Framework is reflection.
Those knowledgeable about Delphis runtime type information
(RTTI) will find reflection familiar; it provides access to type data (or
metadata) at run time. Essentially, reflection is RTTI for .NET. The
topic of reflection is a large one; here we will just scratch the surface.
Lets see what data types are included in a Delphi application,
besides the .NET Framework types. To do this, we need to look at
the module created by the Delphi for .NET compiler, the current
module for example. To get module metadata, we can use the
Module class of the System.Reflection namespace. An example of
such code is shown in Figure 7, and Figure 8 shows the result of
running the application.
Weve already seen this list, havent we? Its the same list from the
Borland.Delphi.System branch revealed by ILDASM. These are
the types that provide functionality for this application and turn
Delphi language unit code to .NET classes.
But how do we list the types defined in some arbitrary assembly? This
can be done by loading the assembly into memory and using the
Assembly class of the same System.Reflection namespace. Figure 9 shows
the example application, and Figure 10 shows some of the output
produced by this application.
Reflection also allows us to get information about class methods,
their parameters, and other application metadata. As you can see,
the concept of reflection resembles the part of the COM API that
is responsible for working with type libraries. However, the .NET
version is far easier to use.
7 November 2002 Delphi Informant Magazine

Figure 10: Output produced by the code shown in Figure 9.

library TestNamespace.TestDelphiClass;
uses System;
type
TTestClass = class
constructor Create;
procedure Output(S: string);
end;
constructor TTestClass.Create;
begin
Inherited Create;
end;
procedure TTestClass.Output(S: string);
begin
Console.WriteLine('Hello, ' + S + '!');
end;
end.

Figure 11: Creating a new class with a method named Output.

Language Interoperability
Another great thing .NET provides is language interoperability
based on the Common Language Specification the set of rules
every .NET-compliant language must obey. This means we can
mix code written in one language into applications written in
another language, and even create descendants of classes written
with different languages.

First Look
Imports System,TestNamespace.TestDelphiClass
Module DelphiVBDemo
Sub Main()
Dim DelphiClass As New TTestClass
Dim Name As String
Console.WriteLine("What is your name?")
Name = Console.ReadLine()
DelphiClass.Output(Name)
End Sub
End Module

Figure 12: Using a Delphi type in a VB .NET application.

Lets look at how Delphi can join the family of .NET languages.
Lets start with a new Delphi class. To do this we need to create
a library where we define such a class (see Figure 11). This class
is named TTestClass, which has an Output method that accepts a
string and writes it to the .NET Console object.
Now lets use this newly created Delphi class in the Visual Basic
.NET application. To do this, we need to refer to the appropriate
namespace defined in our library, as shown in Figure 12.
To compile this VB .NET application, use the following command:
vbc DelphiVBDemo.vb /r: TestDelphiClass.dll

The /r: parameter indicates that we refer to the


TestDelphiClass.dll assembly (created with the Delphi compiler)
in this VB .NET application. The result of executing this application is shown in Figure 13.
Its just that simple to use a Delphi object in a VB .NET application, and the implications are huge. This means that Delphi
developers can create and market classes and components to an
immense .NET community.

Conclusion
Weve created console applications with the preview version of the
Delphi for .NET compiler. We also saw how to implement simple

8 November 2002 Delphi Informant Magazine

Figure 13: Using a Delphi class in a VB .NET application.

interactive applications, how to obtain data about environment


and command-line arguments, and how to get information about
types at run time. Finally, we discovered just how easy it is to use
a Delphi class from a VB .NET application.
Several good examples of using the Delphi compiler for .NET can
be found at Borlands site (http://dotnet.borland.com). There are
also several articles devoted to this compiler at Borland Community Network (http://bdn.borland.com).
All of the example applications referenced in this article are available
on the Delphi Informant Magazine Complete Works CD located in
INFORM\2002\NOV\DI200211AF.

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


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

Greater Delphi
Windows XP Themes / Delphi 7

By Corbin Dunn

Delphi 7 and XP Themes


Using XP Themes in Your Components and Applications

ne of the great new features in Delphi 7 Studio is its native support for Microsoft
Windows XP themes. This article will cover the basics of enabling theme support
in your applications and custom controls.
Delphi developer Mike Lischke did the majority
of the basic theme support, and has some freeware
theme code for other versions of Delphi available
on his Web site at http://www.delphi-gems.com/
ThemeManager.html. I had a hand in some of
the development; I fixed some bugs, created the
TXPManifest component, and incorporated theme
support into the IDE itself.

you must instruct the operating system to use


comctl32.dll version 6, which is included only in
Windows XP and is not redistributable. By default,
applications that run on XP use comctl32.dll
version 5, which is not theme-enabled but is more
compatible with existing applications. Figure 1
shows an example of what you would put in a
manifest file to use comctl32.dll version 6.

Themes in Your Applications

I acquired the information for the sample


manifest from MSDN. Visit http://
msdn.microsoft.com/library/default.asp?url=/
library/en-us/sbscs/setup/application_
manifests.asp for more information on exactly
what you can put in the file. To see the effects
of a manifest, save the contents of Figure 1 in a
text file named delphi32.exe.manifest. Copy this
file to your Delphi 7 \bin directory and restart
the IDE. Now, you should see the IDE come up
completely themed, with virtually no work at all.

To get theme support in your applications, you


must create a manifest for your executable. A
manifest is a simple XML file with the same name
as your application, but with the extension .manifest added to it, e.g. Project1.exe.manifest.
The manifest tells the operating system information about the executable, including what
version of certain DLLs to use. To enable themes,

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>


<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
manifestVersion="1.0">
<assemblyIdentity
type="win32"
name="DelphiApplication"
version="1.0.0.0"
processorArchitecture="*"/>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
publicKeyToken="6595b64144ccf1df"
language="*"
processorArchitecture="*"/>
</dependentAssembly>
</dependency>
</assembly>

Figure 1: A sample manifest file, named Project1.exe.manifest,


for theme support.
9 November 2002 Delphi Informant Magazine

To create a manifest file for your application


you would need to redistribute one more file
with it. The way around this is to create a special
resource, and link the manifest directly into your
application. First, create a manifest file, such as
sample.manifest. Then, create an .rc file, such as
WindowsXP.rc, that contains one line:
1 24 "sample.manifest"

Compile it to a resource from the command


prompt with brcc32, thus:
C:\>brcc32 WindowsXP.rc

This generates WindowsXP.res, which you can link


into your Delphi application by placing the following directive anywhere in your source code:
{$R WindowsXP.res}

Greater Delphi
procedure TMyButton.Paint;
var
TextVert, TextHorz: Integer;
ElementDetails: TThemedElementDetails;
begin
TextVert :=
(ClientHeight - Canvas.TextHeight('X')) div 2;
TextHorz :=
(ClientWidth - Canvas.TextWidth(Caption)) div 2;
if ThemeServices.ThemesEnabled then
begin
{ Draw the themed "button" look by getting the
correct element details for a button element.
FMouseInUs will be True when the mouse is in
the control. }
if not Enabled then
ElementDetails := ThemeServices.
GetElementDetails(tbPushButtonDisabled)
else if MouseCapture and FMouseInUs then
begin
ElementDetails := ThemeServices.
GetElementDetails(tbPushButtonPressed);
Inc(TextVert);
Inc(TextHorz);
end
else if FMouseInUs then
ElementDetails := ThemeServices.GetElementDetails(
tbPushButtonHot)
else
ElementDetails := ThemeServices.GetElementDetails(
tbPushButtonNormal);
{ Use the ThemServices to draw the details. }
ThemeServices.DrawElement(Canvas.Handle,
ElementDetails, ClientRect);
{ Draw text, setting the Brush.Style to bsClear so we
can see the theme elements behind the text. }
Canvas.Brush.Style := bsClear;
Canvas.TextOut(TextHorz, TextVert, Caption);
end
else
begin
{ Non-themed: no disabled look for simplicity,
and no "hot tracking". }
if MouseCapture and FMouseInUs then
begin
DrawFrameControl(Canvas.Handle, ClientRect,
DFC_BUTTON, DFCS_BUTTONPUSH or DFCS_PUSHED);
Inc(TextVert);
Inc(TextHorz);
end
else
DrawFrameControl(Canvas.Handle, ClientRect,
DFC_BUTTON, DFCS_BUTTONPUSH);
Canvas.Brush.Color := Color;
Canvas.TextOut(TextHorz, TextVert, Caption);
end;
end;

Figure 2: This Paint method for TMyButton implements XP themes.

This is rather tedious, however, and things in Delphi are supposed


to be easy. So, we at Borland have done this work for you. You
can drop a TXPManifest component onto your form (from
the Win32 tab from the Component palette) and get the same
resource file linked into your application. The component itself
doesnt do anything, and it actually took more time for me to
create the icon than it did for me to create the component! If you
drop the component on a form or data module, the component
will add the XPMan unit to your uses list. This unit has the
magical directive:

Themes in Your Custom Components


Simply enabling theme support will make most of the controls in a
typical Delphi application appear themed. However, some ownerdrawn areas and custom components may not appear themed. In
these cases, you will have to do the theme drawing yourself.
The easiest way to do this is by creating a simple custom control that
acts like a button. As an example, I created a class named TMyButton
that derives from TCustomControl and overrides the Paint method.
Its shown in Figure 2.
The ThemeServices object is declared in Themes.pas, so you must
add Themes to your uses list in order to access it. Themes.pas is a
high-level wrapper around the Microsoft API, which is declared in
UxTheme.pas. If you ever need direct access to the theme API, you
can use UxTheme.pas.
Before you draw themed elements, check if ThemeServices.ThemesEnabled
is True. It will be True if themes are turned on and available for your
particular application. On the other hand, ThemeServices.ThemesAvailable
will be True if the application simply has access to the theme API, but
themes are turned off. You must always check at least one of these values
before drawing with ThemeServices. Otherwise, youll get access violations
when your application isnt run on Windows XP or is run without the
right comctl32.dll.
The main thing to notice about the Paint method is the
TThemed-ElementDetails variable. ThemeServices.GetElementDetails is
used to get the correct drawing details for a particular element.
Because this demo component is simulating a button, it grabs the
appropriate TThemedButton details, depending on the current state. Take
a look at all of the options in the Themes unit to see what you can draw.
For a demonstration of them in use, Lischkes ThemeExplorer application is available at http://www.delphi-gems.com/ThemeManager.html.
Once the correct TThemedElementDetails has been grabbed,
ThemeServices.DrawElement is used to paint the item into the
given rect. The rest of the Paint routine involves some simple
painting for when themes arent available. For the rest of the
components implementation, download the sample source that
is associated with this article (see the end of the article for details).
The source shows some details not discussed in this article, such
as capturing mouse events and repainting.

Theme Changes in the VCL


An astute observer of the Visual Component Library (VCL) will notice a
few new TControlStyle types: csNeedsBorderPaint and csParentBackground.
Both were added specifically for theme support. csNeedsBorderPaint
should be added to the ControlStyle of visual controls that need to have
their non-client area border painted by the current theme. Typically, you
wont need to add this style to your components. Its also used in the
VCL in a few spots, such as TCustomRichEdit.

{$R WindowsXP.res}

To understand what the second style, csParentBackground, does, youll


need an example. You may have noticed that a themed TPageControl
appears with a slight vertical gradient, as Figure 3 shows. The red,
outlined controls you see in the figure are another custom component named TSimplePanel.

added to it, causing it to give you theme support automatically. If


you want to remove theme support, however, simply deleting the
TXPManifest component wont do it. You must remove XPMan
from your uses list as well.

In the TSimplePanel components Paint routine, the code simply


frames the controls ClientRect and then draws a red square in the
controls center. In un-themed applications, the control looks fine, as
you can see in Figure 4.

10 November 2002 Delphi Informant Magazine

Greater Delphi
Add or remove csParentBackground in the
ControlStyle by using the ParentBackground
property. By default, the property is protected
because it isnt applicable to all TWinControls.
Any component in which youd want to turn
on or off this transparent ability should publish
the ParentBackground property. At Borland, we
published it for controls such as TPanel and
TCustomFrame, in which one might want them
to paint with their assigned Color, and not their
Parents background.

Conclusion

Figure 3: Themed TPageControl,


TSimplePanel, and TMyButton.

Figure 4: Un-themed TPageControl,


TSimplePanel, and TMyButton.

In the themed version (Figure 3), the TSimplePanel on the top left
of the TPageControl appears to have transparent portions, but the
bottom-right one does not. The top left simply has csParentBackground
added to its ControlStyle, and the bottom-right one does not.
Whenever a TWinControl processes a WM_ERASEBKGND message, it will paint with its Parents background if csParentBackground
is in its ControlStyle. This gives the illusion that portions of the
control are transparent.

11 November 2002 Delphi Informant Magazine

For most applications, simply adding a manifest will enable theme support on Windows XP.
For others that have a lot of custom drawing
and controls, you will have to do a little work
to enable theme support. Dont forget to download the sample source code to see the complete
source for TMyButton and TSimplePanel.

The source referenced in this article is available on the Delphi Informant


Magazine Complete Works CD located in INFORM\2002\NOV\
DI200211CD.

Corbin Dunn is a research and development software engineer for Borland Software
Corporation. When he isnt hacking new features into the IDE, he can be found
scaling steep rocks in the Santa Cruz mountains or hanging out in his tree house
(http://www.corbinstreehouse.com). Readers may reach Corbin at cdunn@borland.com.

Dynamic Delphi
Windows XP Themes / Open Tools API / Wizards / Delphi 6, 7

By Fernando Vicaria

An XP Application Wizard
Extend Delphi 6, 7 IDEs to Support Windows XP Themes

elphi is an extensible development tool, not only when it comes to creating and
using your own components, but also with respect to its IDE. Delphis Open Tools
API (or OTA) enables you to create your own tools that work closely with the IDE. This
article will address two seemingly disparate topics: Windows XP themes support and
wizards. In it, I will show you how to extend the IDE to create a wizard that helps you
create an XP application project.
The OTA is a set of interfaces that allows you to
extend the Delphi IDE in many different ways.
One well-known way is to create a custom component and add it to the Component palette. Other
ways include creating a property and component
editors, a version control manager, or a wizard.
Although each of these is used differently, each
uses the same mechanism to attach to the IDE.
Note that the OTA is available only with the
Enterprise and Professional editions of Delphi. You
can add and use the wizard in this article if you
have the Standard edition, but you will not be able
to modify it or recompile it.

What Is a Wizard?
A wizard (previously known as an expert) is just a
tool to help you accomplish a particular task. It might
Interface

Description

IOTAFormWizard

Typically creates a new unit, form, or


other file.

IOTAMenuWizard

Automatically added to the Help menu.

IOTAProjectWizard

Typically creates a new application or


other project.

IOTAWizard

A miscellaneous wizard that doesnt fit


into other categories.

Figure 1: The type of wizard depends on the interfaces the wizard


class implements.
12 November 2002 Delphi Informant Magazine

be one of many tasks involved in creating a new


component or a new specialized application, such as
a database application, or, in this case, an application
that has built-in support for XP themes.
Using the OTA is simply a matter of writing
classes that implement certain interfaces, and
calling on services other interfaces provide.
Your OTA code must be compiled and loaded
into the IDE at design time as a design-time
package or inside a DLL. Thus, writing an OTA
extension is somewhat like writing a property or
component editor.
There are four kinds of wizards, depending on the
interfaces the wizard class implements (see Figure 1).
They differ only in the way the user interacts with
them. Im not going to get into the specifics of each
type here; please refer to the Delphi documentation
if you want more information.

Out with the Old, In with the New


Since Delphi 5, the way we create wizards has
changed. Now, the process is interface-based.
The old component-based implementation
(of classes such as TIExpert) is now deprecated.
You shouldnt use it except to maintain compatibility with older versions of the IDE. Deprecated files include ExptIntf.pas, FileIntf.pas,
IStreams.pas, ToolIntf.pas, VcsIntf.pas, and
VirtIntf.pas.

Dynamic Delphi
Borland is working hard on new documentation that covers the
OTA, and you should see the results in Delphi 7 Studio, which has
a new help file dedicated to this issue. Its still hard to find accurate
OTA sample code on the Internet. I hope this article will help to
spread some of what the folks at Borland are preaching. At the end
of this article youll find a list of good references and information
on how to download the files that accompany this article.

Implementing the Wizard


To start building your wizard, you need to define a new class
named TXPAppProjectWizard. This class (see Figure 2) will implement IOTAProjectWizard, which will make your wizard appear in
the Object Repository in the New Items dialog box.
As you can see in the source code that accompanies this article, this
is a very simple class that contains the purpose, location, name, and
author information for the wizard. The only method worth discussing is Execute, which will tell Delphi whos in charge of providing the
actual contents of the wizard. The method uses the global variable
BorlandIDEServices to obtain an IOTAModuleServices object to create
and register a new project with the IDE:
{ TXPAppProjectExpert }
procedure TXPAppProjectWizard.Execute;
begin
(BorlandIDEServices as IOTAModuleServices).
CreateModule(TXPAppProjectCreator.Create);
end;

The next class you need to define is TXPAppProjectCreator (see


Figure 3), which implements the IOTAProjectCreator interface.
This interface is responsible for creating the project source, and
any other necessary modules (forms or units).
As with TXPAppProjectWizard, most of the methods in
TXPAppProjectCreator will have a simple implementation, often
consisting of a single line of code. The interesting ones will be
NewDefaultModule, NewProjectSource, and GetFileName. Ill take you
through their implementations with a little more detail, and you can
check the others later by looking at the source.
NewDefaultModule is responsible for creating the default module
for the project (in this case, the dpr file). Note that we also use
BorlandIDEServices for this purpose:
procedure TXPAppProjectCreator.NewDefaultModule;
begin
// Create a new default module.
(BorlandIDEServices as IOTAModuleServices).CreateModule(
TXPAppModuleCreator.CreateModule as IOTAModuleCreator);
end;

NewProjectSource is responsible for creating and returning the


project source as an IOTAFile interface object (Ill describe the
IOTAFile interface later in this article):
function TXPAppProjectCreator.NewProjectSource(
const ProjectName: string): IOTAFile;
begin
Result:= TXPSourceFile.CreateNamedFile(ProjectName,
Format(XPProjectSource, [ProjectName]));
end;

GetFileName is responsible for finding a unique name to give to


the project source thats about to be created. GetFileName iterates
through the projects of the current project group, if any, to make
13 November 2002 Delphi Informant Magazine

{ TXPAppProjectWizard }
TXPAppProjectWizard = class(TNotifierObject, IOTAWizard,
IOTARepositoryWizard, IOTAProjectWizard)
public
{ IOTAWizard }
procedure Execute;
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
{ IOTARepositoryWizard }
function GetAuthor: string;
function GetComment: string;
function GetPage: string;
function GetGlyph: Cardinal;
end;

Figure 2: Defining the TXPAppProjectWizard class.

sure the chosen name doesnt clash with that of an existing project
(see Figure 4).
Another class necessary for this wizard is TXPAppModuleCreator
(shown in Figure 5), which takes care of generating a new module for
your project in this case, the default main form for your application. It will implement the IOTAModuleCreator interface. Delphi uses
IOTAModuleCreator to add new forms and units to the current project.
Implementing TXPAppModuleCreator will also be very simple.
The interesting methods this time will be CreateModule and
NewProjectSource. CreateModule will generate a new module by
calling GetNewModuleAndClassName, which will create a unique
module name and class name, given a class prefix such as Form:
constructor TXPAppModuleCreator.CreateModule;
begin
inherited Create;
(BorlandIDEServices as IOTAModuleServices).
GetNewModuleAndClassName('Form', FModuleName,
FFormName, FFileName);
end;

NewImplSource provides the source code for the module. The source for the
default module (and for the project) is hard-coded into a constant string:
function TXPAppModuleCreator.NewImplSource(const
ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
begin
Result := TXPSourceFile.CreateNamedFile('', Format(
XPModuleSource, [ModuleIdent, FormIdent,
AncestorIdent]));
end;

Some of the methods of the interfaces Ive described will take or


return another interface type named IOTAFile. IOTAFile is used to
supply custom file contents for a creator, such as your module and
project creators. Delphi will call the IOTAFile-derived class to obtain
the file contents and related information:
{ TXPSourceFile }
TXPSourceFile = class(TInterfacedObject, IOTAFile)
private
FFileName: string;
FSource: string;
public
constructor CreateNamedFile(const FileName,
Source: string);
{ IOTAFile }
function GetSource: string;
function GetAge: TDateTime;
end;

Dynamic Delphi
{ TXPAppProjectCreator }
TXPAppProjectCreator = class(TInterfacedObject,
IOTACreator, IOTAProjectCreator)
public
{ IOTACreator }
function GetCreatorType: string;
function GetExisting: Boolean;
function GetFileSystem: string;
function GetOwner: IOTAModule;
function GetUnnamed: Boolean;
{ IOTAProjectCreator }
function GetFileName: string;
function GetOptionFileName: string;
function GetShowSource: Boolean;
procedure NewDefaultModule;
function NewOptionSource(const ProjectName: string):
IOTAFile;
procedure NewProjectResource(const Project: IOTAProject);
function NewProjectSource(const ProjectName: string):
IOTAFile;
end;

Figure 3: Defining the TXPAppProjectCreator class.

function TXPAppProjectCreator.GetFileName: string;


const
PROJEXT: array [Boolean] of string = ('.bpr', '.dpr');
var
i, j: Integer;
Found: Boolean;
TempFile1, TempFile2: string;
ProjGroup: IOTAProjectGroup;
begin
Result := GetCurrentDir + '\Project%d' + PROJEXT[True];
ProjGroup:= GetActiveProjectGroup;
// Iterate through projects of current project group.
if ProjGroup <> nil then
begin
for j := 0 to ProjGroup.ProjectCount - 1 do begin
Found := False;
TempFile2 := Format(Result, [j+1]);
// Find next available name for project file.
for I := 0 to ProjGroup.ProjectCount - 1 do
try
TempFile1 := ProjGroup.Projects[i].FileName;
if CompareText(TempFile1, TempFile2) = 0 then
begin
Found := True;
Break;
end;
except on E: Exception do
if not (E is EIntfCastError) then
raise;
end;
if not Found then begin
// No project matched the current name; we're OK.
Result := TempFile2;
Exit;
end;
end;
Result := Format(Result, [ProjGroup.ProjectCount+1]);
end
else
Result := Format(Result, [1]);
end;

{ TXPAppModule }
TXPAppModuleCreator = class(TInterfacedObject, IOTACreator,
IOTAModuleCreator)
private
FFileName: string;
FFormName: string;
FModuleName: string;
public
constructor CreateModule;
{ IOTACreator }
function GetCreatorType: string;
function GetExisting: Boolean;
function GetFileSystem: string;
function GetOwner: IOTAModule;
function GetUnnamed: Boolean;
{ IOTAModuleCreator }
function GetAncestorName: string;
function GetImplFileName: string;
function GetIntfFileName: string;
function GetFormName: string;
function GetMainForm: Boolean;
function GetShowForm: Boolean;
function GetShowSource: Boolean;
function NewFormFile(const FormIdent,
AncestorIdent: string): IOTAFile;
function NewImplSource(const ModuleIdent, FormIdent,
AncestorIdent: string): IOTAFile;
function NewIntfSource(const ModuleIdent, FormIdent,
AncestorIdent: string): IOTAFile;
procedure FormCreated(const FormEditor: IOTAFormEditor);
end;

Figure 5: The TXPAppModuleCreator class.

main form of the project: XPProjectSource and XPModuleSource,


respectively. You can also have these strings included in the main
unit of the wizard as a resource.

XP Themes
By now, you might have noticed that all that the main project
source does differently from a conventional Visual Component
Library (VCL) application is to add the following line of code:
{$R DelphiXP.res}

This resource file, however, includes the manifest for your application, which is mandatory for XP applications. Windows XP has
a theme manager that changes the look and feel for most of the
standard Windows controls.
The actual manifest is the file DelphiXP.manifest. You can edit this
file to fit the requirements of your company. The file is basically an
XML document containing the name, version, author, and dependencies information as well as other information related to your
executable (or assembly, in the .NET world). The manifest is also
responsible for telling Windows which version of the comctl32
library its supposed to use with your application: the older version
(5.8) thats compatible with all flavors of Windows, or the newer
version (6.0) thats compatible only with Windows XP.

Some Additional Notes

The main idea behind version 6.0 was to clean up the code contained within the DLL. The downside is that now there are two
versions of the same control to maintain: one that is backwardcompatible with all previous versions, and one that is not.

Apart from the classes described so far, youll also need a couple
of general-purpose functions to identify the current active project
and project group, if either exist. Youll also define two string
constants to hold the source code for the project file, and the

By default, all programs written under Windows XP will use version 5.8, maintaining the same look and feel as the legacy Windows
applications. To use version 6.0, you must include a manifest in

Figure 4: The GetFileName method.

14 November 2002 Delphi Informant Magazine

Dynamic Delphi
{ Register the wizard. }
procedure Register;
var
S: TResourceStream;
Services: IOTAServices;
TargetFile: string;
begin
RegisterPackageWizard(
TXPAppProjectWizard.Create as IOTAProjectWizard);
S:= TResourceStream.Create(
HInstance, 'DELPHIXP', RT_RCDATA);
try
if Supports(BorlandIDEServices,
IOTAServices, Services) then
begin
TargetFile:=
Services.GetBinDirectory + 'DelphiXP.res';
if not FileExists(TargetFile) then
S.SaveToFile(TargetFile);
end;
finally
S.Free;
end;
end;

Figure 7: The XP application wizard project.

Figure 6: Including the Register procedure.

your application that Windows will read to find out which version
it should load. If it cant find one, either built into the application
as a resource or as a standalone file, Windows will load the old
version. If you alter the manifest file, you will have to recompile the
resource file, using Delphis resource compiler (brcc32.exe).
Note that the Windows XP theme manager wont do anything
to your owner-drawn controls. For these controls, you will have
to ask uxtheme.dll to draw them. Delphi 7 will include its own
implementation for a theme manager and a translation for the
uxtheme DLL.

Registration
To complete your wizard and make sure it has been added to the
IDE properly, you need to include a Register procedure, much
the same way you do when adding your custom components to
Delphis Component palette (Figure 6).
As you can see, the Register procedure delegates the responsibility
of registering the wizard with the IDE to RegisterPackageWizard.
Then, the procedure retrieves the resource file that will be added
to the applications your wizard is going to create. The resource
file is placed inside the wizards resource. This is done to guarantee that your wizard can always find the resource file.

Wrapping Everything Together


To help you compile and install the wizard, I bundled it into a
package. Figure 7 shows the contents of the packaged project. Just
load XPAppWiz.dpk into Delphi, compile it, and install it. To
create a new XP application, select File | New | Other. Then select
the New tab, move the scroll bar all the way to the bottom, and
you will see the new icon for the XP application wizard
(see Figure 8).
Thats all there is to it. From this point, you can create XPcompatible applications transparently, exactly the same way
you did before for the previous versions of Windows.
Of course, theres much more to XP support than just adding a manifest
to your project, and some components will still look the same way they
15 November 2002 Delphi Informant Magazine

Figure 8: The New Items dialog box and the new XP application wizard.

did before. (See Corbin Dunns article Delphi 7 and XP Themes on


page 9 of this issue for more information on this topic.) Delphi 7 and the
VCL take care of that, at least to some extent, and will give themes support to a good number of the components found on the IDE.

Warnings
Microsoft has eliminated a lot of the support code in the comctl32
library. Consequently, some of the standard controls might not
behave exactly as they did with older versions, and, in some
cases, they can even crash applications compiled for the previous
versions. One typical example is Windows new ImageList control,
which is incompatible with the old ones. This incompatibility
could crash old executables compiled with Delphi 6 or earlier,
for example, when you decide to give a manifest to it. Eddie
Churchill, a research and development engineer at Borland, has a
very interesting article in which he exposes a bit of his frustration
and Borlands ongoing battle to maintain compatibility with
comctl32. You can find his article at Borlands community site at
http://community.borland.com/article/0,1410,28423,00.html.

Conclusion
In this article, I tried to expose a relatively unknown and often
misused part of Delphi, the OTA. You saw how to create a
simple project wizard for creating an application that would

Dynamic Delphi
take part in the new Windows XP themes feature. The main
difference between this wizard and the already popular File | New
| Application is that you included a manifest file into the project
as a resource. This manifest will tell Windows, at the time your
application is loaded into memory, whether Windows should use
the new version of Microsofts comctl32 DLL.

Further Reading
The following is a list of OTA-related samples, tutorials, and
resources available on the Internet:
Eriks Open Tools API FAQ and Resources
http://www.gexperts.org/opentools
DelphiOTA http://www.xs4all.nl/%7Erappido/delphi/
delphi.html
Ray Lischners Open Tools API
http://www.tempest-sw.com/opentools
Borlands Open Tools API Newsgroups
news://newsgroups.borland.com/borland.public.
delphi.opentoolsapi
On XP and themes, try these:
The Microsoft Developer Network
http://msdn.microsoft.com/library/default.asp?url=/library/
en-us/dnwxp/html/winxpintro.asp
Abouts Windows XP Manifest in Delphi
http://delphi.about.com/sitesearch.htm?terms=Windows%20
XP%20Manifest%20in%20Delphi%20&SUName=delphi&T
opNode=3042&type=1
Mike Lischkes Windows XP Theme Manager
http://www.delphi-gems.com
All files referenced in this article (manifest, resource, package, etc.)
are available on the Delphi Informant Magazine Complete Works
CD located in INFORM\2002\NOV\DI200211FV.

Fernando Vicaria is a quality-assurance engineer at Borland Software Corporation in Scotts Valley, CA. He is also a freelance technical author who writes
about Delphi and C++Builder issues. Fernando is specialized in VCL and CLX
frameworks. 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.

16 November 2002 Delphi Informant Magazine

Delphi at Work
XML / DOMs / Interfaces / Delphi 6

By Keith Wood

XML Building Blocks


Part III: Displaying XML in the User Interface

n the Part I and Part II of this three-part series, you learned that the concept of XML
building blocks is built around two interfaces: one for producing XML documents as
Document Object Model (DOM) objects and another for accepting a DOM object with
which to work. Components that implement one or both of these interfaces can be
linked to form chains of processing for an XML document. Because the properties of
these components are published, much of the work can be done at design time, leaving only a single call to the first producer to get the sequence going.

As you may remember from the other articles, a


base class for constructing these building blocks
is TXMLBuildingBlock. From this class came
components that loaded an existing document to
create a DOM, applied an XSL transformation to
the DOM, and wrote out a DOM to a file or a
stream. In the second part of this series, you saw
how DOMs could be generated from other sources
(SQL databases and text files), how a time-stamp
element could be added, and how several DOMs
could merge to produce a combined document.

In this article, Ill develop several consumer building blocks that interact with GUI controls. By
using these new building blocks, you can create
and modify your DOM easily before displaying
the results to the user. The options Ill cover for
presenting a DOM are TreeView, StringGrid, and
Memo components, and a Web browser.

TreeView
XML documents have an inherent hierarchical
structure, which makes the TreeView an ideal way
of presenting the documents contents. Each node
in the DOM becomes a node in the tree with corresponding parents and children.
The TXBBTreeView class populates a TreeView from
the DOM it is given (see Listing One at the end
of this article). A new property, TreeView, lets you
associate a TreeView component with this building block. Remember to override the Notification
method to clear this reference if the TreeView
control should be deleted.

Figure 1: Displaying a DOM in a tree.


17 November 2002 Delphi Informant Magazine

As an XML consumer, this class overrides the


ProcessDocument method and fills the tree by
stepping through the DOM structure recursively
and calling the PopulateTree method. At each level,
you pass in the current DOM node and the tree
node to use as the parent of the new entry (starting
with nil for the root). The display name of the tree

Delphi at Work
nodes depends on the DOM nodes type:
Document nodes are hard-coded, text-based
nodes use their nodeValue, and all others use
their nodeName.
The ShowNodes property is the set of node types
from the DOM to include in the TreeView.
Once a node is rejected because of this filter,
all of its children are also ignored. You should
always include ntDocument in the set. You wont
see much without having ntElement there, as
well. The ShowNodes property is published so it
can be set at design time.
To make working with the underlying DOM
easier, it would be useful to have access to the
actual DOM nodes corresponding to each tree
node. Unfortunately, you cannot save a direct
reference to an interface in the Data property
of a tree node. Instead, you need a simple
wrapper class that maintains that reference.
Figure 2: A single-node string-grid display.
So, as each node is added to the tree, a new
TXBBNodePointer is created around the DOM
node and is set as the object associated with that node. Then, you can
{ Display a DOM within a Memo. The XML text corresponding
to the DOM is displayed in the attached Memo
retrieve the original DOM node through the following code:
DOMNode := TXBBNodePointer(trvXML.Selected.Data).Node;

This approach creates another problem, however: Now, you have


additional objects that must be released at the appropriate time. The
TXBBTreeView class supports this through its ClearTree method,
which frees all the objects currently used in the tree. ClearTree is
called whenever the tree is reconstructed and whenever the associated
TreeView component is changed.
As each node is added to the tree, its ImageIndex and SelectedIndex
properties are set based on the DOM nodes type. Then, by assigning
an appropriate image list to the trees Images property, icons are
shown next to each node to identify its type. A default set of images
is provided by the DefaultNodeImages variable from the buildingblock unit. Figure 1 shows the results of loading an existing XML
document and displaying it in a tree.

StringGrid
For XML documents with a degree of repeated sub-structure, a
StringGrid offers an alternative and more compact way of presenting
the contained data. The document should have a main element that
contains multiple copies of a single type of sub-element, which has
just text content or only one further layer of child elements. Then,
each of the multiple elements becomes a row in the grid, with its
contents forming the columns.

(Memo property). }
TXBBMemo = class(TXMLBuildingBlock)
private
FMemo: TMemo;
protected
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
function ProcessDocument(const Document: IDOMDocument):
IDOMDocument; override;
public
constructor Create(AOwner: TComponent;
const Memo: TMemo = nil); reintroduce; overload;
published
property Consumer;
property Memo: TMemo read FMemo write FMemo;
end;
{ Copy the DOM as text into the Memo. }
function TXBBMemo.ProcessDocument(
const Document: IDOMDocument): IDOMDocument;
var
Stream: TMemoryStream;
begin
if not Assigned(Memo) then
Exit;
Memo.Lines.Clear;
Stream := TMemoryStream.Create;
try
(Document as IDOMPersist).SaveToStream(Stream);
Stream.Position := 0;
Memo.Lines.LoadFromStream(Stream);
finally
Stream.Free;
end;
Result := Document;
end;

An alternate display is possible for a single node and its contents. In


this format, the first column lists the names of any child nodes, and
the second column displays its value. This format is suitable for each
multiple element, as I described.

Figure 3: Putting the text of a document in a Memo.

The TXBBStringGrid component (see Listing Two at the end of


the article) caters to both these formats through its SingleNode
property. The StringGrid property links the component to the
StringGrid to fill. The component also promotes the inherited
Consumer property to published status so that additional DOM
consumers can be added to the chain.

The ProcessDocument method is overridden to populate the StringGrid with the documents content. If no StringGrid has been
connected to it, an error occurs. Otherwise, the StringGrid is sized
appropriately based on the format desired and the number of child
nodes the document has. Then, the cells of the grid are filled. For the

18 November 2002 Delphi Informant Magazine

Delphi at Work
TXBBMemo has a property named Memo that
identifies the Memo control to update, and
that promotes the visibility of the Consumer
property to allow for extending the processing
chain (see Figure 3). If the attached Memo is
deleted, the class removes any references to it
through the Notification method.
In the ProcessDocument code, the
component saves the contents of the
DOM passed in to a stream in memory
and then loads the Memos Lines property
from that stream. See Figure 4 for an
example of using this component. An XML
document generated from a database query
is amended by including a time-stamp
element at its start, before sending the text
version of that DOM to the Memo.

Figure 4: An XML document as text.

Web Browser
{ Display a DOM within a Web browser. }
TXBBWebBrowser = class(TXMLBuildingBlock)
private
FTempFileName: TFileName;
FWebBrowser: TWebBrowser;
protected
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
function ProcessDocument(const Document: IDOMDocument):
IDOMDocument; override;
public
constructor Create(AOwner: TComponent); overload;
override;
constructor Create(AOwner: TComponent;
const WebBrowser: TWebBrowser); reintroduce; overload;
published
property Consumer;
property TempFileName: TFileName
read FTempFileName write FTempFileName;
property WebBrowser: TWebBrowser
read FWebBrowser write FWebBrowser;
end;
{ Copy the DOM contents into the Web browser. }
function TXBBWebBrowser.ProcessDocument(
const Document: IDOMDocument): IDOMDocument;
begin
if not Assigned(WebBrowser) then
Exit;
{ Unfortunately it must be saved to a file first. }
(Document as IDOMPersist).Save(TempFileName);
WebBrowser.Navigate(TempFileName);
Result := Document;
end;

Figure 5: Sending XML to a Web browser.

single-node format, the cells just contain the name and text value of
the child nodes. The text value is retrieved by the GetText function,
which combines text from the current node and one level down to
handle simple sub-elements. For the multiple-node format, first
the component must determine which column to use, based on the
nodes name, before inserting the text. New columns are added as
necessary. Figure 1 shows the StringGrid in its multiple-node format,
and Figure 2 shows the single-node version.

Memo
Often, you wish to view the XML source for a DOM. A simple
building block that writes to a TMemo field provides the answer.
19 November 2002 Delphi Informant Magazine

The TWebBrowser component in Delphi is a wrapper around Microsofts Internet Explorer control. As such, it knows about HTML and
XML documents and can display either. You can incorporate this
ability easily by creating another building block that transfers the
contents of a DOM to the browser.
Extending TXMLBuildingBlock is the TXBBWebBrowser component
(see Figure 5). It exposes the Consumer property, adds a WebBrowser
property to link to a browser, and adds a TempFileName property to
use when loading the browser. Because the Web browser cannot be
filled directly, its necessary to save the DOM contents to a file first
and then read that in. This process has the added benefit of identifying the type of document to load HTML or XML based on
the files extension.
As before, you override the ProcessDocument method to accept the
DOM, write it out to the specified temporary file, and then read it
back in to the Web browser. Recall that you also should override the
Notification method to tidy up, if the attached Web browser component is deleted from the form.
Figure 6 shows the results of using this component. As you can see,
the parser loads in a document (movie-watcher.xml), applies an XSL
transformation to it (movies-html.xsl), and directs the results to
the Web browser. Because this particular transformation generates
HTML (although its formatted as an XML document), the temporary file name for the TXBBWebBrowser component is set to
c:\temp\xbbtemp.html. When loaded into the browser, the html
extension determines how the document is handled.

Demonstration
The demonstration program that accompanies this article lets you
experiment with the components Ive presented in this three-part series
(see the end of the article for details on downloading files). Each component is associated with a button or label on the left side of the form,
with the lines indicating possible connections between them. Select the
check box next to each one to include it in a run (the merge and fork
components automatically are used when necessary).
Components at the top of the panel represent the producers, creating
DOMs from existing documents, SQL databases, text files, or even
Delphi components. Those components in the middle alter the DOM
as it passes through, and those at the bottom are the final consumers of

Delphi at Work
ing can be added as needed just by implementing the interfaces.
To start things off, several DOM producers
were developed. These components created
DOMs from existing XML documents (either
in a file, in a stream, or on the Web), from a
query submitted to a SQL database, or from
wrapping the contents of a text file. Other
components modified these DOMs by merging several documents into one, by adding
a time-stamp element to the DOM, or by
applying an XSL transformation to it.

Figure 6: XML viewed in a Web browser.

the documents. The StringGrid is filled automatically when you select


a node from the TreeView. Click the GO button to start a run after
selecting which components to use and setting their properties.
Several of the components have properties that can be modified at run
time; they appear as buttons on the form. Pressing one of these brings up
a dialog box to allow you to alter the components settings. Try out different combinations to see how the various components can be linked.

DOM consumers accept a DOM from a


producer and usually present it to the user
in some manner. The consumers developed
as part of the building blocks suite let you
convert the DOM back into text format and write that to a file or a
stream, or display the contents of the DOM in a GUI control, such
as a TreeView, StringGrid, Memo, or Web browser. These plugand-play components let you deal with XML documents easily in
a more Delphi-centric manner, by setting properties at design time
and with minimal code.
The files referenced in this article are available on the Delphi Informant
Magazine Complete Works CD located in INFORM\2002\NOV\
DI200211KW.

Conclusion
The XML building-block approach provides a suite of components
that work with XML documents expressed as DOMs. They can be
linked together easily to create chains of processing for XML. By creating simple, generic components, they can be combined in numerous
ways to generate the handling your application requires. New process-

Begin Listing One DOM in a TreeView


{ Display a DOM within a TreeView. The nodes as selected
by the ShowNodes property are inserted into the attached
TreeView (TreeView property). Each node is assigned an
image index corresponding to its node type. Assign an
appropriate image list to TreeView to display these. You
can use the DefaultNodeImages variable from this unit. }
TXBBTreeView = class(TXMLBuildingBlock)
private
FShowNodes: TXBBNodeTypes;
FTreeView: TTreeView;
procedure SetTreeView(Value: TTreeView);
protected
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
function ProcessDocument(const Document: IDOMDocument):
IDOMDocument; override;
public
constructor Create(AOwner: TComponent);
overload; override;
constructor Create(AOwner: TComponent;
const TreeView: TTreeView); reintroduce; overload;
destructor Destroy; override;
procedure ClearTree;
published
property Consumer;
property ShowNodes: TXBBNodeTypes
read FShowNodes write FShowNodes
default [ntElement..ntNotation];
property TreeView: TTreeView
read FTreeView write SetTreeView;
end;

20 November 2002 Delphi Informant Magazine

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

{ Fill the TreeView with nodes corresponding to the


document's nodes. }
function TXBBTreeView.ProcessDocument(
const Document: IDOMDocument): IDOMDocument;
{ Recursively add nodes to tree while stepping through
the DOM structure. }
procedure PopulateTree(Node: IDOMNode;
Parent: TTreeNode);
var
DisplayName: string;
Index: Integer;
NewNode: TTreeNode;
begin
if not TXBBNodeType(Node.NodeType-1) in ShowNodes then
Exit;
case Node.NodeType of
DOCUMENT_NODE:
DisplayName := 'Document';
TEXT_NODE, CDATA_SECTION_NODE, COMMENT_NODE:
DisplayName := Node.NodeValue;
else
DisplayName := Node.NodeName;
end;
NewNode := TreeView.Items.AddChildObject(Parent,
DisplayName, TXBBNodePointer.Create(Node));
{ Select images based on node type. }
NewNode.ImageIndex := Node.NodeType - 1;
NewNode.SelectedIndex := NewNode.ImageIndex;
if Assigned(Node.Attributes) then
for Index := 0 to Node.Attributes.Length - 1 do
PopulateTree(Node.Attributes.Item[Index], NewNode);

Delphi at Work
for Index := 0 to Node.ChildNodes.Length - 1 do
PopulateTree(Node.ChildNodes.Item[Index], NewNode);
end;
begin
if not Assigned(TreeView) then
Exit;
TreeView.Items.BeginUpdate;
ClearTree;
PopulateTree(Document, nil);
TreeView.Items.EndUpdate;
Result := Document;
end;

End Listing One


Begin Listing Two DOM in a StringGrid
{ Display a DOM within a StringGrid. The values from the
nodes immediately beneath the document element in the
document provided are displayed in the attached string
grid (StringGrid property). If SingleNode is True (the
default), the nodes are shown in two columns. The first
is the node's name, the second is the node's value. If
SingleNode is False, the nodes are assumed to be multiple
copies of the one type and so are shown in a
two-dimensional format. Each row is a new child element
of the document element, with each column being one of
its children and value. }
TXBBStringGrid = class(TXMLBuildingBlock)
private
FSingleNode: Boolean;
FStringGrid: TStringGrid;
protected
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
function ProcessDocument(const Document: IDOMDocument):
IDOMDocument; override;
public
constructor Create(AOwner: TComponent);
overload; override;
constructor Create(AOwner: TComponent;
const StringGrid: TStringGrid); reintroduce; overload;
published
property Consumer;
property SingleNode: Boolean
read FSingleNode write FSingleNode default True;
property StringGrid: TStringGrid
read FStringGrid write FStringGrid;
end;
{ Fill a StringGrid with values from the DOM. }
function TXBBStringGrid.ProcessDocument(
const Document: IDOMDocument): IDOMDocument;
{ Compile the text content of a node - down to one
level below. }
function GetText(Node: IDOMNode): string;
var
Index: Integer;
begin
Result := Node.NodeValue;
for Index := 0 to Node.ChildNodes.Length - 1 do
Result := Result +
Node.ChildNodes.Item[Index].NodeValue + ' ';
end;
{ Present the DOM, i.e. the nodes beneath the document
element, in two columns: one for the node name, one
for its value. }
procedure DoSingleNode;
var
Element: IDOMElement;
Node: IDOMNode;
Index: Integer;

21 November 2002 Delphi Informant Magazine

begin
with StringGrid do begin
Element
:= Document.DocumentElement;
ColCount
:= 2;
RowCount
:= Element.ChildNodes.Length + 1;
FixedCols
:= 0;
FixedRows
:= 1;
Cells[0, 0] := 'Element';
Cells[1, 0] := 'Value';
for Index := 0 to Element.ChildNodes.Length - 1 do
begin
Node := Element.ChildNodes.Item[Index];
Cells[0, Index + 1] := Node.NodeName;
Cells[1, Index + 1] := GetText(Node);
end;
end;
end;
{ Present the DOM, i.e. the nodes beneath the document element,
in multiple columns. This assumes that the document element
contains multiple instances of the same type of element.
Each row is then one of these elements, and each column is
one of its sub-elements. }
procedure DoMultipleNodes;
var
Element: IDOMElement;
Node, Node2: IDOMNode;
Index, Index2, Column: Integer;
Text: string;
begin
with StringGrid do
begin
Element
:= Document.DocumentElement;
ColCount
:= 2;
RowCount
:= Max(2, Element.ChildNodes.Length+1);
FixedCols
:= 1;
FixedRows
:= 1;
Cells[0, 0] := '#';
Cells[1, 0] := '#text';
Cells[0, 1] := '';
Cells[1, 1] := '';
ColWidths[0] := 20;
ColWidths[1] := 20;
for Index := 0 to Element.ChildNodes.Length - 1 do
begin
Node := Element.ChildNodes.Item[Index];
Rows[Index + 1].Text := '';
Cells[0, Index + 1] := IntToStr(Index + 1);
for Index2 := 0 to Node.ChildNodes.Length - 1 do
begin
Node2 := Node.ChildNodes.Item[Index2];
Text := GetText(Node2);
for Column := 1 to ColCount - 1 do
if Node2.NodeName = Cells[Column, 0] then
Break;
ColCount
:= Max(ColCount, Column + 1);
Cells[Column, 0]
:= Node2.NodeName;
Cells[Column, Index + 1] := Text;
ColWidths[Column]
:=
Max(20, Min(ColWidths[Column], Canvas.TextWidth(Text)));
end;
end;
end;
end;
begin
if not Assigned(StringGrid) then
Exit;
if SingleNode then
DoSingleNode
else
DoMultipleNodes;
Result := Document;
end;

End Listing Two

OP Tech
Run-time Packages / Delphi 5, 6

By Rick Spence

Run-time Packages
Adding Functionality and Flexibility to Your Apps

his article shows you how you can divide your application into packages, which you
can then deploy separately from the core application. The core application can offer
more or less functionality according to the presence or absence of the packages.
This is ideal in situations where certain parts of an
application are only available to users purchasing
them separately, or after registering. An accounting
package, for example, may have an optional payroll
module which is only available for an additional
fee. You can deploy the payroll functions in a separate package, and have the main application detect
its presence or absence and react accordingly. The
main application can function with or without the
package, and doesnt need to change.
We start with an overview of the two types of
packages: design-time and run-time. Most of you

will have installed design-time packages into the


IDE; design-time packages are the deployment
vehicle for third-party components. Few of you
will have worked with run-time packages, however,
so an overview is in order.
Then well look at how to call packages from a main
application, both directly and indirectly. As youll
see, its the indirect calling that makes this dynamic
functionality possible. Finally, well look at how to
detect whether a run-time package is present, and
how to offer more or less functionality in the main
application according to the packages presence.

Run-time Packages: An Overview


Run-time packages are much like a dynamic-link
library; a collection of code and data that forms
an integral part of an application, but is not
linked into the applications executable file. One
advantage of using run-time packages is that
you can update the package without updating
the executable file. You can provide users with
small patches without sending them an entire
application. The executable file is of course
smaller, because some of the code is stored in the
external package.

Figure 1: The Packages tab of the Delphi 5 Project Options


dialog box.
22 November 2002 Delphi Informant Magazine

Another advantage of run-time packages is you


can share them between multiple applications,
saving disk space. As an example of how you
can use this, consider what happens when you
deploy multiple executable files without packages. Each executable file contains the VCL.
If you could find a way to place the VCL in a
run-time package, the executable files would
be much smaller, because they all share the one
copy of the VCL run-time package.

OP Tech
Recognizing this, Borland ships Delphi with a number of run-time
packages already built for the VCL, and that allow you to build
your application using them. To do this, open the Project Options
dialog box and select the Packages tab (see Figure 1). You will see
a check box labeled Build with runtime packages. If you check this,
you can then select which packages you want to use. If youre using
Delphi 5, youll see packages with names such as Vcl50, Vclx50,
VclSmp50, Vcldb50, etc.

DPK

Package source code.

DCP

A binary image containing a package header and the


concatenation of all DCU files in the package. A single
DCP file is created for each package. The base name
for the DCP is the base name of the DPK source file.
This is required to link with run-time packages.

DCU

A binary image for a unit file contained in a package.


One DCU is created, when necessary, for each unit file.

With Delphi 6 the packages have been renamed, and youll see names
such as VCL, RTL, VCLDB, etc. In both versions of Delphi, the file
extensions of these files when using run-time packages is .DCP, and
youll find them located in your \Delphi\Lib directory. These are the
run-time packages supplied by Borland, each of which contains a
portion of the VCL. You can opt to link with all these run-time packages, or just choose the ones you want.

BPL

The run-time package. This file is a Windows DLL with


special Delphi-specific features. The base name for the
BPL is the base name of the DPK source file. This is the
file you need to ship along with your application.

Figure 2: File extensions used by packages.

If you build a simple application using run-time packages for the


VCL, your executable file size will be about 14K, compared to about
287K if you hadnt used run-time packages. Of course, you will
still need to ship the run-time package with the application. And
in Delphi 5, this file is about 2MB, so what have you gained? Well,
now you can change the (small) executable and deploy that, without
redeploying the (large) package.
You can, of course, create your own run-time packages, and well get
to that in a moment. First, I want to review the file extensions used
when working with packages (see Figure 2). The files with extensions
of .BPL are the binary images of the run-time packages. These are
the dynamic link libraries the files you need to ship along with
your executable. The DCP file is what you need in order to link with
run-time packages. Essentially its a combination of all the DCU files
of the compiled package source files.

Figure 3: The Delphi 5 Package editor.

A run-time package is essentially a collection of compiled Pascal


source files. Creating your own package is as simple as creating the
package itself, then adding Pascal source files to it. To create the package, click File | New and select Package from the Object Repository.
This will launch the Package editor (see Figure 3), from which you
can add units to, and remove units from, the package.

If your package contains units that require the VCL, the Package
editor will automatically include the required VCL run-time packages. In this case, you should also build your main application using
the VCL run-time packages; otherwise, youll be distributing the
VCL twice once as part of the executable, and once as a run-time
package required by your own run-time package. If youre creating a
package that contains only functions and does not require the VCL,
remove vcl50.dcp from the Requires list.

When you save the package (using File | Save As), Delphi will create
a file with a DPK extension the package source code. When you
compile the package, it will create the DCP, DCU, and BPL files.

In Delphi 6, the requires clause lists Rtl.dcp, the run-time library


code. If you are using the VCL (as opposed to CLX) you will
probably want to add VCL here as well.

Click on the Options button to display the Project Options dialog


box, which lets you specify directories for these files. You will
probably want to change the output directory; otherwise, the BPL
file will by default be placed in the \Delphin\Projects\BPL directory (and DCP directory). BPL (output) should be \WINNT\
system32, or wherever your other Borland-supplied BPL files are.

Using Your Own Run-time Packages

Creating Your Own Packages

The Package editor allows you to maintain two lists:


A list of Pascal units you want the package to contain. This is
named the Contains list and is specified with the contains clause.
A list of other packages required by this package. This is named
the Requires list and is specified with the requires clause.
In Delphi 5, the Requires list initially contains vcl50.dcp, the main
VCL run-time package. This is because you need the run-time VCL
package if anything in any of your Pascal units uses anything from
the VCL (which any but simple function-only packages will).
23 November 2002 Delphi Informant Magazine

There are two ways to link your main application to a run-time


package: statically and dynamically. With static linking, you add
the names of the Pascal files you require to your main applications uses clause; the fact that these Pascal files are contained in
a package is irrelevant at this stage. This is how you use the VCL
run-time packages, as well. You just list the names of the VCL
Pascal files you require to your main applications uses clause,
then add the corresponding DCP files to your main applications
list of required packages.
When using static linking with packages and when you run your
main application, its run-time code ensures that the run-time
package is present. If not, it generates an error, and the main program will not run. Of course, this defeats our goal of extending
the main application with optional packages. You could argue that
to overcome this you could always have a dummy package that

OP Tech
implements some sort of interface between the main application
and the package, but does nothing. However, this will not work
either. Another problem with static linking is that if you change
the contents of the package, or replace it with another package
entirely, you must relink the main application. Static linking,
then, wont work for us, so lets look at dynamic linking.
Dynamic linking of packages is pretty much identical to dynamic
linking of DLLs packages are a special type of DLL after all.
If youve worked with DLLs, then youll be right at home with
dynamic packages. Lets start by writing a simple main application
that will call a routine from a dynamically-linked package.
Well assume our package contains a procedure named PackageRoutine
that takes no parameters. If you just make a call to PackageRoutine,
the linker will fail with Undeclared identifier: PackageRoutine
because we have not declared it anywhere. If we had the routine already
written, and part of a package, we could add the Pascal file to the uses
clause and add the package to the list of required run-time packages,
but then that would be linked statically. We want the package to be
linked to the main application when the main application is loaded.
To do this, we must tell the compiler that PackageRoutine is external to
our application, and tell it where it can find it. You use the same syntax
as when telling the compiler that PackageRoutine is part of a DLL:
procedure PackageRoutine; external 'PackageTest.bpl';

Here were telling the compiler that PackageRoutine is external to our


application in a file named PackageTest.bpl. Note that the name of the
package file is in quotes, and that we include the file extension. For a
standard DLL, of course, the extension will be .DLL.
In this case, you must still have a package named PackageTest.bpl,
but the linking is done when the application loads, not when
the application is compiled. If the package isnt present, or if the
package is present but does not declare the PackageTest function,
the applications loader will fail. So have we gained anything?
Absolutely. You can now have different versions of the package,
and replace them at will without changing the main application.
As long as the package defines the PackageRoutine function, the
main application will load and can call PackageRoutine. You can
have a dummy version of the package, where PackageRoutine can
display a message indicating that feature is not yet available, and a
real version that performs the real work.
unit PackageTestSourceUnit;
interface
implementation
uses Windows;
procedure PackageRoutine;
begin
MessageBox(0, 'PackageTest', 'In PackageRoutine',
MB_OK + MB_ICONINFORMATION);
end;
exports PackageRoutine;
end.

Figure 4: Pascal file contained in the run-time package.


24 November 2002 Delphi Informant Magazine

In this case we have a simple interface from the main application to


the package a single procedure. In practice, of course, you would
have a more complex interface, but as long as the package implements
all the required routines, this scheme will work. Its also possible to
write the main program so that the package may not even exist. This
requires a little extra work because the main program cannot reference
the function by name; it must do it indirectly by obtaining a pointer
to it. Well discuss that in a moment; first, lets look at how to write
the package with the exported function.
You create the run-time package as normal by going through the
repository. Add a unit to the package, and write the PackageRoutine
procedure as a normal Pascal procedure:
uses Windows;

// For MessageBox prototype.

procedure PackageRoutine;
begin
MessageBox(0, 'PackageTest', 'In PackageRoutine',
MB_OK + MB_ICONINFORMATION);
end;

Note that we used the MessageBox API call rather than a VCL function.
This relieves us from having to use VCL run-time packages (although in
practice you will probably need to).
You must now tell the compiler that this function should be exported;
that is, it should be visible to other programs that want to call it:
exports PackageRoutine;

Place this exports statement inside the implementation section, after the
last procedure, but before the final end keyword. The entire Pascal file is
shown in Figure 4.
As you saw, when you call a routine from a package, the caller must
indicate that the called routine is external. You saw how to do this in the
previous section. You list the name of the routine and the package in
which it is declared, as in:
procedure PackageRoutine; external 'PackageTest.bpl';

What youre actually doing here is telling the compiler that this routine
does indeed exist, and in which run-time package it is contained. In
this case, we have a very simple routine. Its a procedure, rather than a
function, so doesnt return a value. Furthermore, the procedure doesnt
accept any parameters. If the routine youre calling is a function, you
must tell the compiler, as part of this external declaration, what the
functions return type is. And if the routine accepts parameters, you
must also specify those types as part of the external declaration. This is
so the compiler can generate correct code. Note that the compiler has
no way of verifying that the routine actually accepts the parameters you
tell it; be careful with these declarations, theyre often a source of error.
For examples of writing these external declarations look at the VCLs
Windows.Pas unit file. It contains declarations for routines declared in
the Windows API.
In summary, then, to dynamically link to routines declared in a package,
take the following steps:
1) Create a package, and set its output directory to the same place as
the other BPL files on your computer (usually \WINNT\system32
for Windows 2000).
2) Add Pascal files to this package, and declare any routines you want
to be called as external.

OP Tech
3) In the Pascal file that calls the routine from the package, declare the
called routine as external. Be sure to specify the name of the BPL file
in quotes, and include the .BPL extension. You must also specify any
parameters, and if the routine you are calling is a function its
return type.
4) Add the directory containing the DCP file (the compiled version of
the package) to the calling projects directory.

Really Dynamic Packages


In the previous section you saw how to link a main application to
a subroutine declared in a package. The package must be present,
however, although its contents can change without re-linking the
main program. This is a major advantage over static linking, because
it means you can have different versions of the package present at run
time, without changing the main application. It also mean, however,
that you have to have a version of the package which implements all
the required routines, even if all those routines do is display a message indicating theyre not implemented.
To write the main program so that it functions whether the package is
present or not, and to relieve you from having to write a dummy version
when a user isnt authorized to use the package, you must learn how to
dynamically load packages, and how to call the routines indirectly.
The basic problem is that the calling application cannot directly
reference either the function or the package; if it did, then the calling
application would not even load if the package were not present, or did
not implement all the required routines.
Note that all the changes you need to make to dynamically load packages
and call their functions indirectly are in the Pascal files which use the
packages. You dont need to change any of the source code files that
comprise the package. Also note that although I am discussing packages
here, as opposed to DLLs, you can dynamically load DLLs and indirectly
call their routines in exactly the same way.
Well start by looking at how to dynamically load the package we just
wrote, PackageTest.Bpl, and how to indirectly call its one exported
function, PackageRoutine. Well then build on this example to address the
complications introduced by parameters and return values.
First lets look at how to check whether the package is present. You use
the VCL LoadPackage function to attempt to load a package. If it fails,
Delphi raises an exception. So, to test whether a package is present, turn
off the Stop on Delphi exceptions debugger option (when running inside
the IDE), and use the following code:

try
packModule := LoadPackage('PackageTest.bpl');
try
ShowMessage('Loaded OK');
finally
UnloadPackage(packModule);
end;
except
ShowMessage('Not found');
end;

Figure 5: Dynamically loading and unloading a package.

To unload the package, call UnloadPackage, passing it the package


handle. Figure 5 shows how to do this. Call UnloadPackage inside a
try..finally construct to make certain its unloaded.
Youll probably want to check for the packages existence when the main
application loads, so you can modify menu items accordingly. The
PackageExists routine, shown in Figure 6, takes what weve learned so
far and wraps it into one convenient function. Pass it the name of the
package in which you are interested, and it returns True if the package
exists, and False if it doesnt.
Now we know how to check whether the package exists, we need to
look at how to indirectly call a routine from it. Since the VCL offers
no support here, we must resort to the Windows API. GetProcAddress
is our function; we pass it the handle of the dynamically-loaded
package, and the name of the routine we want to call. Note how by
passing the name of the routine as a string, we arent referencing it
directly; therefore its not statically linked. GetProcAddress returns a
pointer to the routine, as in:
var
packModule : HModule;
procAddr
: Pointer;
begin
packModule := LoadPackage('PackageTest.Bpl');
procAddr := GetProcAddress(packModule, 'PackageRoutine');
if assigned(procAddr) then
// We found it...

To proceed to call the function, you must tell the compiler that procAddr
is actually a pointer to a procedure (a procedural type) using a typecast.
In this simple case we can define a new type, ProcPtr, as a pointer to a
procedure which does not accept any parameters:
type
ProcPtr = Procedure;

try
LoadPackage('PackageTest.bpl');
except
ShowMessage('Not found');
end;

Remember to remove the external declaration if youre using the same


project from the previous section. Otherwise, Delphi will attempt to load
the package statically, regardless.
If the package does load, youre responsible for unloading it. LoadPackage
is a function which returns a handle to the package it loaded. We didnt
use it in the previous example, but to unload the package, or in fact
to do anything with the dynamically-loaded package, you must
retain the value. Its type is HModule, a user-defined type defined in
Windows.pas (if you dig deep enough youll find it eventually resolves to
the native Pascal type, Longword, a 32-bit unsigned integer).
25 November 2002 Delphi Informant Magazine

Then call the function using:


ProcPtr(procAddr);

If the routine youre accepting receives parameters, you must declare


a procedural type with the correct number and type of parameters, then typecast to that. As an example, heres a procedural
type named ProcOneStringParam, which, as the name implies, is a
pointer to a procedure that accepts one parameter of type string:
type
ProcOneStringParam = procedure(s: string);

To call this routine, you typecast the return result of getProcAddress to this
type, then pass the string:

OP Tech
Summary
function PackageExists(packageName: string): Boolean;
var
packModule : HModule;
begin
try
packModule := LoadPackage(packageName);
try
result := True;
finally
UnloadPackage(packModule);
end;
except
result := False;
end;
end;

Figure 6: Checking whether a package exists.


S := openDialog1.FileName; // s is a string.
ProcOneStringParam(procAddr)(s);

As you can see, to work with GetProcAddress you need a good


understanding of procedural types. I refer you to my article,
Procedural Types, in the August 2001 issue of Delphi Informant
for more details than youd ever require.

26 November 2002 Delphi Informant Magazine

In this article I showed you how to keep optional parts of an application


separate from the main application using packages. We used Delphispecific run-time packages, but the same technique works with standard
Windows DLLs. You must define an interface between the main application and the external package, usually as a number of functions.
I demonstrated two techniques for the main application to call the
routines in the package. The first uses dynamic linking, where the main
application defines the routines as external, and the loader resolves the
references. This requires the package to be present when the application
is loaded. The second uses what I called really dynamic linking, where
the main application explicitly loads the packages and calls the routines
indirectly using the GetProcAddress API function. This technique doesnt
require the package to be present. With either technique you can replace
the packages without changing the main application.
The projects and package referenced in this article are available on
the Delphi Informant Magazine Complete Works CD located in
INFORM\2002\NOV\DI200211RS.

Rick Spence (RSpence@WebTechCorp.com) is the technical director of Web Tech


Training and Development (http://www.webtechcorp.com). The company has
offices in Florida and England, and specializes in Delphi training and development.

Sound+Vision
DirectShow / DirectX Media Objects / Delphi 5, 6

By Ianier Munoz

DirectX Audio Plug-ins


Emulating DirectShow Filters with DirectX Media Objects

ll major companies writing audio-processing software have adopted the DirectX


technology as a foundation for developing audio plug-ins for their products. A
DirectX plug-in is basically a DirectShow filter, which is a component of the DirectShow architecture designed to work with any streaming data (not just with audio).
Typically, filters intended to be used as DirectX
plug-ins have only one audio-input pin, and one
audio-output pin. In addition, their property pages
are usually implemented to react in real time without waiting for the user to apply changes explicitly.
Although filters are essentially COM objects,
coding them is difficult because the interfaces are
fairly complex to implement, and the developer has
to deal with many threading issues. Implementing such objects in Delphi is complicated further
by the fact that the filter base classes provided
with the DirectShow SDK make frequent use of
multiple inheritance and other features not present
in Object Pascal.
Introduced with DirectX 8.0, DirectX Media
Objects (DMOs) are data-streaming compovar
Wrapper : IDMOWrapperFilter;
Filter : IBaseFilter;
...
// Create wrapper.
Wrapper := ComObj.CreateCOMObject(
CLSID_DMOWrapperFilter) as IDMOWrapperFilter;
// Initialize wrapper with your DMO class and category.
OleCheck(Wrapper.Init(DMO_Class, DMO_Category));
// Now we have a filter...
Filter := Wrapper as IBaseFilter;
// ...which can be inserted into a filter graph.
OleCheck(fFilterGraph.AddFilter(
Filter, 'My Wrapped DMO'));

Figure 1: Inserting a DMO into a filter graph.


27 November 2002 Delphi Informant Magazine

nents similar to DirectShow filters, but based


on a simplified object architecture. That makes
DMOs an effective alternative to DirectShow
filters for some applications.
Despite their apparent simplicity, DMOs are
flexible enough to allow you to implement
everything a multimedia stream processor may
need. In fact, most of the DMO limitations
dont come from the simplified architecture
itself, but from the DMO Wrapper Filter,
a piece of code that enables DirectShow
applications to use DMOs within a filter graph,
just as if they were DirectShow filters. An
example of such a limitation is that the Wrapper
Filter never processes data in place, even though
DMOs can support this feature.
The process of inserting a DMO into a filter graph
isnt automatic. The programmer is responsible
for creating the Wrapper Filter and initializing
it with the CLSID of the desired DMO, as
shown in Figure 1. On the other hand, DMOs
are enumerated in a different way than filters,
so applications that enumerate DirectX plug-ins
cannot see DMOs.
In this article, well focus on solving these
problems. Well create a custom COM class
factory that registers its objects as DirectShow
filters, and creates initialized DMO wrappers
that are ready to be consumed by applications
designed to host filters.

Sound+Vision
Prerequisites
To understand this article, you should
be familiar with DirectX concepts, such
as filters and filter graphs. I recommend
reading Jon Webbs series of articles named
Delphi Does DirectShow in the January
and March 2002 issues of Delphi Informant.
You also should be familiar with COM
programming concepts, such as aggregation.
Aggregation is an object-reuse mechanism
in which an outer object exposes
interfaces from inner objects as if they
were implemented on the outer object
itself. The outer and inner objects are said
to form an aggregate. You may want to
search for the topic COM aggregation
in the MSDN Library for more details.

The sample plug-in Ive provided with this


Figure 2: Typical DMO implementation: Delphi vs. C++.
article requires the DirectX 8 binaries to
run. To compile the plug-in, you will need
the JEDI DirectX headers, which are available from Project JEDI.
setting the number of input and output streams to 1, keeping a list
of supported audio formats, and forcing the input and output to
For debugging and testing, you may use either the GraphEdit
use the same media type.
tool provided with the DirectX SDK, or any other application
supporting DirectX plug-ins, such as Adapt-X for Winamp.
In addition, TAudioDMO reduces all buffer processing to a single
For information about specifying the host executable for the
virtual method named InternalProcess. This method takes the
debugging session, see the topic Debugging dynamic link
source and destination pointers as input parameters along with
libraries in Delphis help. Youll find URLs for these tools at the
the size of the allocated memory for each pointer. Note that sizes
end of this article. Youll also find details about how to download
are passed by reference, to allow the programmer to adjust them
the code used for this article.
when coding effects that alter the length of the sound.

Setting up a Framework

Implementing Your DMO: A Volume Control

The TDMOImpl Delphi base class is a translation of the


IMediaObjectImpl C++ template class from the DirectX SDK.
TDMOImpl takes care of validating input parameters as well as
some other bookkeeping tasks. See the topic Using the DMO
Base Class in the DirectX SDK documentation for more details.

Because of its simplicity, we will implement a volume control.


The first thing to do is to create an interface for our effect so we
can access its parameters programmatically. Heres the code that
shows the IVolumeDMO interface. In this case, its rather simple
because it exposes only one parameter:

The original class was implemented using C++ generic


programming. In other words, its a template, so developers are
free to choose any COM framework for implementing the basic
IUnknown functionality through multiple inheritance.

type
IVolumeDMO =
interface['{748A4D54-FE90-4459-973D-92E175DBAFCF}']
function GetVolume: Single; stdcall;
procedure SetVolume(aVolume: Single); stdcall;
property Volume: Single read GetVolume write SetVolume;
end;

Aside from the fact that Delphi doesnt support generic


programming, the translation is almost identical to the original
class. Note that TDMOImpl inherits from TAggregatedObject.
Here, a COM aggregate has replaced the multiple inheritance
to achieve an equivalent degree of flexibility, with the difference
being that now you have to write two separate classes: one inner
class derived from TDMOImpl to implement your own processing
algorithms, and one outer class to expose the IMediaObject
interface through aggregation (see Figure 2).
The main advantage of using this approach is that you still can
derive your actual DMO from any base class, such as TComObject
or TInterfacedObject. The drawback is that you must write two
classes per DMO, although the outer class is easy to code using the
implements keyword in Delphi.
TAudioDMO is an abstract class that specializes TDMOImpl to
provide some functionality common to audio effects, such as
28 November 2002 Delphi Informant Magazine

Note that we chose stdcall as the calling convention to allow the


plug-in to interoperate with applications written in languages other
than Delphi. The next step is to implement the effect itself. For this,
we create a TVolumeDMO class that inherits from TAudioDMO. This
class implements the effect interface by storing the volume parameter
in a member variable. We protect this variable from concurrent access
by calling the Lock method provided by the base class.
We add a constructor to the class to initialize the volume, and to
declare the audio formats the effect supports.
To complete the effect, we need to implement our processing
algorithm by overriding InternalProcess. We apply the volume to
the audio data by multiplying each sample by the stored volume
value. We dont need to lock, because the base class already locked
the DMO before calling us.

Sound+Vision
A more complex implementation would interpolate the volume
for each sample to avoid the audible clicks caused by sudden
volume changes between consecutive blocks of audio data. This
phenomenon is known as zipper noise.

info from the DMO, and pass it to IFilterMapper2.RegisterFilter. This


is somewhat different from the filter-registration samples provided with
the DirectX SDK, in which the registry information is described using a
static structure.

At this point, were done with our volume effect. We also would
need to override InternalFlush, InternalAllocateStreamingResources,
and InternalFreeStreamingResources, if we were coding effects
involving any kind of buffering (but thats not the case here).

Registering our COM object as a filter isnt enough because


applications that create such objects would expect them to
implement the filter interfaces. Thats why we also overrode the
CreateInstance method of the IClassFactory interface to do the
actual work; that is, return a DMO Wrapper Filter initialized
with the CLSID of our DMO. Except for the last step, the code is
exactly the same as that shown in Figure 1.

Now, we need to wrap our effect into a COM object in order


to expose it to interested applications. Create a COM object
using the wizard (select File | New | Other | ActiveX | COM Object).
Uncheck the Include Type Library and Mark interface Oleautomation
options, and set Threading Model to Both.
Add IMediaObject and IVolumeDMO to the list of implemented
interfaces. Declare a field of type TVolumeDMO and a
corresponding read-only property to expose the interfaces through
aggregation by using the implements keyword. Then, override the
Initialize method, and the destructor to manage the lifetime of
the aggregated object.
You should not use the constructor for initialization, because
the class factory creates objects from a class reference. Therefore,
your code would never be executed because the constructor of
TComObject is not virtual.
Theres one extra detail you should care about. The DMO Wrapper
Filter also uses the COM aggregation mechanism to expose the
interfaces of your DMO, so, in fact, the filter aggregates something
that is already an aggregate. This is known in COM jargon as nested
aggregation. In such cases, according to COM rules, the object
in the middle (that is, the instance of your class) should pass inner
objects the controlling IUnknown interface it receives from its outer
object, instead of its own IUnknown implementation. Figure 3 shows
how this is handled during initialization.

Registering your DMO


Now, we have a full-blown object we could register using
the DMORegister function to make it visible to multimedia
applications capable of hosting DMOs. Unfortunately, there are
almost no such applications out there. Thats why we need to
find a trick to register our DMO in such a way so that existing
applications designed to host DirectShow filters can enumerate it.
TDMOFilterFactory does the necessary magic by extending the
UpdateRegistry method of TComObjectFactory to extract all the necessary
procedure TVolumeDMOImpl.Initialize;
begin
inherited;
if Controller <> nil then
begin
fDispatchObj := TDummyDispatch.Create(Controller);
fVolumeDMO := TVolumeDMO.Create(Controller);
end
else
begin
fDispatchObj := TDummyDispatch.Create(Self);
fVolumeDMO := TVolumeDMO.Create(Self);
end;
end;

Figure 3: Handling nested aggregation.


29 November 2002 Delphi Informant Magazine

In practice, all we need to do is generate a new CLSID for our


fake filter, and add an instance of TDMOFilterFactory for it
during initialization. We also have to provide a call-back function
to the class factory to capture the necessary parameters for
registration (if you prefer, you could add all these parameters to
the constructor).
Optionally, you also can register the DMO in the standard way,
so applications supporting DMOs enumerate it. For this, set the
RegDMO field of the TDMORegParams structure to True and specify a
valid DMO category, such as DMOCATEGORY_AUDIO_EFFECT,
in the DMOCategory field.

The User Interface


The user interface is an important part of your DirectX plug-in,
because it provides users a simple way to manipulate your plug-in
parameters. In this case, we only need a TrackBar control for the
volume. For DirectX plug-ins, the user interface consists of a
standard COM property page, so the first thing were going to do
is create one using the Delphi wizard.
The next step is to implement the standard ISpecifyPropertyPages
interface on our plug-in to return the CLSID of the property page
we just created. At run time, the plug-in host application will
query our plug-in object for this interface, and use the returned
CLSID to create an instance of our property page. Note that
even if the plug-in object is actually an instance of the Wrapper
Filter initialized with our DMO, it exposes our internal interfaces
through aggregation.
Some host applications create the filter object and its
corresponding property page in different threads. This
may prevent the plug-in from loading, because the COM
run time complains about marshaling when passing the
filter object reference to the property page. To address this
problem, we could specify tmBoth as the threading model of
the property page to let the filter object and the property page
communicate using the free-threaded marshaler. However, the
default TActiveXPropertyPageFactory implementation doesnt
allow specifying the threading model as a constructor parameter.
This is why our example implements its own property-page
factory class named TActiveXPropertyPageFactoryTM.
If you look at the sample source code, youll see that our plug-in
also features a minimal implementation of the standard IDispatch
interface. This is required because the OleObject property of
TPropertyPage, which we will use to access our plug-in object
by querying it for the IVolumeDMO interface, is stored in a
variant, and Delphi variants can hold COM objects only if they
implement IDispatch.

Sound+Vision
Also, note that plug-ins typically implement a callback mechanism to notify the property page whenever
parameters change, so the user interface can refresh itself
automatically. The sample plug-in does not implement
such a mechanism. I leave this as an exercise for you.

Conclusion
This article demonstrates how to create DirectShow
filters in Delphi by wrapping DirectX Media Objects
using a custom COM class factory automatically. The
resulting filters can be used by any sound-processing
application capable of hosting DirectX plug-ins.
Figure 4: Our plug-in at run time.

The only thing left is to drop a TrackBar control on the property


page and code its OnChange event to modify the volume
parameter of the plug-in object:
procedure TVolumePropertyPage.VolTrackBarChange(
Sender: TObject);
begin
(IDispatch(OleObject) as IVolumeDMO).Volume :=
VolTrackBar.Position / 100;
end;

Thats all there is to it! Just register the resulting DLL through the
Run | Register ActiveX Server menu command on the Delphi IDE
(or use the regsrv32.exe utility), and youre ready to test the plugin with your favorite host. Figure 4 shows our plug-in in action.

Supporting Presets
DirectX plug-in presets are handled through the standard COM
persistence mechanism. All we have to do is implement the
IPersistStream interface in our object, and the host application
will take care of the rest. The only gotcha here is that the CLSID
returned by IPersistStream.GetClassID should be the generated
CLSID of the fake filter.

30 November 2002 Delphi Informant Magazine

This approach to coding DirectX audio plug-ins has


the advantage of being very simple, compared with
writing transform filters. The main drawbacks are that the audio
data cannot be processed in place, and that the resulting plug-in
requires DirectX 8.0 (or greater) to work.

Resources
DirectX binaries: http://www.microsoft.com/windows/

directx/downloads.
DirectX SDK: http://msdn.microsoft.com/directx.
JEDI DirectX headers: http://www.delphi-jedi.org/
delphigraphics.
Adapt-X, a free DirectX plug-in host for Winamp:
http://perso.worldonline.fr/jobsearch.

The project referenced in this article is available on the Delphi Informant


Magazine Complete Works CD located in INFORM\2002\NOV\
DI200211IM.

Ianier Munoz lives in France and works as a senior consultant and analyst for
Dokumenta, an international consulting firm based in Luxembourg. His specialty is in
multimedia applications, and he has authored some popular software, such as American
DJs Pro Mix, Adapt-X, and Chronotron. Readers may reach him at ianier@hotmail.com.

New & Used


By Clay Shannon

Pascal Analyzer
Evaluates Your Code for Problems, Fat, etc.

here are a number of memory-leak detection and profiling tools for Delphi on the
market. When it comes to code-analyzing tools, however, theres somewhat of a
dearth. Fortunately for Delphi developers, theres PAL, short for Pascal Analyzer, from
Peganza. PAL provides an automated way to review and inspect your code.
Peganza describes its tool in this way: Pascal
Analyzer ... is a unique development tool that
measures, checks, and documents your Delphi or
Borland Pascal source code. It makes software projects of any size easier to understand and enables
developers to produce more reliable code. PAL
contains numerous optimization, validation, and

documentation features, which help in fine-tuning


and managing the development process. ... Reports
generated by Pascal Analyzer contain a wealth of
important information about the source code. This
data will help you better understand your source
code, and assist you in producing code of higher
quality and reliability.
If your code is already perfect, PAL can serve as a
verifier. Otherwise, it will help you by pointing out
things in your code that can be improved. As Steve
McConnell wrote in Code Complete (Microsoft
Press, 1993): Conducting review, inspections,
and tests is a way of compensating for anticipated
human fallibilities. These review techniques originated as part of egoless programming (Weinberg,
1971). If you never made mistakes, you wouldnt
need to review your software. But you know that
your intellectual capacity is limited, so you augment it with someone elses.

Delphi and PAL Pointers

Figure 1: Class Field Access Report.


31 November 2002 Delphi Informant Magazine

You might think of many of PALs reports as a kind


of Delphi hints and warnings on steroids without
the adverse side effects. With that in mind, if you
dont have hints turned on in Delphi, youre denying
yourself helpful information provided by the compiler about possible mistakes in your code. To turn
on hints, go to Project | Options | Compiler | Messages
| Show Hints. More importantly, you should select

New & Used


anomalies, like unused variables, name conflicts,
and incorrect scope.

PALs Reports
Specifically, what does PAL bring to the table? How
can it help you make better programs and fine-tune
your coding skills? There are 32 reports available in
PAL, separated into five categories: General, Reference, Metrics, Classes, and Controls. You can see
them by selecting Options | Properties | Reports. The
Class Field Access Report is shown in Figure 1, and
the Code Reduction Report in Figure 2. For information on the specifics of these and all other reports,
consult Peganzas documentation.
PALs reports can be generated in either text
or HTML format (this review shows them in
HTML). In the text reports, the same font size
is used throughout and it makes the reports hard
to read. In the HTML reports, this problem is
addressed via the use of various font sizes. This
is the only default setting that I recommend
changing at Options | Properties | Format | Report
Format (see Figure 3).

Figure 2: Code Reduction Report.


Show Warnings at the same location because warnings tell you about

probable (as opposed to merely possible) serious problems in your code.


PAL is more picky about potential problems than Delphis hint
generator. This is a good thing, because PALs feedback teaches
you or reminds you of efficient ways of programming. It
might even indicate problems with your program logic. At the
very least, PAL will help you make your code more efficient
and elegant. PAL even reports potential cosmetic problems with
your application, such as controls that arent aligned with one
another, are overlapping, or are of different widths. As Peganza
says: Pascal Analyzer spots many types of programming bugs and

Figure 3: Changing the Report format setting to HTML.


32 November 2002 Delphi Informant Magazine

A drawback of some code-snooping tools is that


you must make changes to your source or the
tool itself makes changes to your source in
order for the tool to do its job. This doesnt
happen with PAL; its completely non-intrusive.
All you need to do is select the project or
individual .pas file you want PAL to analyze, select Analysis | Run (or
the lightning-fast speed button), and in a matter of seconds, your
code is thoroughly analyzed and all the reports are generated. By
default, the reports are stored in a subdirectory with the same name
as the project below PALs directory.

Additional Details
You can imagine all the ways a code analyzer, such as PAL, can
help you with your Delphi and Turbo Pascal programs. You can
set PAL to parse your Pascal 7 programs in addition to all versions
of Delphi through version 6. To do so, select Options | Properties |
Parsers to open the Properties dialog box and make the appropriate

Figure 4: Options in the Parser tab.

New & Used

Pascal Analyzer, PAL for short, provides an automated way to


review and inspect your code. This useful tool includes 32 customizable reports for Delphi programmers interested in improving their
coding skills and the quality of their applications.
Peganza
E-Mail: info@peganza.com
Web Site: http://www.peganza.com
Price: US$89 to US$119, depending on required licenses.

changes (see Figure 4). You might also want to add some paths to
the Exclude identifiers from these folders in reports edit box to reduce
information overload about potential problems in the third-party
component code used by your project. You can do this by copying
and pasting your Delphi projects search path string found at
Project | Options | Directories/Conditionals | Search Path.
By default, PAL is installed to \Program Files\Peganza\Pascal Analyzer.
Support is available via Peganzas Web site, and by e-mail. Peganza
provides a well-written and comprehensive help file in both .hlp and .pdf
formats, as well as a readme.txt for a quick start. I agree with Peganzas
self-assessment: PAL quickly pays for itself through easier maintenance,
fewer errors, improved code quality, and easier migration of projects
between programmers.
PAL is a standalone executable, but it can be added to your Tools menu.
Instructions are provided in the help file. It can be used as shown in this
review that is, as a conventional GUI app or as a command-line
tool. By using PAL from the command line, you can integrate it into
your production and quality assurance process.
An evaluation version of this valuable tool can be downloaded from
Peganzas Web site. The evaluation version contains all of the functionality of the registered version, but the reports are truncated (only the first
few instances of each potential problem are shown). Although the evaluation version is limited in the length of its reports, youll get the idea of
what the product does. You can also order a full-featured registered version from their site. If you purchase PAL, make sure to always download
the latest patch from http://www.peganza.com/patch. Copy the updated
version of Pal.exe over the existing one.

Conclusion
PAL is a well-thought-out tool that installs smoothly. Its comprehensive,
intuitive, and visually-pleasing to boot. I recommend that all Delphi
programmers interested in improving the quality of their applications
and coding skills use PAL. The benefits are worth the modest investment
of time and money.

Clay Shannon is the author of The Tomes of Delphi: Developers Guide to


Troubleshooting (Wordware, 2001) and the novels Twisted Roads (featuring
a protagonist who is a Delphi developer), The Vavilovian Conspiracy, and
The Resurrection of Samuel Clemens. You can download free e-versions of
these novels from http://codecentral.borland.com/codecentral/ccweb.exe/
listing?id=17956. Clays currently working on his next novel, The Wacky
Misadventures of Warble McGorkle.

33 November 2002 Delphi Informant Magazine

New & Used


By Bill Todd

NitroSort

Data Manipulation Language Lets You Quickly


Select, Sort, and Reformat Flat-file Data

itroSort is an exceptionally fast tool for sorting, reformatting, and selecting


records from flat files. You can use it as a standalone file manipulation utility, or
call the NitroSort DLL from your applications.
And NitroSort goes beyond sorting. You can also
select the fields to include in the output file, overwrite or append to the output file, build a single
output file from many input files, select the records
that are sent to the output file, and control the
number of records processed.

Using the Data Manipulation Language


You cannot use NitroSort until you understand
its data manipulation language, so lets start by
building a sort command describing each option
as its added to the command. Use the InFiles
command to provide a comma-separated list of
input files, for example:

By default the input files are opened for exclusive use. Add the Access command to allow
other users to access the files:
InFiles "C:\IMPORT\20020601.DAT",
"C:\IMPORT\20020602.DAT";
FileType Fixed 100;
Access ShareRead;

In most cases, your sort wont be writing to the


input file, so its safe to allow other users to read
the file while NitroSort runs. Other options
include ShareWrite and ShareReadWrite, but its
unlikely that youll want other users writing to
the file you are processing.

InFiles "C:\IMPORT\20020601.DAT",
"C:\IMPORT\20020602.DAT";

FileType specifies the input file type. The following example identifies the file type as fixed-length
records with a length of 100 bytes:
InFiles "C:\IMPORT\20020601.DAT",
"C:\IMPORT\20020602.DAT";
FileType Fixed 100

NitroSort also supports the Text file type for


records that end with a carriage return/line feed,
and Var for variable-length records. Variablelength records begin with a two-byte integer that
contains the record length.
34 November 2002 Delphi Informant Magazine

The following example uses the OutFile command to identify the output file:
InFiles "C:\IMPORT\20020601.DAT",
"C:\IMPORT\20020602.DAT";
OutFile "C:\OUTPUT\SORTED.DAT";
FileType Fixed 100;
Access ShareRead;

Use the Key command to identify the fields by


which to sort. Each key specification consists
of four values enclosed in brackets in the form
[Starting Position, Length, Data Type, Direction].
The following example sorts the input file in
ascending order by the eight characters starting in

New & Used


character with a value of 9 is the tenth byte in the file. The following
example uses the EBCDIC.SEQ file that comes with NitroSort:
InFiles "C:\IMPORT\20020601.DAT", "C:\IMPORT\20020602.DAT";
OutFile "C:\OUTPUT\SORTED.DAT";
Key [10, 8, char, ascend], [1, 4, int, ascend],
[20, 8, IEEEFloat, descend];
FileType Fixed 100;
Access ShareRead;
Header 80 Retain;
Collate "EBCDIC.SEQ";

To change the layout of the records in the output file, use the OutRec
command. Field specifications take the form [Starting Position,
Size]. You can also include string constants, hex numbers, the record
number in the input file, or the record address (offset) in the input
file in the output record. In this example, the output record contains
the 10 bytes starting in column 20 of the input file, the 19 bytes staring in column 1, the letters XXX, the 20 bytes starting in column 40,
and a byte containing the hexadecimal value FF:
Figure 1: The NitroSort standalone utility.

column 10, ascending by the four-byte integer starting in column


1, and descending by the eight-byte floating point number starting
in column 20. The first key will perform a case-sensitive sort. For a
case-insensitive sort, change the data type from char to alpha:
InFiles "C:\IMPORT\20020601.DAT", "C:\IMPORT\20020602.DAT";
OutFile "C:\OUTPUT\SORTED.DAT";
Key [10, 8, char, ascend], [1, 4, int, ascend],
[20, 8, IEEEFloat, descend];
FileType Fixed 100;
Access ShareRead;

Another common problem in flat files is a header record. A header


record appears once at the beginning of the file and has a different format than the other records in the file. Use the NitroSort
Header command to specify the length of the header, and whether
it should be copied to the output file, or skipped. The following sort command identifies an 80-character header record, and
directs that it be retained in the output file. Replace the keyword
Retain with Skip to omit the header in the output file:
InFiles "C:\IMPORT\20020601.DAT", "C:\IMPORT\20020602.DAT";
OutFile "C:\OUTPUT\SORTED.DAT";
Key [10, 8, char, ascend], [1, 4, int, ascend],
[20, 8, IEEEFloat, descend];
FileType Fixed 100;
Access ShareRead;
Header 80 Retain;

You can create your own collation file to sort


Char fields using something other than the
standard ASCII collation order. A collation
file is 255 bytes with the position of each
byte corresponding to the ordinal value of
the character in the input file, and the value
of each byte set to the value you want used
to sort that character. For example, suppose
you want all tab characters sorted as though
they were spaces. The ordinal value of a tab
is 9, and the ordinal value of a space is 32, so
youd give the tenth byte in the collation file
the value 32. The bytes are numbered starting
with zero, so the byte corresponding to the
35 November 2002 Delphi Informant Magazine

InFiles "C:\IMPORT\20020601.DAT", "C:\IMPORT\20020602.DAT";


OutFile "C:\OUTPUT\SORTED.DAT";
Key [10, 8, char, ascend], [1, 4, int, ascend],
[20, 8, IEEEFloat, descend];
FileType Fixed 100;
Access ShareRead;
Header 80 Retain;
Collate "EBCDIC.SEQ";
OutRec [20, 10], [1, 19], 'XXX', [40, 20], $FF;

One of the reasons that NitroSort is so fast is that it takes maximum


advantage of memory to reduce disk I/O. NitroSort will allocate all of
the available memory on the system. However, this can starve other
processes running at that same time and reduce their performance.
To prevent this, use the MaxMem command to set an upper limit, in
megabytes, on the amount of memory NitroSort will use. The following example limits NitroSort to 100 megabytes of memory:
InFiles "C:\IMPORT\20020601.DAT", "C:\IMPORT\20020602.DAT";
OutFile "C:\OUTPUT\SORTED.DAT";
Key [10, 8, char, ascend], [1, 4, int, ascend],
[20, 8, IEEEFloat, descend];
FileType Fixed 100;
Access ShareRead;
Header 80 Retain;
Collate "EBCDIC.SEQ";
MaxMem 100;

Figure 2: The Batch Processing dialog box.

New & Used

NitroSort is an exceptionally fast tool for sorting, reformatting, and


selecting records from flat files. The documentation is excellent and
includes both a help file and a user manual in Word format. The
options are clearly described and numerous examples demonstrate
how to use each of the options in complete sort commands. A trial
copy is available for download from the Cole Research Web site.
Cole Research
734 Topaz Street
Superior, CO 80027
Phone: (303) 554-7224
E-Mail: info@cole-research.com
Web Site: http://www.cole-research.com
Price: US$89.00, includes royalty-free distribution of run-time libraries.

Figure 3: Building a test file.


procedure TfrmMain.btnSortClick(Sender: TObject);
var
CmdList:
TStringList;
CmdStr:
array [0..2047] of Char;
ErrorStr:
array [0..1023] of Char;
ErrorSize: Cardinal;
SortReturn: Integer;
begin
CmdList := TStringList.Create;
try
// Load the sort command from a file.
CmdList.LoadFromFile(ExtractFilePath(
Application.ExeName) + 'Test.srt');
// Copy the command string to the character array.
StrCopy(CmdStr, PChar(CmdList.Text));
// Get the size of the error array.
ErrorSize := SizeOf(ErrorStr);
// Sort the file.
SortReturn :=
Sort(CmdStr, ErrorStr, ErrorSize, nil, nil);
ShowMessage('Sort finished.');
finally
CmdList.Free;
end; // try
end;

Figure 4: The Sort buttons OnClick event handler.

If NitroSort cannot sort the data in memory, it will create a temporary sort work file on the drive that hosts the output file. To place the
work file somewhere else, use the WorkArea command:
InFiles "C:\IMPORT\20020601.DAT", "C:\IMPORT\20020602.DAT";
OutFile "C:\OUTPUT\SORTED.DAT";
Key [10, 8, char, ascend], [1, 4, int, ascend],
[20, 8, IEEEFloat, descend];
FileType Fixed 100;
Access ShareRead;
Header 80 Retain;
MaxMem 100;
WorkArea "D:\TEMP\SORTWORK.TMP";

NitroSort provides two ways to select the records that will be written
to the output file. Use the Range command to specify a starting and
ending record number, or a starting record number and number of
records. For example, Range 1000, 2000 will select records 1000
36 November 2002 Delphi Informant Magazine

through 2000 from the input file. Range 1000, +1200 will sort the
1,200 records starting with record 1000 in the input file.
Use the Qualifier command to select records based on their data
values. Field values in a Qualifier expression have the form [Starting Position, Size, Data Type]. For example:
Qualifier [1,4,int] = 200 and
([7,3,char]='ABC' or
[7,3,char]='abc');

indicates that a record will be output if the four-byte integer starting in position 1 equals 200, and the three-byte Char starting in
position 7 is equal to either ABC or abc. Qualifier expressions
can also include hexadecimal constants. Hex values begin with a
dollar sign. For example, instead of 255 you can use $FF.
Options is the last command in the NitroSort data manipulation language. Use it to add an EOF (end-of-file) character to
the output file, append the sorted records to an existing output
file, use the input file as the work file to save disk space, and have
NitroSort make progress callbacks to your application.

Using the Standalone Utility


Figure 1 shows the NitroSort main form. Here you can enter a sort
command, and click the Sort button to execute it. Clicking Save
Statement saves the command to a file. The Load Statement button
loads a saved statement from a file.
Click the Batch Process button to display the dialog box shown in
Figure 2. Use this dialog box to build a list of saved sort commands,
then click the Batch Sort button to execute the commands in sequence.
From the main form, click the Build Test File button to open the
Build Sorting Test File dialog box shown in Figure 3. Enter the
type, location, and size of your sort keys along with the file path,
record size, and number of records, and NitroSort will build a
fixed-length test file for you.

Using NitroSort
To use NitroSort, simply add the NITRODLL.PAS unit to your
project, and to the uses clause in the unit where you want to call
the Sort function. Figure 4 shows the code from the OnClick event
handler of the Sort button in the sample application that accompa-

New & Used


nies this article (see end of article for download details). This method
loads the sort command from a file into a StringList then copies it to
a Char array. The ErrorStr array is provided to hold any error message
returned by the Sort function.
The Sort function takes five parameters. The first is a PChar that contains the sort command. The second is a PChar to receive an error message if an error occurs. The third is the size of the ErrorStr PChar. The
fourth is a pointer to a callback function in your application. If the sort
command includes the Status option, NitroSort will call this function
periodically and pass information you can use to display a progress bar.
The final parameter is a pointer to a user-defined data structure. This
pointer is passed to the callback function each time its called to allow
you to pass any information you wish to the callback function.
NitroSort also provides the SortMem function to sort a block
of records in memory, and the SortMemByPtrs function to sort
pointers to records in memory. Sorting the pointers instead of the
entire record requires less memory.

Conclusion
NitroSort is fast, easy to learn, and easy to use. If you need to sort,
reformat, and select data from flat files, NitroSort may be just the tool
you need. Sort performance can only be described as stunning. Sorting one million 100-byte records took only 33 seconds on my 1 gHz
notebook. The accompanying documentation is excellent, and includes
both a help file and a user manual in Word format. The options are
clearly described and numerous examples show you how to use each of
the options in complete sort commands.
The sample application referenced in this article is available on
the Delphi Informant Magazine Complete Works CD located in
INFORM\2002\NOV\DI200211BT.

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


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

37 November 2002 Delphi Informant Magazine

File | New
Directions / Commentary

Project JEDI 2002

ts been a long time since I wrote about Project JEDI (Joint Endeavor of Delphi Innovators). Too long. I first wrote
about the Project in the December, 1997 Delphi Informant, just 10 days after the Project had begun. That column
reflects my initial excitement an excitement that continues to this day.
When I wrote about the Project next, in June 1999, I emphasized an
emerging partnership between the Project and Borland. This partnership
has matured, expanded, and benefited the entire Delphi/Kylix community. My role in the Project has also changed over the years.
The history. Project JEDI has an interesting, albeit humble, history. It
was a Friday evening toward the end of September, 1997. On a Delphi
Internet discussion group sponsored by the former COBB Group someone posted a complaint about the disparity between new technologies
available to C/C++ developers and those available to Delphi developers.
Why couldnt Borland convert more of these APIs for our use? Early
in the discussion someone suggested that this was an area in which we
could actually help ourselves. Thus, Project JEDI was born.
The Project began as a very loosely structured organization that benefited
from a small, energetic cadre of developers who produced initial results
rather quickly. Word began to spread. An early supporter, Bob Swart,
promoted the Project on his Web site. As time passed, third-party vendors offered invaluable tools and support: HREF, producers of WebHub
(http://www.href.com), donated space on their Web site; Toolsfactory
donated its popular source code documentation tool, Doc-O-Matic
(http://www.doc-o-matic.com) for use by the Projects help file writers;
and a series of newsgroups was provided through the generosity of Rene
Tschagelaar (http://www.talkto.net).
The Projects early days were characterized by ups and downs. During
one early down period it appeared the Project might die. But Tom
Guarino, an active Project member, contacted several of us to form an
Admin Group to help steer the Project uphill. It worked; Project activity
and communication picked up and we forged a successful partnership with Borland. As the Project grew, a Steering Team formed that
included many team leaders, as well as our first Borland representative,
Charlie Calvert. Calvert helped solidify the partnership with Borland,
and Project JEDI was able to contribute to the companion CDs that are
distributed with each new version of Delphi.
But nothing lasts forever. As the Steering Team took charge of more of
the Projects decision making, the Admin Group appeared mysterious
and aloof to some. During one period of conflict, Tom made the difficult
decision to disband the Admin Group. This important move provided
clearer organizational structure, wider representation to JEDI teams, and
greater diversity of opinions and ideas.
Under Toms leadership and with a burst of new energy from the Steering
Team, the Project continued to grow, moving into new territory of open
38 November 2002 Delphi Informant Magazine

source code and component development. Eventually the Steering Team


decided to hold annual director elections. In its first election in May,
2001, members elected Robert Marquardt as the new director. Under
Roberts stewardship the Project continued its steady growth, which was
recognized at the annual Borland Conference in Long Beach, California,
when the Project and many of its active members received the 2001
Spirit of Delphi Award. In May, 2002, Project members elected a new
director (someone you may have heard about): Alan C. Moore.
Personal rewards. It didnt take long for my involvement in the Project
to become helpful in my own development and writing. That first
year another founding member, Ken Kyler, got me interested in TAPI.
I helped some with the testing of the various TAPI translations (the
most recent one supports TAPI 3.0). Using the first Project JEDI TAPI
translation as our basis, we wrote a series of articles for this magazine
and developed a fairly sophisticated telephony project that folks are still
downloading and writing me about. I cant speak for others, but involvement with Project JEDI has been very rewarding for this developer.
Earlier I mentioned Bob Swarts support. In the early days of the
Project, Bob made his Header Converter (HeadConv) utility available
for API conversion teams and individuals. At the Borland Conference in Philadelphia in 1999 he asked if I would help clean up the
source code so it could be released to Project JEDI as open source. I
readily agreed, and over the next two and a half years worked closely
with him as time permitted, finally finishing the initial refactoring
toward the end of 2001. With the help of other JEDI knights during
the early months of 2002 I put the final touches on the source code
and released it just before the 2002 Borland Conference. We also
resurrected an old JEDI team, DARTH (Delphi Artificial Translator
Helper), to further develop the open source HeadConv, producing a
GUI front end for the tool early on.
The present. The Projects scope has expanded considerably and includes
a variety of projects, from code libraries to components, from educational resources to programming tools. You can learn more by visiting
the Projects Web site at http://delphi-jedi.org.
Another very active endeavor, the JEDI Code Library (JCL) maintains
a library of thoroughly tested and fully documented utility functions,
and non-visual classes for use in Delphi and C++Builder projects. The
JCL recently released version 1.21. Although most of the routines in the
library are intended for the Win32 (Microsoft Windows) environment,
there are some that support Linux and Kylix. The JCL home page is
located at http://sourceforge.net/projects/jcl. Even larger, the JEDI Visual

File | New
Component Library (JVCL) consists of over 300 components. A team is
currently creating help files for these components, but the many example
programs that accompany them demonstrate their use and make these
popular components very usable. Several new projects have emerged this
year, including the JEDI Version Control System (JVCS) that is aimed
at becoming a replacement for the FreeVCS client, and the JEDI-Math
Project, which we intend to develop as an open source math library for
Delphi and potentially Kylix.
Of course, API conversions remain one of the major activities of the
Project and we continue to update and add those. But these days there
is hardly an area of Delphi programming in which Project JEDI is not
involved, from business applications to games. There is a JEDI Business
Application Framework on one extreme and the JEDI-SDL project on
the other. In the area of multimedia, the JEDI-QuickTime project provides support for playing QuickTime movies. Most JEDI projects now
live on SourceForge. You can get an overview of the ones Ive mentioned
(and many more) by visiting http://projectjedi.sourceforge.net.
The future. Project JEDI is undergoing another structural change
one more evolutionary than revolutionary. Were restructuring the Steering Team to consist of the most active people in the
Project. We are also establishing an Advisory Group of leaders from
the Delphi community who will provide advice and support. We
want the Projects structure to be both effective and transparent to
everyone in the Delphi community.

39 November 2002 Delphi Informant Magazine

Very recently, Project JEDI made a major contribution to the


Companion CD distributed with Delphi 7. It includes updated
versions of many of our most popular resources. The Project has
and will continue to establish ties with other open source projects.
One new initiative, the JEDI Alliance, enables the Project to provide support for projects that aspire to join our efforts by helping
them survive the difficult formative period.
Project JEDI has meant a great deal to me personally. In a recent message, one of our founding members and a major contributor expressed
the way many of us feel about working in Project JEDI: My day job
I do for money and other people make the rules. Unpaid jobs I do for
personal pleasure, fulfillment and challenge. Working with Project JEDI
I have experienced a full measure of pleasure, fulfillment, and challenges.
Until next time.
Alan C. Moore, Ph.D.

Alan Moore is a professor at Kentucky State University, where he teaches music theory and
humanities. He was named Distinguished Professor for 2001-2002. He has been named
the Project JEDI Director for 2002-2003. He has developed education-related applications
with the Borland languages for more than 15 years. Hes the author of The Tomes of Delphi:
Win32 Multimedia API (Wordware Publishing, 2000) and co-author (with John Penman)
of an upcoming book in the Tomes series on Communications APIs. He also has published
a number of articles in various technical journals. Using Delphi, he specializes in writing

Potrebbero piacerti anche