On The Cover: December 1999, Volume 5, Number 12
On The Cover: December 1999, Volume 5, Number 12
ON THE COVER
6
DBNavigator
FEATURES
12 Undocumented
The Secret World of PIDLs Kevin J. Bluck and
James Holderness
Dull yet essential, PIDLs are integral to Win32 shell development, yet
theyre virtually undocumented especially within the Delphi community. Until Messrs Bluck and Holderness came along ...
18 Sound + Vision
A Multimedia Assembly Line: Part I Alan C. Moore, Ph.D.
REVIEWS
31 1st Class
Product Review by Bill Todd
25 In Development
An Automation Server Ron Loewy
Mr Loewy shows us how to design an Automation server, and build it
with Delphi. He also describes how to test the server by implementing it
as a plug-in, and using script to put it though its paces.
1
DEPARTMENTS
2 Delphi Tools
5 Newsline
34 File | New by Alan C. Moore, Ph.D.
Delphi
T O O L S
New Products
and Solutions
Delphi
T O O L S
New Products
and Solutions
The SourceView ActiveX control can be customized for coloring of any language syntax. In
most cases, customization can
be accomplished by setting control properties. For more complex syntax requirements, COM
interface hooks are provided for
plugging in custom parsing
implementations. Delphi,
Visual Basic, Visual C++, and
C++Builder examples are provided for coloring of C, Pascal,
Basic, and HTML syntax.
Developers using modern
development tools that sup-
Delphi
T O O L S
New Products
and Solutions
Woll2Woll Softwares
InfoPower, and Julian Zierschs
WPTools. Any edit component
will work with EDSZipCode.
Eminent Domain Software
Price: US$89
Phone: (800) 246-5757
Web Site: http://www.onedomain.com
News
L
December 1999
Inprise Names J.D. Hildebrand Content Director for New Community Site
Scotts Valley, CA Inprise
Corp. named J.D. Hildebrand
Content Director and Editorin-Chief of Inprises online
community for software developers. The service is currently
being previewed at http://
community.borland.com. The
goal of the new site is to provide information and services
to software developers worldwide.
As Editor-in-Chief,
Hildebrand will establish and
supervise systems for selecting,
acquiring, writing, fact-checking, and editing content for the
site. Prior to joining Inprise,
Hildebrand served as editorial
director of the Developer
Group at PennWell Publishing
Co., where he was responsible
for VB Tech Journal, Windows
Tech Journal, and other publications for software development professionals.
A programmer himself,
Hildebrand brings over 20 years
of experience in editorial management, magazine launches,
trade-show management, curriculum development, editorial
training, and business development to the project. He has
trained thousands of magazine
editors on topics ranging from
fact-checking and copyediting to
DBNavigator
Frames / Delphi 5
Delphi Frames
Understanding Delphi 5s New Visual Container Class
Overview of Frames
There are two primary benefits of frames. The
first is that, under certain circumstances, frames
can dramatically reduce the amount of resources
that need to be stored in a project. The second,
and generally more important benefit, is that
frames permit you to visually create objects that
can be duplicated and extended. These happen to
be the same two benefits that you enjoy with
visual form inheritance (VFI).
VFI permits you to create form objects that can be
inherited from easily. The main limit to VFI is that
you must use the form in an all-or-nothing fashion.
Specifically, when you use VFI you always create an
entirely new form. Frames, on the other hand, are
more similar to panels in this respect. That is, a single form can contain two or more frames.
Importantly, every frame maintains its relationship
with the parent TFrame class, meaning that subsequent changes to the parent class are automatically
inherited by the instances. Although you could
achieve a similar effect using TPanel components,
doing so would be a strictly code-based operation.
That is, you would have to write the code to define
the TPanel descendants manually. Frames, on the
other hand, are designed visually, just like forms.
6
Frames can also be thought of as sharing some similarities with component templates (a group of one
or more components that are saved to the
Component palette by selecting Component | Create
Component Template). However, the similarities are
limited to the fact that both component templates
and frames are designed visually (unlike traditional
component design, which is an exclusively codebased process). The differences between component
templates and frames are actually very great. As
youve already learned, a frame is an instance of a
defining class, and, as such, is changed when the
defining class is changed. By comparison, component templates are aggregates of components. A
change to a component template has no effect on
objects previously created from that template.
Creating a Frame
The following steps demonstrate how to create a
frame (the code for this project is available for
download; see end of article for details).
1) Select File | New Application to create a new
project.
2) Select File | New Frame to create a new frame.
On this frame, place three labels and three
DBEdits. Also place a DBNavigator and a
DataSource (as shown in Figure 1). Set the
captions of the labels to ID, First Name, and
Last Name. Set the DataSource property of
each DBEdit and the DBNavigator to
DataSource1.
3) With this frame still selected, set its Name
property to NameFrame. (More so than other
objects, its particularly important to give a
frame a meaningful name.) Finally, save the
frame by selecting File | Save As. In this
case, save the frame using the file name
NAMEFRAM.PAS.
DBNavigator
Thats all there is to creating a frame. The following section demonstrates how to put it to use.
Using a Frame
A frame is a component. However, its use typically differs from
most other components that appear on the Component palette.
The following steps demonstrate how to use a frame:
1) Select Form1 of the application you created in the preceding steps.
2) Add two group boxes to the form, one above the other. Set the
caption of the first frame to Customers, and the caption of the
second to Employees. Your form may look something like that
shown in Figure 2.
3) Now add the frames. With the Standard page of the
Component palette selected, click on the Frame component and
drop it in the Customers frame. Delphi responds by displaying
the Select frame to insert dialog box (see Figure 3).
4) Select NameFrame. The frame will now appear in the Customers
frame. Repeat this process, this time placing the frame within the
Employees frame. You may have to select each frame and correct
its size, depending on how you placed it originally. When youre
done, your form should look similar to that shown in Figure 4.
5) Continue by placing two Table components onto the form. Set
the DatabaseName property of both tables to IBLocal. Set the
TableName property of Table1 to CUSTOMER and the TableName
property of Table2 to EMPLOYEE. Make both tables active by setting their Active properties to True.
6) Heres where things get interesting. Select the DataSource in the
Customers frame, and set its DataSet property to Table1.
Normally you cant directly select objects that appear within a
component, but frames are special. You can select any of the
objects that appear within a frame, and work with their properties. Next, repeat this operation by selecting the DataSource in
the Employees frame and setting its DataSet property to Table2.
7) Finally, hook up all the DBEdits. Assign the DataField property of the three DBEdits on the Customers frame to
CUST_NO, CONTACT_FIRST, and CONTACT_LAST, respectively.
For the Employees frame, set the DataField properties of these
same DBEdits to EMP_NO, FIRST_NAME, and LAST_NAME.
8) Save this project and then run it. The running project will look
something like that shown in Figure 5.
DBNavigator
can change the properties and event handlers associated with the
objects inside the inherited frame. These changes override the inherited values. Specifically, subsequent changes to the overridden property in the original frame dont affect the inherited value. The following steps demonstrate this behavior:
1) Select the label whose caption is ID in the Customers frame.
Using the Object Inspector, change its Caption property to
Customer No:. Now select the ID label for the Employees
frame and change it to Employee ID:.
2) Press S@ and select NameFrame. Change the caption of
this ID label to Identifier.
3) Return to the main form. Notice that the Caption properties of
the labels havent changed to Identifier. They still use their overridden values.
4) This effect is accomplished through information stored in the
DFM file. Figure 7 displays a relevant part of the DFM file for
this project.
Notice that information about all components contained within the
frame whose property values have been changed appear in the frames
inline section of the DFM file. However, this section only lists those
values that have been changed. All other properties are assigned their
values based either on the values set for the original frame (and which
are stored in the frames DFM file), or are designated as default values
in the individual components class declarations.
DBNavigator
procedure TTwoButtonFrame.Button1Click(Sender: TObject);
begin
if (TComponent(Sender).Tag = 0) or
(Application.HelpFile = '') then
MessageBox(Application.Handle,'Help not available',
'Help',MB_OK)
else
Application.HelpContext(TComponent(Sender).Tag);
end;
procedure TTwoButtonFrame.Button2Click(Sender: TObject);
var
AParent: TComponent;
begin
AParent := TComponent(Sender).GetParentComponent;
while not (AParent is TCustomForm) do
AParent := AParent.GetParentComponent;
TCustomForm(AParent).Close;
end;
Figure 9: The OnClick event handlers for the Help and Done
buttons on our frame.
procedure TForm1.TwoButtonFrame1Button2Click(
Sender: TObject);
begin
with TForm2.Create(Self) do begin
ShowModal;
Release;
end;
// The following is the original, auto-generated code
// TwoButtonFrame1.Button2Click(Sender);
end;
If you inspect the code associated with the Button2Click event handler, which is associated with the Done button, youll notice that the
event handlers associated with the frame introduces an interesting
artifact. Specifically, Self is the frame, not the form in which the
frame is contained. Consequently, it isnt possible to simply invoke
the Close method from within this event handler to close the form.
When an unqualified method invocation appears in code, the compiler assumes you want it to apply to Self. Because a TFrame object
doesnt have a Close method, the compiler generates an error if you
simply use an unqualified call to Close.
Because the frame in this example is designed to be embedded within a
form, the event handler uses the GetParentComponent method of the
frame to climb the containership hierarchy within which the frame is
nested. Once a TCustomForm instance is found (which will either be a
TForm descendant or a custom form based upon TCustomForm), that
reference is used to invoke the forms Close method.
DBNavigator
LogoFrame
appears on more
than one form in
the FramDemo
project. The
alternative to
using a frame to
display the logo
is to place an
Image object on
Figure 13: The Component Template
each form upon
Information dialog box.
which you want
the logo to appear. However, the use of a frame for this purpose significantly reduces the amount of resources that must be compiled into the
.EXE, and, therefore, results in a smaller executable.
The reason for this can be seen if you consider the following segment of the DFM file for the form shown in Figure 11:
inline LogoFrame1: TLogoFrame
Left = 6
Top = 6
Width = 211
Height = 182
inherited Image1: TImage
Width = 211
Height = 182
end
end
If, instead, a TImage instance had been placed onto the form, the
DFM file for the form would have had to contain the entire binary
representation of the logo. Figure 12 shows a segment of
LogoFrames DFM file. (Note that it shows only a tiny portion of the
entire hexadecimal representation of the binary resource.)
Furthermore, every form containing one of these images would have
repeated this resource. When a frame is used, however, that resource
is defined only once.
3)
DBNavigator
2)
3)
a new class, not an instance. This new class is either a copy of the
original or a descendant, depending on which radio button you
select in the Object Repository dialog box. If you want to use a
frame in a project, it makes a great deal of sense to place an instance,
rather than define a new class for your frame. For this purpose, saving the frame to the Component palette is the best approach.
The one situation where you might want to use the Object
Repository is when youre specifically creating hierarchies of frames,
where each frame descendant introduces additional objects, methods, or event handlers. Here, the inheritance offered by the Object
Repository makes it easier for you to create each new descendant.
However, once youve defined the frame descendants you want to
use regularly, I would again suggest that you add these to the
Component palette to simplify their use.
The files referenced in this article are available on the Delphi Informant
Works CD located in INFORM\99\DEC\DI9912CJ.
Conclusion
Does it make a difference whether you place a frame you want to
reuse on the Component palette or the Object Repository? The
answer is a strong Yes! In most cases, youll want to place frames
you use frequently onto the Component palette. When you place a
frame from the Component palette, youre always placing an
instance of the frame class. You can then easily change the properties
and event handlers of this instance as described earlier in this article.
By comparison, placing a frame from the Object Repository creates
11
Undocumented
PIDLs / Shell IDs / Delphi 2-5 / 32-bit Windows
ne of the most visible changes introduced with Windows 95 was the new shell interface. Love it or hate it, the shell fundamentally changed how applications integrate
with the Windows operating system. Unfortunately, Microsoft has been parsimonious
when distributing information about how to integrate your applications with the shell.
Frankly, its shocking how little information is available about shell programming, and
almost all of what is available is in the form of unbelievably dry technical details with
strong soporific qualities. Moreover, virtually no information is available for Delphi developers, because almost all Windows API documentation is for C/C++ programmers.
This article is a start at changing that sad state of
affairs. Were sorry to say there will be no nifty
components in this piece. Instead, well examine
the unglamorous plumbing at the heart of shell
programming. The center of everything, when
programming the shell, is a little-understood construct known as the PIDL. Thats right PIDL.
Undocumented
Under the old DOS file system, every element of the system was
uniquely identified by its fully qualified path. The fully qualified
path was the absolute name. A certain 8.3-style file name or single
directory name might be repeated many times on a given disk drive,
but the same name couldnt exist more than once inside a given
directory. The 8.3-style file name alone is a relative name, unique
only to the directory containing the file. Tracing and pre-pending
the complete list of relative directory names from the root to the
destination item always results in a completely unique absolute
name for every element on the disk.
Most developers are familiar with the DOS-style notation for forming an absolute name in the namespace. A fully qualified path
begins with the drive letter, delimited by a colon. The root is
assumed, more or less equivalent with the drive itself. Each directory
along the path to the ultimate goal is listed by its relative name.
Each directory name is delimited by a single backslash, so that you
can tell where one name ends and the next begins. The last entry
after the last backslash is the relative name of the item being identified, whether its a directory or a file. The entire string together constitutes the directory or files absolute name.
This naming system worked adequately for the DOS file system.
However, with the advent of Windows 95, not all folders and
items could be described in terms of the file system. A new naming system was needed to uniquely identify all the elements of
the namespace.
Redmonds answer was two new data structures. Each elements relative name was specified with an item identifier, defined as a
SHITEMID record, and known in Delphi as TShItemID. These
records could be concatenated as needed, conceptually the same
operation as directory names being strung together with backslashes.
Such a string of records was known as an item identifier list, or IDL,
defined by the ITEMIDLIST record type, and known in Delphi as
TItemIDList. Because IDLs are almost always manipulated by pointers, the entire structure became commonly known as a PIDL (pronounced and sometimes written as piddle). This pointer type is
known in Delphi as PItemIDList. PIDLs, or pointers to item identifier lists, are the universal means of identifying elements within the
namespace shell. All of these Delphi data types are defined in the
standard VCL ShlObj unit.
The precise structure of these records often causes confusion. The
most important thing to understand about the internal structure of
PIDLs is that youre not supposed to understand most of it. DOSstyle paths, as you know, are string types, and are, therefore, humanreadable. PIDLs are binary types, and most of their content isnt
intended for your direct manipulation. Adding to the confusion is
the fact that TShItemID and TItemIDList are variable-length types.
Their declarations are, therefore, less than clear.
almost any size up to about 65,533 bytes. At first glance, the abID
(array of bytes ID) data member seems pretty weird. An array with a
single element of Byte? Why not just Byte?
Dont take this literally. The Windows programmers took advantage
of the C languages ability to de-reference any desired index of an
array, even if that index exceeds the original boundaries defined for
the array. This isnt really an array of one byte. They just say that in
the definition to provide a placeholder for later use. Its actually an
array of (cb - SizeOf(cb)) bytes. Exactly what data goes in this array?
Quite literally, anything. Unless youre writing a shell namespace
extension, which is well beyond the scope of this article, you have
no business poking around in that data. For most purposes, just
resign yourself to not knowing whats in there, and simply accept
that it exists.
The declaration of TItemIDList is, if possible, even more unintuitive:
TItemIDList = packed record
mkid: TShItemID;
end;
First is a Word known as cb. Translated from that lovely Hungariannotation brevity C programmers seem to prefer, cb means count of
bytes. Its the total size, in bytes, of the entire TShItemID record.
You need this information because the next data member can be of
13
Undocumented
and absolute PIDLs. An absolute PIDL is one that is fully qualified
with a string of TShItemID records that walk down all the way from
the root of the namespace, the Desktop folder. A relative PIDL is
usually taken to mean a PIDL with a single TShItemID contained in
the list, naming a folder or item relative to its immediate parent
folder. Obviously, this single ID is only useful if you know which is
its parent folder.
Folders in the shell are represented by a COM interface called
IShellFolder. We wont get into a lot of detail about IShellFolder, as
its a rather broad topic that deserves its own article. Suffice it to say
that a given reference to IShellFolder represents a given shell folder.
The various methods provided by IShellFolder nearly always deal
with relative PIDLs, because the IShellFolder object itself serves to
identify the PIDLs parent folder. The family of SH... shell API
functions, however, doesnt typically include a reference to a specific
IShellFolder object. Therefore, these standalone shell functions generally take and return absolute PIDLs. Consequently, its important
to keep track of what sort of PIDL your pointer is referencing, as a
PIDL returned by an IShellFolder method isnt likely to be immediately useful in an SH... function.
abID
cb
22 bytes 17
abID
cb
15 bytes 20
abID
cb
18 bytes 0
abID
0 bytes
14
IMalloc = interface(IUnknown)
['{ 00000002-0000-0000-C000-000000000046 }']
function Alloc(cb: Longint): Pointer; stdcall;
function Realloc(pv: Pointer; cb: Longint):
Pointer; stdcall;
procedure Free(pv: Pointer); stdcall;
function GetSize(pv: Pointer): Longint; stdcall;
function DidAlloc(pv: Pointer): Integer; stdcall;
procedure HeapMinimize; stdcall;
end;
function SHGetMalloc(var ppMalloc: IMalloc):
HResult; stdcall;
Undocumented
From Paths to PIDLs and Back Again
Typically, PIDLs are returned to you by certain functions and
passed around to other functions, without you ever really knowing
anything about the internal structure of the IDL and without you
ever needing to create a PIDL. Obviously, if youre implementing a
namespace extension, youll need to create item identifiers for the
objects in your own virtual folders, but the format of those item
identifiers is specific to your folder, and you can do whatever you
want with them. But what happens if you need to create a PIDL
for somebody elses namespace, such as a path in the file system?
The documented way would be to get an IShellFolder interface for
the desktop, convert the path string in question into a PWideChar
null-terminated UNICODE string, and then call the desktop
IShellFolders ParseDisplayName method with the PWideChar. What
a drag! If youre feeling lazy, you might find it easier to use one of
these three functions:
Display Name
If you need to determine the display name of a PIDL, the documented method is to use the method IShellFolder::GetDisplayNameOf.
After you figure out which of the three possible ways the string was
returned to you is the correct one, youll have the user-friendly
name for the shell object the PIDL represents.
Its also possible to get the display name from the documented
SHGetFileInfo API function, if you dont mind figuring out which
flags to use and fishing around in the returned record for the data
you want. Or, if youre in a hurry, you can use the
ILGetDisplayName function. ILGetDisplayName basically calls the
desktops IShellFolder::GetDisplayNameOf function with the flag
SHGDN_FORPARSING. The ordinal value for ILGetDisplayName
is 15. As you can see from the code snippet below, this function
doesnt return the normal short display name for file system
objects, but rather the fully qualified path. If you want the short display name, you will be better off using SHGetFileInfo:
15
You may have noticed that the string-type parameters for the
undocumented functions previously shown are declared as type
Pointer instead of PChar. This is because of a small catch that
is common for undocumented functions. All of these string-type
parameters take PAnsiChar on Windows 95, and PWideChar on
Windows NT.
Theres no choice of ANSI or UNICODE as would be expected
for a documented function. Windows 95 uses the ANSI version
only, and Windows NT uses the UNICODE version only. Take it
or leave it. If you want your applications to function correctly on
both platforms, youre going to have to check what operating system is in use at run time. The Win32Platform global variable provided in the SysUtils unit is handy for this. If your application is
running on NT, youll need to convert any string parameters to
PWideChar before calling the function. When the function
returns, youll also obviously need to convert any returned strings
back to PAnsiChar. It may be annoying, but thats the price you
pay for using undocumented functions.
Undocumented
parameter of the function to True. The following code shows
the function declarations:
function ILIsEqual(PIDL1: PItemIDList; PIDL2: PItemIDList):
LongBool; stdcall;
function ILIsParent(PIDL1: PItemIDList;
PIDL2: PItemIDList; ImmediateParent: LongBool):
LongBool; stdcall;
The ordinals for these functions are 21 and 23, respectively. Note
that the equality of two ID lists cant necessarily be determined
with a binary comparison if youre tempted to try that.
Equivalent PIDLs can conceivably have different internal binary
structures. Both of the functions previously shown use the
Desktop folders IShellFolder::CompareIDs method to perform
equality tests. The ILIsParent function, of course, only tests
whether the base PIDL of the child is equal to the parent PIDL.
Parsing a PIDL
Sometimes you have a need to parse a PIDL, identifying individual
IDs contained in the list. There seem to be no documented functions for these tasks. Apparently, Microsoft expects you to implement functions to slice and dice PIDLs yourself. Luckily, weve got
the inside scoop for you.
If you need to determine the total size in bytes of all the identifiers
in a PIDL, you can use the ILGetSize function. If you need to iterate
forward through each item identifier in a PIDL, youll probably find
ILGetNext very useful. When given a PIDL (or a pointer to any ID
in the list, for that matter), the function will return a pointer to the
next item identifier in the list. If the PIDL is nil or is already pointing to the last item in the list, the function will return nil. For the
specific case of finding the last item identifier in the list, you can just
call ILFindLastID.
An even more specific form of search is the ILFindChild function. Given a parent PIDL and a child PIDL, it will return a
pointer to the unique portion of the child. For example, if you
passed it PIDLs for the folder C:\DIR as the parent and the
item C:\DIR\FILE.TXT as the child, it would return a pointer to
that portion of the child PIDL that represents FILE.TXT. If the
given child isnt a child of the parent, the function will return nil.
The ordinals for these functions are 152, 153, 16, and 24, respectively (see Figure 5).
If you want to combine two PIDLs, you would use the ILCombine
function. Given two PIDLs, it will create a new PIDL containing the
two source lists joined consecutively. If you want to combine a single
item identifier with a PIDL, you would use the ILAppendID function.
It can be used to append a TItemID record to the beginning or end of
an existing IDL. However, unlike ILCombine, the original PIDL is
destroyed by this operation. The ILAppendID function can also be
used to create a PIDL from an item identifier alone by passing a nil
for the PIDL. The ordinals for these functions are 25 and 154, respectively. The function declarations are shown here:
function ILCombine(PIDL1: PItemIDList; PIDL2: PItemIDList):
PItemIDList; stdcall;
function ILAppendID(PIDL: PItemIDList; ItemID: PShItemID;
AddToEnd: LongBool): PItemIDList; stdcall;
On Windows NT, these global functions just use the default process
heap (as returned by GetProcessHeap). This led us to believe that
heap allocations were in some way more efficient than the shell allocator, and that global functions were only used internally by the shell
for reasons of efficiency.
However, on Windows 95 most of the internal structures in the shell
need to be shared between all instances of the DLL. In the case of
PIDLs, the memory used when allocating them obviously has to be
shareable as well. ILGlobalClone solves this problem by using an
undocumented shared heap for the allocations, conveniently making
the pointers accessible from anywhere. In general, this is a specialized
technique only applicable to magic programming within the
Explorer itself. Were sure Microsoft would frown on those who dare
to use this facility for their own purposes.
Truncation
If you need to delete an entire PIDL, just use the ILFree function. However, if you only need to remove the last item
identifier from the end of a list, you can use the ILRemoveLastID
function:
16
Undocumented
The ordinal value is 17. The return value is True if the operation
was successful. However, note that it doesnt actually free any
memory; it just resets the end of list marker. Unfortunately, this
is the only deletion function that exists. If you want to remove
an item identifier from the beginning of an IDL, the best you
can do is use a combination of ILGetNext and ILClone to make a
copy of the original list starting with the second ID in the list,
and then delete the original PIDL with ILFree. Trying to delete
IDs from the middle of the list would be even more complicated,
but we cant imagine that such needs are very common.
Conclusion
PIDLs arent an exciting topic by any stretch of the imagination,
but a good understanding of their nature is essential to virtually
any task in programming with the Windows 95 shell. Its unfortunate that Microsoft has done such a poor job of publicizing the
information, which developers need to properly integrate their
applications with the shell. We hope this article has provided the
Delphi developer community with the basic foundation necessary to undertake any project that will interface with the shell.
The mechanics of manipulating PIDLs may be dull, but were
sure your users wont think the well-integrated applications you
can produce using this knowledge are boring. Get ready to write
something amazing!
The files referenced in this article are available on the Delphi Informant
Works CD located in INFORM\99\DEC\DI9912KB.
17
Sound + Vision
Multimedia / Experts/ Tool Services / Delphi 1-5
n early 1997, I wrote a Delphi Wizard (expert) to produce multimedia components with
WAV-file-playing capabilities. That original expert was limited in a number of respects.
Here well extend that expert, adding greater flexibility and functionality. Well also provide a model for other complex experts that generate component code.
18
Sound + Vision
Flag
Description
Flag
Description
snd_Sync
snd_Application
snd_Async
snd_Memory
snd_Loop
snd_NoStop
19
snd_Alias
snd_Alias_Id
snd_Async
snd_FileName
snd_Loop
snd_Memory
snd_NoDefault
snd_NoStop
snd_NoWait
snd_Purge
snd_Resource
snd_Sync
The expert also needs to keep track of the status of each check box
and the possible sound event class it may eventually produce. We use
another enumerated type to accomplish this:
EventCheckBoxStatus =
(ecbsUndefined, ecbsDefined, ecbsChecked);
The first possibility, ecbsUndefined, indicates the check box has never
been checked. The second, ecbsDefined, indicates that data has been
entered for the particular check box. Then, if you enter data,
uncheck the box, and then decide to check it again, the original data
entered in the popup dialog box will appear and can be edited. The
final possibility, ecbsChecked, simply keeps track of the checked status of the check box and is used in the component-producing
engine, which well be discussing toward the end of this article. A
value for each event/check box is stored in the following array:
EventCheckBoxArray:
array [0..EventMax] of EventCheckBoxStatus;
Because event names are used in constructing the new classes well
be discussing below, we need to keep track of them in a constant
array whose members correspond to the event/check boxes:
EventNames: array [0..MaxEvents] of string =
('Click', 'DragDrop', 'DragOver', 'EndDrag', 'Enter',
'Exit', 'KeyDown', 'KeyPress', 'KeyUp', 'MouseDown',
'MouseMove', 'MouseUp', 'StartDrag');
Sound + Vision
The information from the dialog box applying to each sound event
(see Figure 4) is stored in the following structure:
SoundFactors = packed record
SoundSource : TSoundSource;
Yield : Boolean;
WavFileDefault : Boolean;
DefaultWavFile : TFileName;
SoundPlayOptions : TSoundPlayOptions;
end;
Because there are 13 possible sound events (12 in Delphi 1), the
data is stored in a public array of the expert class:
Delphi version
(again, see
Listing Two).
The functions
in the 32-bit
versions end
with stdcall
and export; in
Delphi 1 they
end with only
export. The
Figure 4: The Sound Playing Options dialog box.
function definitions are indicated in the following conditional define:
Now that youve been introduced to the main variables and data
structures used in this expert, youll be able to study the code with
greater comprehension. Now we need to look at the steps involved
in writing the code. Consider three basic steps in expert creation:
1) Creating the Delphi expert interface
2) Creating the user interface
3) Writing the expert engine
Well have quite a bit to say about each of these major steps. Well
begin by discussing the Delphi expert interface. In this case, well
create a library project that will work in either 16-bit or 32-bit
Delphi environments.
{ $IFDEF WIN32 }
function InitExpert(ToolServices: TIToolServices;
RegisterProc: TExpertRegisterProc; var Terminate:
TExpertTerminateProc): Boolean; stdcall; export;
{ $ELSE }
function InitExpert(ToolServices: TIToolServices;
RegisterProc: TExpertRegisterProc; var Terminate:
TExpertTerminateProc): Boolean; export;
{ $ENDIF }
This function is also used to register the expert with Delphi using
the RegisterProc method. There are many differences between the 16bit and 32-bit versions of this expert. These are clearly indicated
with conditional statements.
If you need to free any memory or perform other cleanup (which
we didnt in this instance), youll need a DoneExpert procedure,
which is included for your reference. For Delphi 1 only, a
FaultHandler function must also be included.
Now that weve fully examined the expert interface, lets look at
the user interface implemented in SndExp1.pas (not shown here
due to space constraints). The entire project is available on disk or
for download; see end of article for details. Note that global variables used in this and other units are declared in SndTypes.pas
(again, see Listing One).
Sound + Vision
to save it, its ancestor, and the additional
units to include in the uses clause (usually the ancestors unit at a minimum).
The second TEdit and the TCombo behave
in unusual ways. By writing an OnClick
(and OnEnter) method, clicking on or
moving to the second TEdit automatically
brings up TSaveDialog so we can choose a
file name and directory for our new component. The PopulateComboBox method
automatically fills the check box with the
names of all components installed in the
VCL. It uses some of Delphis tool services
to accomplish this. Lets see how the
magic works.
Method
Use of Method
GetComponentCount(X)
Figure 5: The tool services methods used in the expert to get information on
installed components.
Conclusion
Well have to leave it there for now. Weve discussed most of the
basic issues involved with the expert interface and user interface.
Next month well discuss the experts component-creating engine.
Well have a bit more to say about the tool services when we reach
the end of this section and discuss the Finish button. First, lets take a
look at the second page.
On the second page of the expert, you can check the events you want
to implement in the new component (see Figure 6). Whenever a new
event is checked, another dialog box prompts for the parameters for
that sound event (refer to Figure 4). This dialog box always appears
with either the default values checked (in this case none), or the previously selected values (if the event had been selected previously).
The files referenced in this article are available on the Delphi Informant
Works CD located in INFORM\99\DEC\DI9912AM.
interface
You must choose at least one event before the Finish button
becomes enabled. When you click on Finish the expert closes, and
the generated code comes up in the Delphi IDE editor. This is
accomplished with another ToolServices function:
iTools.OpenFile(UnitName);
uses SysUtils;
type
String80
String30
String15
String2
=
=
=
=
string[80];
string[30];
string[15];
string[2];
Sound + Vision
'(Key)',
{ For KeyPress
'(Button, Shift, X, Y)', { For MouseDown and MouseUp
'(Shift, X, Y)',
{ For MouseMove
'(DragObject)'); { For StartDrag in 32-bit versions
AnEventSelected: EventSelected;
EventIsEnabled: array [0..12] of Boolean;
EventCheckBoxArray: array [0..12] of EventCheckBoxStatus;
EventCheckBoxStatus =
(ecbsUndefined, ecbsDefined, ecbsChecked);
implementation
end.
const
{ Number of events user can sound-enable. 0-based;
actual number of events is one more. }
{ $IFNDEF VER80 }
EventMax = 12;
{ $ELSE }
EventMax = 11;
{ $ENDIF }
uses
{ $IFNDEF VER80 }
ShareMem,
{ $ENDIF }
Forms,
WinTypes,
ExptIntf,
ToolIntf,
VirtIntf,
SysUtils,
WinProcs,
Sndtypes in 'SNDTYPES.PAS',
OptDlg in 'OptDlg.pas' { SoundPlayingOptions },
compgen in 'compgen.pas',
SndExp1 in 'sndexp1.pas' { SoundCompForm };
22
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
Sound + Vision
end;
{ Expert Methods }
{ $ifndef Ver80 }
constructor TSoundExpert.Create;
var
MainMenu : TIMainMenuIntf;
MainMenuItems, ComponentMenu : TIMenuItemIntf;
begin
inherited Create;
MainMenu := nil;
MainMenuItems := nil;
ComponentMenu := nil;
NewMenuItem := nil;
try
try
MainMenu := ToolServices.GetMainMenu;
MainMenuItems := MainMenu.GetMenuItems;
ComponentMenu := MainMenuItems.GetItem(6);
NewMenuItem := ComponentMenu.InsertItem(0, 'S&ound',
'TSoundComponentExpert', '', 0, 0, 0,
[mfVisible, mfEnabled], RunSoundExpert);
finally
ComponentMenu.Free;
MainMenuItems.Free;
MainMenu.Free;
end;
except
ToolServices.RaiseException(ReleaseException);
end;
end;
{ $ENDIF }
{ Exceptions must be handled this way. }
procedure HandleException;
begin
ToolServices.RaiseException(ReleaseException);
end;
{ $ifndef Ver80 }
procedure TSoundExpert.RunSoundExpert;
begin
try
ExecuteExpert(ToolServices);
except
ToolServices.RaiseException(ReleaseException);
end;
end;
destructor TSoundExpert.Destroy;
begin
NewMenuItem.Free;
inherited Destroy;
end;
function TSoundExpert.GetAuthor: string;
begin
try
Result := 'Alan C. Moore';
except
HandleException;
end;
end;
{ $endif }
function TSoundExpert.GetName: string;
begin
try { try/except blocks needed for each expert method. }
Result := 'Sound Component Delphi Expert';
except
HandleException;
end;
end;
function TSoundExpert.GetComment: string;
begin
try
23
Sound + Vision
ExptIntf.ToolServices := ToolServices;
if ToolServices <> nil then
Application.Handle := ToolServices.GetParentHandle;
Terminate := DoneExpert;
RegisterProc(TSoundExpert.Create);
end;
{ $IFDEF WIN32 }
{ $ELSE }
function FaultHandler(FaultID: Word; FaultAddr: Pointer):
TFaultResponse; export;
begin
DefaultExceptHandler(FaultID, FaultAddr);
end;
{ $ENDIF }
exports
{ $IFNDEF WIN32 }
FaultHandler name FaultHandlerSignature resident,
{ $ELSE }
{ $ENDIF }
InitExpert name ExpertEntryPoint resident;
begin
end.
24
In Development
Automation / COM
By Ron Loewy
An Automation Server
Object Model Design and Implementation
his article describes how to design an Automation server, and build it with Delphi. It
also describes how to test the server by implementing it as a plug-in, and using
script to put it though its paces. Lets begin with a review of the technology involved,
and terminology used to describe it.
An application is an Automation server when it
allows an external application (or scripting tool) to
control it programmatically, via a COM interface.
The Microsoft Office applications (Word, Excel,
etc.) are good examples of Automation servers.
For example, many applications use Words mail
merge capabilities from external applications to
create letters, reports, and forms populated with
data from the managing (client) application. Excel
is similarly used by many enterprise applications
as a calculation engine, e.g. a pre-defined spreadsheet is created and data is transferred to Excel by
the client application (known as the Automation
client) to perform complex calculations. Other
examples include network charting applications
that employ general-purpose tools such as Visio or
Micrografx Charter to diagram a network.
25
Why COM?
There are some advantages to using COM as a
way to create an application extension framework.
COM defines a standard method to offer
Automation. Any application that knows how to
talk to an Automation server will be able to automate your application via COM interfaces.
Unlike DLLs that require extensive documentation, COM-based Automation objects are self
documenting via type libraries. Though you
should always provide documentation for an
application extension framework, you wont need
to create interface modules for every language you
want to support, and you wont be limited to tools
that can call DLLs. (Note: Delphis Open Tools
API was created with Delphi 1 and has been
enhanced since that time.)
In Development
Although we often hear that Delphi uses Delphi to extend itself,
Delphi allows experts and add-ins created with other tools to be
installed. The Open Tools API, however, must provide special functions to allow C/C++ functions to call and manipulate Delphi functions, objects, and data structures. Even though COM plug-ins have
more overhead than Delphi plug-ins, I have the feeling that Delphi
would benefit from the cleaner approach to application extension
via COM. (The Delphi debugger is implemented as a COM object
of some sort, so Inprise developers are also aware of the benefits of
COM for application extensibility.)
good idea to hold a field in your applications main form that points
to an instance of the Application Automation objects interface. This
object can be instantiated in the OnCreate event of the main form.
In the sample application, I created an Automation object named
eAuthorApp (using Delphis Automation object expert on the
ActiveX page of the New Items dialog box). The main form holds a
field using the following declaration in its private section:
FeAuthorApplication : IeAuthorApp;
Providing access to the other objects in the object model is done via
properties and methods of the Automation object. Because the sample applications database objects descend from a common ancestor,
I created a virtual CreateAutomationObject method in the ancestor
and created a default Automation object that applies to every object
in the database. Some of the objects that descend from this common
ancestor override CreateAutomationObject and return a pointer to an
Automation object that provides more functionality than the default
object. Your design will have to be based on the actual design of
your objects and data structures.
My applications Application object provides an entry into the database
object model by exposing a Project property defined of type IDispatch.
(Remember that properties and methods defined in Automation objects
must be defined using the Type Library editor and implemented in the
implementation unit). Because the sample application has only one
entry point into the database object model and because this entry point
is commonly used, I want to cache it to see that my implementation of
TeAuthorApplication includes a private pointer to IDispatch:
ProjectObject : IDispatch;
As you can see, the main form holds a global HyperTextProject Delphi
object and calls the CreateAutomationObject method to retrieve a
pointer to its IDispatch interface. Notice the need to call _AddRef to
ensure the objects reference count will keep it cached in memory.
Obviously, I need to free it in the destructor:
In Development
destructor TeAuthorApplication.Destroy;
begin
if (Assigned(ProjectObject)) then
IUnknown(ProjectObject)._Release;
end;
EditableObject
EditableObject
Descends From
Descends From
AutoProject
CreateAutomationObject
AutoHtmlPage
AutoXMLDocument
THyperTextProject
CreateAutomationObject
CreateAutomationObject
THtmlPage
TXMLDocument
must ensure the object model you designed works as expected and
provides access to functionality needed by extension clients. When
I was ready to test and debug my COM object model, I considered
writing an Automation controller application with Delphi to test the
server. Although this approach would work, I decided to test my
object model by developing the scripting code and testing the object
model from within the sample application. This made the concept
of setting debug breakpoints and writing scripting code to test new
objects easy and compile-free. (Again, see Delphi 3 ActiveX in the
February, 1998 issue of Delphi Informant Magazine for more about
ActiveScript and its inclusion in Delphi applications.)
For my purposes, I added a Scripts menu option to the main menu
of the main form. This option opens a script dialog box that
includes two TMemo components: one used as an editor, and the
other as a console to test the results of the script.
In the TActiveScriptSite implementation of my ActiveScript unit, I
exposed the Application and Project objects using the code shown
in Figure 2.
Finally, I connected the console to the Application Automation
object by adding Write, WriteLine, and ClearConsole methods. The
Write implementation follows:
procedure TeAuthorApplication.Write(
const AText: WideString);
begin
if (Assigned(eAuthorSite.Console)) then
eAuthorSite.Console[eAuthorSite.Console.Count - 1] :=
eAuthorSite.Console[eAuthorSite.Console.Count - 1] +
AText;
end;
function TActiveScriptSite.GetItemInfo(
ItemName: WideString; dwReturnMask: DWord
out UnkItem: IUnknown; out TypeInfo: ITypeInfo): HResult;
var
ObjDispatch : IDispatch;
begin
{ Does the engine want the Automation object's
IUnknown pointer? }
if (dwReturnMask = SCRIPTINFO_IUNKNOWN) and
(ItemName = 'Application') then
UnkItem := eAuthorSite.eAuthorApplication;
if (dwReturnMask = SCRIPTINFO_IUNKNOWN) and
(ItemName = 'Project') then
UnkItem := eAuthorSite.eAuthorApplication.Project;
{ Does the engine want the Automation object's
type information? }
if (dwReturnMask = SCRIPTINFO_ITYPEINFO) and
(ItemName = 'Application') then begin
ObjDispatch := eAuthorSite.eAuthorApplication;
{ Get a handle to our Automation object's
type library. }
ObjDispatch.GetTypeInfo(0,0,TypeInfo);
end;
if (dwReturnMask = SCRIPTINFO_ITYPEINFO) and
(ItemName = 'Project') then begin
ObjDispatch := eAuthorSite.eAuthorApplication.Project;
ObjDispatch.GetTypeInfo(0,0,TypeInfo);
end;
Result := S_OK;
end;
In Development
Console is a global TStrings property of the main form. When the
Application object needs to write a line to the console, it simply adds
it to the console.
28
Help
In Development
procedure TAddInObject.RegisterMenus;
var
NewMenu : OleVariant;
TheMenu : IAutoMenuItem;
begin
TheMenu := eAuthorApp.Menu('Help') as IAutoMenuItem;
NewMenu := TheMenu.AddSubItem;
NewMenu.Tag := 3;
NewMenu.Caption := 'Add In!';
NewMenu.SetHandler(Self as IeAuthorMenuHandler);
end;
constructor TAutomatedMenu.Create;
begin
inherited Create(AOwner);
OnClick := HandleClick;
end;
procedure TAutomatedMenu.HandleClick;
begin
if (Assigned(FHandler)) then
FHandler.Execute(Tag);
end;
Sample Application
To demonstrate application extension via COM plug-ins, I wrote a
simple text editor application (see Figure 6). The application uses a
simple tabbed interface that allows you to edit multiple text documents. Think of it as a poor mans Notepad with extensibility. I
decided to implement only a subset of the functionality one would
normally implement in such an application, which is the minimum
I needed to make the example useful.
The code that is available as part of this article includes the project
editproj.dpr, the Delphi source for the sample application (available
for download; see end of article for details). MainForm.pas is the
definition and the code of the main form. PIMgr.pas is the code for
the plug-in manager dialog box, which is used to register COM
plug-ins with the sample application.
The applications object model consists of an Application object that
provides access to the menu bar and to the document being edited.
A more complete example will allow access to documents not currently edited by the user and the ability to switch between documents, but for the purpose of this example, this is all I needed.
The COM Application object (implemented in AutoApp.pas) provides access to COM objects implemented in the AutoMenu.pas
file, and to the COM document object implemented in the
AutoDoc.pas. The Menu object allows you to set menu attributes
(caption, short-cut, visibility, and for newly created menus
code handler), and allows you to create new menu entries that will
be inserted into the applications menu bar. The document object
provides access to the selected text and cursor location, and allows us
to insert new text lines into the document.
In Development
In the applications OnShow event, the list of registered
plug-ins is read from the registry, and these plug-ins are
activated. The plug-ins in turn create new menu items that
are added to the applications menubar. The user can now
select the new menu entries and activate them.
The type library of the sample project also defines two
interfaces, IEditMenuHandler and IEditPlugin, that must
be implemented by plug-in COM objects. The application source also includes XtdMenu.pas, which implements a TAutomatedMenu menu item descendant that
can activate a plug-in via the IEditMenuHandler interface from a menu.
I wrote two simple plug-ins to show the capabilities of what
we can do with COM objects compiled separately from our
application. The first, defined in HelloWorld.dpr, defines an
Automation object named HelloWorld.Plugin that impleFigure 7: The results of using the ProcRemark plug-in to create a procements the required interfaces (see HWPI.pas), and adds a
dure header remark and body for the HelloWorld procedure.
Hello World menu item to the Help menu. As can be expected,
the plug-in displays a dialog box with the universal message displayed
Installing the Sample Application
for programmer education worldwide.
The code provided with the application includes the source and
the compiled versions of the application and the plug-ins.
A more sophisticated plug-in is implemented in ProcRemark.dpr,
Youll need to perform the following steps to use the plug-ins
which takes advantage of the applications object model and adds two
with the application:
menu items to the Edit menu. The Remark procedure adds a three-line
1) Register the plug-in DLLs with the COM system (use
remark with the selected text between lines of asterisks, which are
regsvr32.exe in Windows \System directory. Or, if youre
located above the line of the currently selected text (see Figure 7). The
rebuilding the DLLs with Delphi, use the Run | Register |
Procedure Body menu item adds a begin..end procedure body beneath
ActiveX Server menu option. (When youre deploying an applithe selected text in the editor. A long time ago, I wrote two functions
cation that uses COM plug-ins, youll register the ActiveX
like these in Briefs extension language (Brief being the text editor creservers from within your set-up program. Most set-up develated by Underware Software and being sold for a while by Borland).
opment tools allow you to set a file attribute and declare it as
Its amazing how much more productive I became when my Pascal
a self-registering ActiveX/OCX server.)
sources were always prefixed and suffixed with the same style. The
2) RegSvr32 HelloWorld.DLL
code for the functions implementation is provided in PRPI.pas. Its
3) RegSvr32 ProcRemark.DLL
trivial, and, as such, left unexplained in this article.
4) You need to register the plug-ins with the sample application.
Start EditProj.exe and display the Plugin Manager dialog box
Conclusion
using the Tools | Plug-ins menu option (see Figure A). Use the
Todays applications require extensibility and programmability. The
Add button to add HelloWorld.Plugin and ProcRemark.Plugin
Microsoft COM foundation is an excellent way to provide scripting,
to the plug-in list. When you close the Plugin Manager dialog
Automation, and plug-in capabilities from your application. Creating
box, the new menu items will be added to the application.
an extensible application requires a well-thought-out object model that
maps the applications objects and concepts as COM objects that can
Ron Loewy
be accessed from extension modules. Plug-ins can be created by adding
support for minimal extension interfaces within the applications.
COM plug-ins can be created in different development tools, and
doesnt require recompiling the application.
The files referenced in this article are available on the Delphi Informant
Works CD located in INFORM\99\DEC\DI9912RL.
Ron Loewy is a software developer for HyperAct, Inc. He is the lead developer of
eAuthor Help, HyperActs HTML Help authoring tool. For more information about
HyperAct and eAuthor Help, contact HyperAct at (515) 987-2910 or visit
http://www.hyperact.com.
30
1stClass
A Set of First-class Delphi Components
f youre looking for a set of controls to give your applications a distinctive look and
feel, look no further than 1stClass from Woll2Woll Software. 1stClass includes Shape
Button, Image Button, Button Group, Color Combo, Color List, Tree View, DB Tree View,
Tree Combo, Font Combo, Image Form, Imager, Label, OutlookBar, and Status Bar components that offer a host of features not found in their Delphi counterparts.
If you like the look of Microsoft Outlook, the
TfcOutlookBar component will let you duplicate
it, and more. Working with the OutlookBar is
surprisingly easy. The first step is to drop the
OutlookBar on your form and set its Align
property to position the bar either vertically at
the left or right side of the form, or horizontally
at the top or bottom. Next, select the
OutlookItems property and click the
ellipsis button to
open the Collection
Editor. Using the
context menu or
toolbar in the
Collection Editor,
add one or more
pages to the
OutlookBar. The
page buttons are
actually TfcShapeBtn
components, so you
can assign a graphic
to the buttons Image
property and the
button assumes the
shape of the graphic.
Click the active page
in the OutlookBar
and select its Images
property to assign an
Figure 1: TfcImager with a JPEG photo loaded.
ImageList compo31
32
Figure 4, by using an
ImageBtn for each
area of the map.
TfcShapeBtn combines the functionality of TSpeedButton
and TBitBtn with the
ability to create buttons of virtually any
shape. Figure 5
shows the standard
shapes for the
ShapeBtn. If the
standard shapes dont
meet your needs, you
can create custom
Figure 5: The standard shapes for
shapes by defining a
TfcShapeBtn.
list of points that
describe the shape.
You can control the buttons color as well as the highlight colors
that are used for three-dimensional effects. The image and shape
buttons provide OnMouseEnter and OnMouseLeave events, which
you can use to make the button change its appearance when the
mouse cursor passes over the button.
TfcButtonGroup lets you easily create a collection of shape buttons
or image buttons. You can arrange the buttons in any number of
rows and columns, as well as adjust the spacing between adjacent
buttons. Three behaviors are available when the buttons are
clicked: The buttons can behave as a group of radio buttons, as
toggle buttons, or as click buttons. If you set the ClickStyle property to bcsRadioGroup, only one button can be depressed at a
time. Optionally, you can allow all the buttons to be up, or you
can require that one always be depressed. The bcsCheckList option
allows any combination of buttons to be depressed at one time,
simulating a group of checkboxes. Choosing bcsClick makes the
buttons behave as regular buttons so they remain depressed only
as long as you hold the mouse button down. The TfcButtonGroup
class also provides a Transparent property. When set to True, all
areas of the button group not covered by a button will be transparent. Figure 6 shows a button group with the Transparent prop-
Conclusion
1stClass is an excellent set of components for adding sophistication to the user interface of your Delphi programs. Woll2Woll
still believes in providing user manuals, and 1stClass comes with
33
Bill Todd is President of The Database Group, Inc., a database consulting and
development firm based near Phoenix. He is a Contributing Editor of Delphi
Informant, a co-author of four database programming books and over 60 articles, and a member of Team Borland, providing technical support on the Borland
Internet newsgroups. He is a frequent speaker at Borland Developer Conferences
in the US and Europe. Bill is also a nationally known trainer and has taught
Paradox and Delphi programming classes across the country and overseas. He
was an instructor on the 1995, 1996, and 1997 Borland/Softbite Delphi World
Tours. He can be reached at [email protected].
File | New
Directions / Commentary
ow that Delphi 5 has shipped, lets put the Delphi 4 books into perspective. Ill also include some classics books
that have been around (in some cases since Delphi 2) that are still worth adding to your collection. Many of these
continue to be relevant, although, of course, none will cover the new features specific to Delphi 5.
The classics. We can usually expect much from a book that has
been published in several editions. It generally indicates a high level
of quality and strong confidence on the part of the publisher and
the audience. Ill examine three such enduring classics, one of which
I reviewed in these pages. Of course, all three emphasize Delphi 4s
new features; all three are comprehensive, covering the major aspects
of Delphi and its underlying language, Object Pascal; and all three
deal with the major Delphi disciplines, including component writing, database programming, and the IDE. However, each has its particular emphasis and strength.
Among them, Mastering Delphi 4 by Marco Cant [SYBEX, 1998]
continues to be the best entry-level work. In fact, I still refer to it
from time to time when I decide to explore an area of Delphi thats
new to me. Always an innovator, Cant has come up with what I
consider to be an effective new approach to publishing: Rather than
include all the books code on a CD-ROM, he has made it available
at his Web site. I wonder if this will become a trend in Delphi publishing; its certainly worth imitating.
Charlie Calverts Delphi 4 Unleashed [SAMS, 1998] is an update of a
Delphi classic. It didnt appear in a Delphi 3 version, but is back for
version 4. A great deal has changed since the Delphi 2 version, so
much so that calling this an update is really not fair. Gone is the introduction to Object Pascal that began the earlier edition. In its place are
several chapters designed to introduce the reader to Delphi programming topics such as program design, Delphis IDE, and enhancements in version 4. However, the excellent discussions of component
writing and multimedia remain in the new edition, along with updated
database topics. In particular, this book really shines in its treatment of
the new technologies MIDAS, CORBA, and COM/DCOM.
I reviewed Borland Delphi 4 Developers Guide by Steve Teixeira and
Xavier Pacheco [SAMS, 1998]. Although I pointed out that one of
the great strengths of this work is its collection of tips, the value of
this book goes much further. It begins with an excellent chapter on
Object Pascal. As with the other two classics mentioned, it provides an
overview of the extensions to the language introduced in Delphi 4. I
found the chapters on dynamic link libraries and multithreading particularly excellent. It also covers component-writing topics, including
component editors and packages. A large part of this work deals with
database programming, going well beyond the usual topics, and continues generally to be the most advanced of the three titles.
New works. As with Delphi 3, there have been some new specialized and general works that have appeared in the last year. One spe-
34