June 2001, Volume 7, Number 6: On The Cover
June 2001, Volume 7, Number 6: On The Cover
ON THE COVER
5 The API Calls 31 Distributed Delphi
Working with Files and Folders — Bill Todd IP Helper API: Part I — John C. Penman
Oddly, Delphi offers insufficient help when it comes to manipulating John Penman explains the basics of the IP Helper API, and then dives
files and folders. But there’s no need to resort to API calls — at least into the specifics of how to retrieve IP configuration details for any
not directly — since Bill Todd has wrapped them up so nicely. adapter on a local machine. It’s the first of a two-article series.
FEATURES
37 At Your Fingertips
13 On Language Kylix Tips Redux — Bruno Sonnino
Compiler Directives — Cary Jensen, Ph.D. Bruno Sonnino revisits some Delphi tips of columns past — rotating
Cary Jensen describes the directives that allow you to control the text, and detecting drives and obtaining information about them —
compiler. From renaming file extensions, to conditionals to handle and gives them a Kylix gloss. One is cross-platform, two are not.
multiple OSes, make sure you’re getting the most from Delphi.
17 Sound+Vision REVIEW
Getting Started with OpenGL: Part II — Eli Bar-Yosef
Eli Bar-Yosef wraps up his series on 3D programming with OpenGL. 41 GExperts
This month he illustrates techniques to create the illusion of lighting, Product Review by Clay Shannon
materials, and texture — and shares a working demo.
26 On the ’Net
SAX Generation of XML — Keith Wood
Keith Wood introduces the latest Microsoft XML package (MSXML v3)
and its support for SAX generation via its IMXWriter interface, then
demonstrates its use by generating XML from a database.
1 June 2001 Delphi Informant Magazine
Delphi /Xpress Press/ and SecureWave Announce SecureStack 1.0
/Xpress Press/ and SecureWave
T O O L S announced the release of
SecureStack 1.0, a solution to pro-
New Products tect Windows NT/2000 systems
and Solutions from buffer overflow attacks.
Buffer overflow attacks are one
of the biggest security threats
on the Internet. A survey pub-
lished in Information Security
magazine found that 24 percent
of all US companies suffered a
“buffer overflow” attack in the
year 2000. The article is available
at http://www.securewave.com/
html/sec_stack_1.html.
A buffer overflow attack occurs
Book Picks when a hacker overflows an
input buffer on the execution
stack with more data than
the application was designed to
Oracle Net8
Hugo Toledo and Jonathan Gennick accept. Buffer overflow attacks
O’Reilly exploit the lack of bounds check-
ing on the size of input being
stored in a buffer. The attack
data has three elements: An
attacker determines the amount is executed. A buffer overflow dows 2000 with any existing
of data necessary to generate an allows the attacker to take full and future service packs and for
overflow condition; provides an control of your system. hardware platforms with Intel
input string that is executable, SecureStack protects user-mode Pentium, MMX, Pentium Pro,
binary code that will be used applications (including services), Pentium II, Pentium III, or Pen-
to run additional commands but not kernel-mode drivers; is tium IV processors.
ISBN: 1-56592-753-2 on the system; the buffer independent of the server appli-
Cover Price: US$39.95 overflow changes the return cations; supports uni-processor SecureWave
(387 pages)
address to point to the exploit/ machines; and protects from Price: SecureStack Professional, US$249.
malicious code. When the func- remote buffer overflow attacks Contact: sales@securewave.com
tion returns, the exploit code for Windows NT 4.0 and Win- Web Site: http://www.securewave.com
Red Hat Linux 7 Unleashed Extended Systems Releases Advantage Database Server 6.0
Bill Ball, David Pitts, et al.
SAMS Extended Systems is shipping Database Server allows develop- enhanced database security and
Advantage Database Server 6.0, ers the flexibility to combine encryption functionality; and
a high-performance relational SQL statements and relational native SQL in both Advantage
database server (RDBMS). data access methods with the Database Server and Advantage
Enhancements to Advantage performance and control of nav- Local Server.
Database Server 6.0 include igational commands. Advantage Advantage TDataSet
complete referential integrity has native development inter- Descendant replaces the BDE
support, including primary/ faces, optimized data access for easy deployment and fea-
foreign key definition and cas- methodology, and provides secu- tures native components par-
caded updates and deletes. The rity, stability, and data integrity allel to TTable, TQuery, and
ISBN: 0-672-31985-3 Advantage Extended Procedures with no database administrator. TStoredProc. Advantage OLE
Cover Price: US$49.99
(1,112 pages, 2 CD-ROMs)
provide easy-to-develop stored Advantage Database Server 6.0 DB Provider is available for
procedures that are executed at features include free application ADO database access; Advan-
the server. The Advantage Data development tools (TDataSet tage Database Server supports
Architect allows developers to Descendant, OLE DB Provider, Windows 95/98/ME, Windows
import various table types such as ODBC Driver); the capability NT/2000, and Novell Net-
Access, InterBase, Paradox, and of scaling from local to peer- Ware. Thirty-day evaluation
SQL Server to Advantage-com- to-peer to client/server environ- copies are available.
patible tables. ments with no code changes;
Advantage Database Server is royalty-free distribution for local Extended Systems
a high-performance client/server and peer-to-peer environments; Price: Two-user license, US$249;
RDBMS for stand-alone, net- full server-based transaction unlimited-user license, US$7,495.
worked, Internet, and mobile processing; field-level and Contact: (800) 235-7576, ext. 5030
database applications. Advantage record-level constraint support; Web Site: http://www.extendedsystems.com
ISBN: 1-861004-48-6
HTML Shrinker Pro 2.0 Reduces Web or WAP Site Size
Cover Price: US$59.99 Harald Heim Software PL, and PHP. It shrinks plain users to select single files or to
(1,391pages) announced the release of HTML HTML, WML and XML code, shrink an entire Web site with one
Shrinker Pro 2.0, a tool for and also Javascript, PHP, Perl, click. Users can replace the orig-
reducing the size of various Web, WMLscript, and VBScript code. inal files with those of reduced
Windows 2000 Quick Fixes WAP, and script files, to speed Comments from different size while making backups, or
Jim Boyce Web or WAP site loading, reduce HTML editors such as Front- can shrink files to a destination
O’Reilly
Web space and bandwidth, and Page, Dreamweaver, PageMill, folder while the original files stay
consequently save Web hosting and SourceSafe are supported untouched. Users can also shrink
fees. and SSI commands are left files or whole folder trees directly
HTML Shrinker Pro 2.0 untouched. HTML Shrinker 2.0 from the Windows Explorer by
can reduce the size of various shrinks files up to 50 percent using the context menu.
file types, e.g. HTML, WML, and allows them to be restored at HTML Shrinker Pro 2.0
WMLS, SHTML, JHTML, any time. also allows users to clean up
XML, CFML, JS, CSS, ASP, HTML Shrinker Pro 2.0 allows HTML documents by removing
all images, scripts, styles, NB
ISBN: 0-596-00017-0 spaces, or font tags to make them
Cover Price: US$29.95 slim for archiving or transfer to
(285 pages) pocket computers.
HTML Shrinker Pro 2.0 is
available for Windows 95, 98,
Me, NT, and 2000. A feature-
limited Light Version of HTML
Shrinker is available as freeware.
By Bill Todd
C reating, moving, copying, renaming, deleting, and searching folders are common
tasks, yet Delphi provides no components to make these jobs easy. Instead, you
have to resort to the Windows API for most of these functions. In this article, you’ll
create two components. The first, TdgFileOperation, will let you copy, move, delete,
rename, or create entire directory trees. The second, TdgFileFinder, will search any
directory tree for the file(s) you specify. It allows you to attach event handlers to
events that fire before and after each directory is scanned, and when a requested
file is found.
Delphi does provide some run-time library rou- The first member of this structure is the window
tines for working with folders; these routines are handle used to display information about the prog-
shown in Figure 1. However, these routines are ress of the operation. The second parameter is the
limited in several ways. First, there’s no function operation to perform, specified by one of these con-
to copy a folder or files within a folder. Second, stants: FO_COPY, FO_MOVE, FO_DELETE, or
there’s no way to move a folder. Third, the routines FO_RENAME. The third member, pFrom, is a
that let you delete a folder require the folder to be pointer to a null-terminated string that identifies
empty. You can overcome all these limitations — the source folder or files on which to operate. The
and more — by using the Windows API. next member, pTo, is a pointer to a null-terminated
string that contains the destination folder or file.
TdgFileOperation The fFlags member is used to set options that con-
The most powerful and flexible tool in the Win- trol the operation, and is discussed in more detail
dows API for manipulating files and folders is the later in this article. The next member of the struc-
ShFileOperation function. ShFileOperation takes a ture, fAnyOperationsAborted, is set to True if the
single parameter of type LPSHFILEOPSTRUCT, user cancels the operation. The hNameMappings
that is, a pointer to SHFILEOPSTRUCT. Figure member is a handle to a file-name mapping object
2 shows the C-language declaration for that lets you specify the old and new names of indi-
SHFILEOPSTRUCT. vidual files on which to operate. The final structure
member, lpszProgressTitle, contains the title string to
Method Description
be displayed in the progress dialog box.
ChDir Changes the current directory
CreateDir Creates a subdirectory ShFileOperation is much more powerful than it
DirectoryExists Returns True if the directory exists
may appear on first examination. If you specify
ForceDirectories Creates a directory tree
GetCurrentDir Returns the current directory
a path to a folder in pFrom and pTo and
GetDir Returns the current directory for a specified choose FO_COPY as the operation, the source
drive folder and all the files and folders it contains
MkDir Creates a subdirectory will be copied to the destination folder. The
RemoveDir Deletes an empty subdirectory same thing happens if you choose FO_MOVE;
RmDir Deletes an empty subdirectory the entire folder tree will be moved. Choosing
SetCurrentDir Changes the current directory FO_DELETE deletes the entire source folder
Figure 1: Delphi’s folder manipulation routines. tree and all the files it contains.
The FilesOnly property lets you specify that operations on files using
wildcards will affect only files and not folders. ProgressTitle lets you Figure 4: The DoFileOp method.
specify a title for the progress dialog box. If RenameOnCollision is
True, any file whose name conflicts with the name of an existing file and TargetFolder contain null-separated lists of file names, the list
in the target folder will be renamed. SourceFolder and TargetFolder must end with two nulls. AllocMem not only allocates the required
contain the names of the folders or files on which to operate. memory, but also fills it with nulls so the nulls at the end
will be there. If you need to use the ability to have multiple
Since all the operations supported by ShFileOperation use the same file names separated by nulls in the pFrom and pTo members
parameters (with the exception of the function to be performed), the of SHFILEOPSTRUCT, you’ll need to change the SourceFolder
TFileOperation component needs just one method, DoFileOp, to make and TargetFolder properties from the string to TStrings, create a
the call to ShFileOperation. Figure 4 shows the code for DoFileOp. StringList to hold the value of each property, and write a method
to convert the contents of the StringList to a null-delimited, double-
DoFileOp takes a single parameter which can have a value null-terminated PChar. Since I’ve never needed to use lists of explicit
of FO_COPY, FO_MOVE, FO_DELETE, or FO_RENAME. file names, I didn’t implement the capability in this component.
DoFileOp is a function that returns False if the user cancels
the operation and True in all other cases. The code begins by The next step is to set the Flags variable in which each bit corresponds
converting the string properties ProgressTitle, SourceFolder, and to one of the flag options. The FOF_NOCONFIRMATION flag
TargetFolder to null-terminated strings. AllocMem is called to allo- is always included to suppress any dialog boxes asking the user to
cate memory for the three PChar variables. In each case, the amount confirm the file operation. The following if statements set the flags
of memory allocated is equal to the length of the string plus two that correspond to the component’s Boolean properties. Once the
bytes. The additional two bytes are required because if SourceFolder Flags variable is set, all members of the SHFILEOPSTRUCT record
Figure 5: The FileOp demonstration program’s main form. Figure 7: TdgFileFinder properties.
The BeforeDirectoryScan event is fired before scanning the files in procedure TdgFileFinder.RaiseWindowsError(
a directory. The path, file name, and short name of the directory ErrorCode: DWORD);
are passed as parameters with the var parameter ContinueSearch. begin
if ErrorCode <> ERROR_NO_MORE_FILES then
AfterDirectoryScan is triggered after all the files in a directory have raise EFileFinderError.Create(GetWindowsErrorMsg(
been scanned. The parameters are the path, file name of the direc- ErrorCode) + ' (' + IntToStr(ErrorCode) + ')');
tory, short name, creation date of the most recently created file, end;
access date of the most recently accessed file, update date of the most
recently updated file, total size of all the files that met the search
Figure 11: RaiseWindowsError and GetWindowsErrorMsg.
criteria, and ContinueSearch.
Figure 7 shows the component’s properties. The first property, calling Application.ProcessMessages, and then checks to see if the
CancelScan, is a run-time property that’s set to False by default. If CancelScan property has been set to True. This allows you to add
it’s set to True while a file search is in progress, the search will termi- a Cancel button to your application so users can stop the directory
nate at the beginning of the next directory. IncludeSystemFiles is a scan. The next block of code checks the path to see if it’s the root
published property that’s False by default. Set this property to True directory. If so, the long and short names are set to backslash. If not,
to include files that have hidden and system attributes. The last FindFirst is called with its attribute parameter set to faDirectory to
three properties are the event properties for the AfterDirectoryScan, find the record for this directory.
BeforeDirectoryScan, and OnFileFound events.
When you call FindFirst or FindNext, the information about the
Figure 8 shows TdgFileFinder’s only public method, FindFiles. file or directory is returned in the TSearchRec variable passed as
FindFiles sets the CancelScan property to False, and then checks the the third parameter. Figure 9 shows the declaration of TSearchRec.
Path parameter to see if it’s an empty string. If Path is empty, the After the call to FindFirst, the DirLongName and DirShortName
call to GetDir sets the variable SearchPath to the current directory, variables are set from the cFileName and cAlternateFileName mem-
otherwise a call to DirectoryExists verifies that the directory in Path bers of TSearchRec.
exists. If the directory exists, it’s assigned to SearchPath, otherwise
an exception is raised. If SearchPath doesn’t end with a backslash, Next, the BeforeDirectoryScan event is fired, and the value of
one is added to the end of the string. Finally, if the Mask parameter ContinueSearch is checked. If the event handler has set
is a null string, ScanDirectory is called with a mask of *.*, otherwise ContinueSearch to False, Result is set to False and the method
ScanDirectory is called with its second parameter set to Mask. The exits. Next, FindFirst is called with the Path and Mask to find the
third parameter of FindFiles is passed as the third parameter to first file that matches the Mask. Then, a while loop begins that
ScanDirectory. If this parameter, ScanSubdirectories, is True, the continues as long as the variable SearchResult is zero; SearchResult
entire directory tree below the directory in the Path parameter will is the value returned by FindFirst and FindNext which indicates
be scanned. If ScanSubdirectories is False, only the directory passed that a file was found. If the IncludeSystemFiles property is False,
in Path will be searched. and the file has the hidden and system attributes, FindNext is
called followed by Continue to jump back to the beginning of
Listing Two (beginning on page 10) contains the ScanDirectory the while loop. Now that a file that matches the search criteria
method that’s the heart of the component. ScanDirectory starts by has been found, the name and date values are extracted from the
Figure 12 shows the main form of a simple test program for this
component. Type in a starting path, click Scan, and information
about all the files and folders in the directory tree starting at the path
Figure 12: TdgFileFinder demonstration application. you entered will be loaded into two ClientDataSets by code in the
OnFileFound and AfterDirectoryScan event handlers. The complete
TSearchRec, and the attribute string is constructed from the bit listing of the TdgFileFinder component is not included in this article
flags in the dwFileAttributes member of TSearchRec. Now, the total due to its length. To see it, just download the files that accompany
variables for the directory are updated and the OnFileFound event this article (see end of article for details).
is fired. Finally, FindNext is called to locate the next file.
Conclusion
ScanDirectory uses two custom methods. The first is Searching, copying, moving, deleting, renaming, and creating
Win32FileTimeToDateTime, which is shown in Figure 10. This folders can all be done in Delphi with enough code and some
function calls the Windows API functions FileTimeToLocal- Windows API calls. However, the beauty of object-oriented pro-
FileTime to convert the file time from UTC time to local gramming is that it allows you to write code once, and create a
time, and FileTimeToSystemTime to convert the time from the couple of easy-to-use components that should meet your file and
file time format to the system time format. Finally, the Delphi folder manipulation needs for all of your applications. ∆
SystemTimeToDateTime function converts the value to
TDateTime format. The two sample projects referenced in this article are available on the
Delphi Informant Magazine Complete Works CD located in INFORM\
The second custom method is RaiseWindowsError, shown in 2001\JUN\DI200106BT.
Figure 11. RaiseWindowsError checks the error code to see if
it’s ERROR_NO_MORE_FILES; if it is, no exception is raised.
Otherwise, an EFileFinderException is raised that displays the
Windows error message and error code. The Windows error mes-
sage is retrieved by a call to GetWindowsErrorMsg, also shown
in Figure 11. This function calls the Windows API function Bill Todd is president of The Database Group, Inc., a database consulting and
FormatMessage to get the error message that corresponds to the development firm based near Phoenix. He is co-author of four database program-
error code. While you can get along without these functions by ming books and over 80 articles, and is a member of Team Borland, providing
calling RaiseLastWin32Error when FindFirst or FindNext returns technical support on the Borland Internet newsgroups. He is a frequent speaker
a value other than zero or ERROR_NO_MORE_FILES, using at Borland Developer Conferences in the US and Europe. Bill is also a nationally
these function makes life easier when you write your application known trainer and has taught Delphi programming classes across the country and
because you know that any exception raised by this component overseas. Bill can be reached at bill@dbginc.com.
will be an EFileFinderError exception.
begin
Begin Listing One — TFileOperation component Result := DoFileOp(FO_DELETE);
end;
unit FileOperation;
function TdgFileOperation.DoFileOp(FileOp: UINT): Boolean;
interface
var
SHFileOpStruct: TSHFileOpStruct;
uses
Flags: FILEOP_FLAGS;
Windows, Messages, SysUtils, Classes, Graphics, Controls,
SourceDir: PChar;
Forms, Dialogs;
TargetDir: PChar;
ProgressTitle: PChar;
type
begin
TdgFileOperation = class(TComponent)
Result := False;
private
ProgressTitle := AllocMem(Length(FProgressTitle) + 2);
FSourceFolder: string;
try
FTargetFolder: string;
SourceDir := AllocMem(Length(FSourceFolder) + 2);
FShowProgress: Boolean;
try
FProgressTitle: string;
TargetDir := AllocMem(Length(FTargetFolder) + 2);
FFilesOnly: Boolean;
try
FRenameOnCollision: Boolean;
StrCopy(ProgressTitle, PChar(FProgressTitle));
{ Private declarations. }
StrCopy(SourceDir, PChar(FSourceFolder));
function DoFileOp(FileOp: UINT): Boolean;
StrCopy(TargetDir, PChar(FTargetFolder));
protected
Flags := FOF_NOCONFIRMATION;
{ Protected declarations. }
if FRenameOnCollision then
public
Flags := Flags or FOF_RENAMEONCOLLISION;
{ Public declarations. }
if FFilesOnly then
constructor Create(AOwner: TComponent); override;
Flags := Flags or FOF_FILESONLY;
function DoCopy: Boolean;
if not FShowProgress then
function DoCreateFolder: Boolean;
Flags := Flags or FOF_SILENT
function DoDelete: Boolean;
else
function DoMove: Boolean;
Flags := FOF_SIMPLEPROGRESS;
function DoRename: Boolean;
with SHFileOpStruct do begin
published
Wnd := Application.Handle;
{ Published declarations. }
wFunc := FileOp;
property FilesOnly: Boolean read FFilesOnly
pFrom := SourceDir;
write FFilesOnly default False;
pTo := TargetDir;
property ProgressTitle: string
fFlags := Flags;
read FProgressTitle write FProgressTitle;
fAnyOperationsAborted := False;
property RenameOnCollision: Boolean
hNameMappings := nil;
read FRenameOnCollision write FRenameOnCollision
lpszProgressTitle := ProgressTitle;
default True;
if SHFileOperation(SHFileOpStruct) <> 0 then
property ShowProgress: Boolean
RaiseLastWin32Error;
read FShowProgress write FShowProgress
Result := fAnyOperationsAborted;
default True;
end;
property SourceFolder: string
finally
read FSourceFolder write FSourceFolder;
FreeMem(TargetDir, Length(FTargetFolder) + 2);
property TargetFolder: string
end;
read FTargetFolder write FTargetFolder;
finally
end;
FreeMem(SourceDir, Length(FSourceFolder) + 2);
end;
procedure Register;
finally
FreeMem(ProgressTitle, Length(FProgressTitle) + 2);
implementation
end;
end;
uses ShellApi, FileCtrl;
function TdgFileOperation.DoMove: Boolean;
procedure Register;
begin
begin
Result := DoFileOp(FO_MOVE);
RegisterComponents('DI', [TdgFileOperation]);
end;
end;
function TdgFileOperation.DoRename: Boolean;
{ TFileOperation. }
begin
constructor TdgFileOperation.Create(AOwner: TComponent);
Result := DoFileOp(FO_RENAME);
begin
end;
inherited;
FShowProgress := True;
end.
FFilesOnly := False;
FRenameOnCollision := True;
end;
End Listing One
function TdgFileOperation.DoCopy: Boolean;
begin
Result := DoFileOp(FO_COPY);
Begin Listing Two — ScanDirectory method
end; function TdgFileFinder.ScanDirectory(var Path: string;
const Mask: string; const ScanSubdirectories: Boolean):
function TdgFileOperation.DoCreateFolder: Boolean; Boolean;
begin { Called by FindFiles to scan the directory tree. This
Result := ForceDirectories(FSourceFolder); method calls itself recursively. Parameters:
end; Path: The path to the directory to scan.
Mask: The mask for the files to be found.
function TdgFileOperation.DoDelete: Boolean; ScanSubdirectories: True to scan entire directory tree.
{ end; // with
TSearchRec = record { Fire the BeforeDirectoryScan event. }
Time: Integer; ContinueSearch := True;
Size: Integer; if Assigned(FBeforeDirectoryScan) then begin
Attr: Integer; FBeforeDirectoryScan(Path, DirLongName,
Name: TFileName; DirShortName, ContinueSearch);
ExcludeAttr: Integer; end; // if
FindHandle: THandle; if not ContinueSearch then begin
FindData: TWin32FindData; Result := False;
end; FindClose(SRec);
Exit;
TWin32FindData = packed record end; // if
dwFileAttributes: Integer; { Save the Path length }
ftCreationTime: Int64; PathLength := Length(Path);
ftLastAccessTime: Int64; { Search for all files in the current directory. }
ftLastWriteTime: Int64; SearchResult := FindFirst(Path + Mask, faAnyFile, SRec);
nFileSizeHigh: Integer; if SearchResult <> 0 then
nFileSizeLow: Integer; RaiseWindowsError(SearchResult);
dwReserved0: Integer; try
dwReserved1: Integer; while SearchResult = 0 do begin
cFileName: array[0..259] of Char; { If the found file is not a directory or volume
cAlternateFileName: array[0..13] of Char; label, process it. }
end; if (SRec.Attr and
} (faDirectory or faVolumeID)) = 0 then
var begin
SRec: TSearchRec; with SRec.FindData do begin
SearchResult: Integer; { If IncludeSystemFiles is False and this is a
PathLength: Integer; system file, skip it. }
ContinueSearch: Boolean; if (not FIncludeSystemFiles) and
{ File information. } (dwFileAttributes and
LongName, FILE_ATTRIBUTE_HIDDEN <> 0) and
ShortName: string; (dwFileAttributes and
AttributeStr: string; FILE_ATTRIBUTE_SYSTEM <> 0) then
Created, begin
Accessed, SearchResult := FindNext(SRec);
Updated: TDateTime; if SearchResult <> 0 then
FileSize: Int64; RaiseWindowsError(SearchResult);
{ Directory information. } Continue;
DirLongName, end;
DirShortName: string; { Process this file. }
DirLastCreated, LongName := StrPas(cFileName);
DirLastAccessed, ShortName := StrPas(cAlternateFileName);
DirLastUpdated: TDateTime; if ShortName = '' then
DirSize: Int64; ShortName := LongName;
begin Created :=
Result := True; Win32FileTimeToDateTime(ftCreationTime);
DirSize := 0; Accessed :=
DirLastCreated := 0; Win32FileTimeToDateTime(ftLastAccessTime);
DirLastAccessed := 0; Updated :=
DirLastUpdated := 0; Win32FileTimeToDateTime(ftLastWriteTime);
{ See if the user wants to cancel the scan. } FileSize :=
Application.ProcessMessages; (nFileSizeHigh * MAXINT) + nFileSizeLow;
if FCancelScan then Exit; { Build the string of attibutes. }
{ Get the record for this directory. } AttributeStr := '';
with SRec.FindData do begin if dwFileAttributes and
if ((ExtractFileDrive(Path) <> '') and FILE_ATTRIBUTE_NORMAL = 0 then begin
(Length(Path) = 3)) or (Path = '\') then if dwFileAttributes and
begin FILE_ATTRIBUTE_ARCHIVE <> 0 then
DirLongName := '\'; AttributeStr := AttributeStr + 'A';
DirShortName := '\'; if dwFileAttributes and
end FILE_ATTRIBUTE_COMPRESSED <> 0 then
else AttributeStr := AttributeStr + 'C';
begin if dwFileAttributes and
SearchResult := FindFirst(Copy(Path, 1, FILE_ATTRIBUTE_HIDDEN <> 0 then
Length(Path) - 1), faDirectory, SRec); AttributeStr := AttributeStr + 'H';
try if dwFileAttributes and
if SearchResult <> 0 then FILE_ATTRIBUTE_SYSTEM <> 0 then
RaiseWindowsError(SearchResult); AttributeStr := AttributeStr + 'S';
DirLongName := StrPas(cFileName); if dwFileAttributes and
DirShortName := StrPas(cAlternateFileName); FILE_ATTRIBUTE_OFFLINE <> 0 then
finally AttributeStr := AttributeStr + 'O';
FindClose(SRec); if dwFileAttributes and
end; // try FILE_ATTRIBUTE_READONLY <> 0 then
end; // if AttributeStr := AttributeStr + 'R';
if dwFileAttributes and
FILE_ATTRIBUTE_TEMPORARY <> 0 then
AttributeStr := AttributeStr + 'T';
end; // if
Compiler Directives
Controlling the Delphi Compiler
T hey look similar to comments, but they aren’t. And nearly every project has at
least one, even the default project Delphi creates when you first load it. They are
compiler directives, and they are the focus of this month’s article.
You use compiler directives to control how Delphi’s turned on when the directive is followed by a
compiler performs its task. For example, the {$R} plus sign (+) or the word ON, and turned off
compiler directive instructs the compiler to link a when it is followed by a minus sign (-) or the
specified .res file into the resulting executable. By word OFF. In the case of the {$H} compiler direc-
default, every unit associated with a form includes at tive, it instructs the compiler to treat all string
least one {$R} compiler directive, which tells Delphi declarations as ANSIStrings when turned on, and
to include a binary version of the form’s .dfm file in as Pascal-style strings when turned off.
the compiled .dcu file as a Windows resource.
Parameter directives identify a file name, file
You can control compiler directives in one of two extension, or setting required by the compiler.
ways. The first way is to use the checkboxes on the The {$R *.RES} resource compiler directive is
Compiler page of the Project Options dialog box, as one example of a parameter directive. Condi-
shown in Figure 1. To see this page, select Project | tional directives permit you to instruct the com-
Options from Delphi’s main menu, and then select the piler to conditionally compile a segment of code.
Compiler tab. The {$IFDEF} directive is a conditional directive.
The second way is to embed compiler directives Compiler directives can also be categorized
directly in your code. Embedded compiler directives by their scope, which is either local or
look a lot like braced global. Global scope directives apply to the
comments. The only dif- compilation of the entire project. For example,
ference is that compiler the {$APPTYPE CONSOLE} directive instructs the
directives have a dollar sign compiler to build a console application. Local
($) immediately following scope directives, by comparison, apply to only
the opening brace. Like part of the code being compiled. For instance, a
all other statements inter- specific section of code can be compiled with the
preted by the compiler, {$H-} directive, instructing the compiler to treat
however, if the {$ com- all declared string variables as short strings in
bination appears within a that section, and with {$H+} for the remainder
comment, it’s ignored. of the project.
There are three types Every project has a default set of compiler direc-
of compiler directives: tives, defined by the values that appear on the
switch, parameter, and Compiler page of the Project Options dialog box.
conditional. Switch direc- If you like, you can instruct Delphi to explicitly
tives turn a particular fea- insert the current compiler directives into a unit.
Figure 1: Many of Delphi’s compiler options can be ture on or off. The {$H+} To do this, press COO (oh-oh, not zero-zero).
controlled using the Compiler page of the Project directive is a switch direc- An example of a unit where COO has been
Options dialog box. tive. Switch directives are pressed is shown in Figure 2.
{$HINTS OFF}
function LaunchApp(const AppName: string): Boolean;
var
ProcessInfo: TProcessInformation;
StartUpInfo: TStartupInfo;
begin
Result := False;
try
FillMemory(@StartupInfo, SizeOf(StartupInfo), 0);
StartupInfo.cb := SizeOf(StartUpInfo);
Result := CreateProcess(nil, PChar(AppName), nil, nil,
False, NORMAL_PRIORITY_CLASS, nil, nil, StartUpInfo,
ProcessInfo);
finally
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
end;
Figure 2: Press COO in the editor to insert your project’s end;
{$HINTS ON}
default compiler directives at the top of the current unit.
As you inspect Figure 2, you will notice that the first compiler
directive includes a large number of switches. When you insert Figure 3: Turning compiler hints off for one method.
switch compiler directives manually, you can enter each separately,
each contained in its own set of braces, or include a comma-
separated list of the switch directives in a single set of braces.
Sometimes these generated hints and warnings are annoying. For the function sets the Result variable to False, the compiler will generate
example, you may have a function that really does return a value in a hint saying that the return value of the function may not be set. Since
every case, but the compiler cannot detect this. In these cases, you you know that the function always returns a value, you can turn off this
can turn hints and/or warnings off. annoying hint using the {$HINTS} compiler directive.
The {$HINTS} and {$WARNINGS} compiler directives are local switch Automatically Compiling Resource Files
directives. To turn hints off use: Until Delphi 4, the use of resource scripts to compile Windows
resource files required that you compile the resource scripts
{$HINTS OFF} manually using the Borland Resource Command-line Compiler
(BRCC32.EXE). Unfortunately, the manual nature of this compila-
To turn them back on use: tion made it possible for you to change a resource script, forget to
recompile it, and then link the old resource file into your application
{$HINTS ON} using the {$R} resource compiler directive.
For warnings use: A new version of the resource compiler directive was introduced in
Delphi 4. This version is shown in the following example, which
{$WARNINGS OFF} must be placed in your project’s .dpr file:
{$WARNINGS ON} When the Delphi compiler encounters this compiler directive, it
performs a number of valuable tasks. First it checks to see if the
Consider the function shown in the code segment in Figure 3. It named .res file exists. If it doesn’t, it automatically compiles the
simplifies the launching of an executable. Even though the first line of named .rc file to create the .res file. If the .res file already exists, the
Controlling Optimizations The {$IFDEF} and {$IFNDEF} compiler directives can also be followed
Delphi’s compiler performs optimizations that produce significant by an {$ELSE} directive (but only before the corresponding {$ENDIF}).
increases in application performance. Sometimes, however, these This directive supplies the same if..then..else logic that you know from
optimizations can reduce your ability to debug your application. For Object Pascal. If the symbols are defined for an {$IFDEF}, the state-
example, it’s not uncommon for the value of many of your variables ments following {$IFDEF} are compiled and those following the nested
to be unavailable to the integrated debugger due to optimizations. {$ELSE} are not. The opposite is true concerning {$IFNDEF}. Figure 5
This can be annoying if these values are crucial to your debugging shows a pseudo-code segment that demonstrates the use of {$DEFINE}
process. (Note: Even when compiler optimizations are turned off, and {$IFDEF}.
some variable values can be unavailable to the integrated debugger.)
As the code appears here, clicking the button that uses this OnClick
The optimization compiler directive is a local directive. To turn event handler will result in the display of the message “TIniFile.”
optimizations off, use: If you comment out the {$DEFINE} directive and run it again, the
message will display “TRegistry.”
{$OPTIMIZATIONS OFF}
Standard Conditional Symbols
To turn them back on again, use: Delphi has a number of standard conditional symbols that it
defines, depending on the version of Delphi you’re running, and
{$OPTIMIZATIONS ON} under which operating system. You can use these symbols in the
var counted. They are also very nice to use in most situations. How-
Form1: TForm1; ever, there are certain circumstances where you might want the
string type to refer to the original Pascal-style strings, which
{$DEFINE SPAN}
are arrays of Char of up to 255 characters in length. One such
{$IFDEF ENGL} situation is when you have written an application that you must
{$INCLUDE 'englstrs.txt'} continue to compile in both 16-bit and 32-bit versions of Delphi,
{$ELSE}
and you want to treat your strings the same regardless of the
{$IFDEF SPAN}
{$INCLUDE 'spanstrs.txt'} version. (Granted, this situation was more common a few years
{$ENDIF} ago, but there are some Delphi developers who must support both
{$ENDIF} 16-bit and 32-bit applications.)
implementation
... While the individual characters in both ANSIStrings and Pascal
strings can be indexed, only the Pascal-style strings include a zero-
Figure 7: This code shows a resourcestring block. order byte, the Ord value of this character indicating the length of
the string. ANSIStrings don’t have a zero-order byte. If you have
{$IFDEF} and {&IFNDEF} compiler directives without using a cor- an application that expects a string to include a zero-order byte,
responding {$DEFINE} directive. Figure 6 shows a table of the regardless of which version of Delphi is compiling the application,
most common of these conditional symbols, and the version of you can use the {$H OFF} compiler directive.
Delphi with which they’re compiled.
Note, however, that while Delphi 2 and later recognize the {$H}
Delphi 6 and Kylix each will include one new standard condi- directive, Delphi 1.0 (the 16-bit version) does not. Consequently, the
tional symbol. When compiling a project using Kylix, the LINUX {$H} compiler directive has to be made conditional, thus:
symbol will be defined. By comparison, when compiling with
Delphi 6 and later, the WINDOWS symbol will be defined. These {$IFDEF WIN32}
defines will be valuable when you create projects that you want to {$H OFF}
{$ENDIF}
be able to compile for both Linux and Windows.
resourcestring
sMainFormCaption = 'Example Project';
sOKButtonCaption = '&OK';
sCancelButtonCaption = '&Cancel';
sFileExit = '&Exit';
sFileOpen = '&Open'
sFileClose = '&Close';
sFileReport = 'Re&port';
// More resourcestring declarations...
After processing the {$INCLUDE} compiler directive, the contents of Cary Jensen is president of Jensen Data Systems, Inc., a Houston-based database
the appropriate .txt file are compiled along with all other code within development company. Cary is co-author of 17 books, including Oracle JDeveloper
the unit. [Oracle Press, 1998], JBuilder Essentials [Osborne/McGraw-Hill, 1998], and
Delphi in Depth [Osborne/McGraw-Hill, 1996]. He is a Contributing Editor of
Using Short Strings by Default Delphi Informant Magazine, and an internationally respected trainer of Delphi and
Delphi’s ANSIString type is the default type for 32-bit applica- Java. For more information, visit http://www.jensendatasystems.com, or e-mail
tions. These strings are large, dynamically allocated, and reference Cary at cjensen@compuserve.com.
By Eli Bar-Yosef
The OpenGL lighting model is divided into three To create light sources, use the following function:
lighting types: ambient, diffuse, and specular. Each is
computed separately, then combined. Ambient light procedure glLightf*(light: GLenum,
is light that has bounced and spread in so many pname: GLenum, param: Glfloat)
This call should name each of the textured objects with a unique
name. The first parameter takes the number of textured objects you
want to name; the second takes an array of where you would like
to place the object.
Figure 2: Diffuse light.
Another useful routine is the glBindTexture procedure:
ation factor. Check out the OpenGL API documentation for more glBindTexture(target: Glenum; textureName: GLuint)
information.
It creates a new textured object and assigns it a unique texture name
The last parameter is a bit involved. In general, it sets properties of that you generate using glGenTextures. The function can also be used
the previous parameter, pname, and its value and format depend on to activate an old textured object. After generating a texture name
the value of pname. and assigning an object to it, we add our texture image to it. This can
be done by using the procedure glTexImage*:
Materials
Objects in the real world are made from specific materials with unique procedure glTexImage*(target Glenum;
reflective properties. One material might reflect light, another might Level: GLint ;
Components:GLint ;
absorb it, and so on. Why can’t we specify the material of the object
Width: GLsizei ;
that we construct? We can. You might have noticed from the previous Height: GLsizei ;
example that materials go hand in hand with lighting. To emphasize Border: GLuint ;
this point, let’s look at how this is calculated. Let’s say you have a light Format: GLEnum ;
source, L1, and an object with material of M1. The calculation is: Type: GLenum ;
Pixels: Pointer);
ous parameter.
This function is usually called twice: once for texture image
Texture magnification, and the second time for texture image reduction.
Texture is very important and The first parameter is the target, as with the previous
you’ll employ it in nearly every function. The second is GL_TEXTURE_MAG_FILTER or
Figure 3: Specular light. 3D application. Texture helps GL_TEXTURE_MIN_FILTER (magnification or reduction).
The last routine for this section is glTexCoord*. This function is used
to map the texture image on the object coordinates. The parameters
of this function depend on the object vertex. To explain this function,
let’s say we have a 2D quad that needs to be textured. This means we
have four vertexes we should handle, so we need to call glTexCoord2D
four times. The parameters of glTexCoord2D are supposed to be filled
with the location of the vertexes.
The first parameter indicates if the vertex is on the right side. If so,
this parameter is 1; otherwise it’s 0. The second indicates whether
the vertex is a top vertex: if so, it must contain 1; otherwise it’s 0. Figure 4: “The Scene;” the demonstration application in action.
Here’s a quick example for clarification; we’ll use the polygon we
built in Part I:
The parameters of this structure, nSize, should hold the size of
glBegin(GL_POLYGON); the structure. The nVersion is the version and must always be set
glTexCoord2D(0.0, 0.0) to 1. The dwFlags is set to support PFD_SUPPORT_OPENGL
glVertex3f(-1.0, -1.0 ,0.0);
and PFD_DOUBLEBUFFER. The PFD_DOUBLEBUFFER is
glTexCoord2D(1.0, 0.0)
glVertex3f(1.0, -1.0, 0.0); included because it helps eliminate animation flickers. The last
glTexCoord2D(1.0, 1.0) flag, PFD_DRAW_TO_WINDOW, is used to allow the buffer to
glVertex3f(0.5, 0.5, 0.0); draw directly to the window. Then we set the color bits and the
glTexCoord2D(0.0, 1.0) depth bits both to 24. This way every color buffer will have 2^24
glVertex3f(-0.5, 0.5, 0.0);
glEnd();
bits of color. iLayerType is set to GL_MAIN_PLANE. The rest
are all set to zero.
Now that you’re familiar with the basic concepts and commands, it’s It’s then necessary to retrieve the window DC using the GetDC func-
time to start programming your first OpenGL program. tion. This has to be done to allow the use of the two functions that
help to choose and set our pixel format. Next, the ChoosePixelFormat
The Scene function checks if Windows can serve our pixel format request. If it
This simple demonstration will show the implementation side of can, it will return the non-zero pixel index.
the issues described in this series (see Figure 4). You don’t need
to worry about the installation of special libraries to run the If we have a valid index, we can use SetPixelFormat to safely set the
demonstration. All the libraries used in this demonstration come current pixel format of the window to fit our needs. Then we must
with the Windows operation system. There’s plenty of informa- create our RC using wglCreateContext. This function takes the window
tion to help you understand the framework of this demonstration DC as an argument. After we successfully create the RC, we need to
and the role of every method in it. For an in-depth understand- make it the current RC by using glMakeCurrent. This function takes
ing of the code, you should explore the code. The complete the current DC so that future calls to OpenGL will eventually draw
demonstration application is available for download; see end of on the DC. The second parameter is the RC we’ve created.
article for details.
Since we have finished handling the RC, we can start program-
Setting the OpenGL Window ming our scene. Note that when you close the program you
Normally we get a full-fledged form and start working. Unfortunately, must release the DC and the RC. There is not really that much
you will have to build your own window when you work with OpenGL. to explain; all of the commands are the reverse of the previous
More specifically, you can manage the form to collaborate with OpenGL. ones. (You can see the code under the WM_CLOSE event in the
This can add significant overhead to your application, however, and WinMain function.) Now that you know how to create a suitable
acceptable speed is a crucial aspect of a good 3D application. window for OpenGL rendering, let’s explore the classes used to
implement the demonstration application.
You can see from the code that the Win32 API was used directly
to construct the window. Building the window is outside the scope The Scene’s Objects
of this article, so let’s start with setting the OpenGL rendering The scene is built from 10 classes, each of which has special
context (RC). characteristics:
The OpenGL RC is similar to the window device context (DC). Particle system. As you can see in the demonstration, particles are
All OpenGL 3D operations are executed on this RC. You can spread all over the scene. This kind of effect is called a particle
see in the WinMain function (under the WM_CREATE event) system. The particle system implemented in this demonstration
all the operations that require building the RC. Using the consists of three classes from which the other classes are derived.
PIXELFORMATDESCRIPTOR structure does some of the work; We’ll look at them using a top-down approach, starting with the
it’s filled with information to represent our request. TParticle class and ending with TParticleSystem.
TParticleSystem. The methods in this class handle the motion and TSceneBuilder. This class is derived from TSceneHandler and its
rendering of particles on the screen: aggregate, TSceneObjects, and is used for building the scene. This class
RenderParticle renders the particle on the screen. has just one method, named DrawScene. This method is virtual and
GenerateTTL generates a random TTL for every particle. abstract, and is being implemented in the TSceneBuilder class.
SetParticleDirection sets the particle direction using an equation
that generates a random screen location. This has been a very brief introduction of the classes, so I highly
SetParticleGravity sets the particle gravity. recommended you explore the demonstration application to get a
UpdateParticle includes all of the above to create motion. full understanding of the classes and how they interact to create
the scene.
TRingData. If you take a quick look at the demonstration, you will
notice several rings that rotate around a cube. TRingData is used as Conclusion
a container to hold the ring data. The x/y/z properties contain the We’ve powered through some core ideas concerning one of the more
location of each element in the ring. important APIs in the 3D industry. Although we haven’t covered the
more advanced facets of 3D graphics and OpenGL, the information
TSceneOjects. This class is derived from TParticleSystem and contains in this article should help you embark on an OpenGL education. ∆
all the 3D objects in the scene:
InitRing generates the ring data. The complete demonstration project referenced in this article is available
DrawFirstRing, DrawSecondRing, DrawThirdRing, and on the Delphi Informant Magazine Complete Works CD located in
DrawFourthRing draw the rings to the screen and set the INFORM\2001\JUN\DI200106EB.
rings’ materials.
DrawFloor draws the floor (better described as a net) of the scene.
DrawCube draws a 3D cube, setting its materials and texture.
RingData is an interface to all the ring data elements.
SetObjectProperties sets the object properties, e.g. for line smooth-
ing and setting the shading model.
By Charlie Calvert
T his is the second in a series of articles on Kylix and the Linux programming
environment. In the April issue of Delphi Informant Magazine you were introduced
to the Linux desktop, and became acquainted with some general facts about the world
of the X Window System, or more simply, X.
This second article in the series is relatively techni- Figure 1). The second is a standard Kylix applica-
cal, and the good times and light-hearted jokes are tion that queries X to find out about the attri-
few and far between. For those desperate for more butes of the current display (see Figure 2). The
accessible information, I’ll start by recommending purpose of this program is to show you how
a few books that all Linux users absolutely need to the low-level code found in this article relates to
own. These basic books describe in glorious detail standard CLX programming. A final program is
how to use the environment. a bit more complete, and offers a more in-depth
survey of what you can do when programming
If these were Windows books, you might fear they X. It is shown in Figure 6 on page 24, and
were trivial. “At the bottom of the screen, you will is available for download (see end of article for
see the Start button. Hold the mouse firmly in the details). This latter program gives you the ability
palm of your hand and slide it....” Okay, enough to handle keyboard input.
of that. Linux, fortunately, is a technical world, so
even the basic books on using the environment are The first two programs do little more than pop
filled with arcane information. Here are two that up windows and draw a bit of text in them. The
contain most of the lore you must master to be on second program, however, gives you a few ideas of
speaking terms with your Linux box: A Practical how to access some of the special features of the
Guide to Linux by Mark Sobell [Addison-Wesley, X programming environment. (Unlike the corre-
1997], and Running Linux, Third sponding situation in Windows, you can of course
Edition by Matt Welsh, et al. find the entire source for Xlib. The best place to
[O’Reilly & Associates, 1999]. look for it is at http://www.xfree86.org.)
If you have no flesh and blood The low-level X programs shown in this article,
guru to which you can turn, I and the next in this series, are extremely atypical
suggest you purchase both books. examples of how to program with Kylix. They’re
When you’re getting started with fairly complex in structure, and don’t use any of
Linux, a second opinion can be the helpful tools and objects usually employed
invaluable. in a Kylix program. Writing the same kind of
program using the Kylix visual tools and CLX
Programming X with Kylix would be trivial in the extreme. It would, at
In this article, I’ll show you most, take you about two minutes to complete.
three X programs. The first is a However, if you don’t use CLX, and instead
bare bones program that shows write directly to the X library API, then the task
Figure 1: A bare bones “Hello World” pro- you the basics of directly pro- of creating a simple GUI program is consider-
gram written using Xlib. gramming X from Kylix (see ably more difficult.
PDisplay = ^Display;
Display = record
ext_data : PXExtData; // Hook for extension to hang data.
private1 : PXPrivate;
fd : Longint; // Network socket.
private2 : Longint;
proto_major_version : Longint;
proto_minor_version : Longint;
vendor : PChar; // Vendor of the server hardware.
private3 : XID;
private4 : XID;
private5 : XID;
private6 : Longint;
resource_alloc : TDisplayResourceAllocProc;
byte_order : Longint; // Screen byte order.
bitmap_unit : Longint; // Padding and data requirements.
bitmap_pad : Longint; // Padding requirements on bitmaps.
bitmap_bit_order : Longint; // Least or MostSignificant.
nformats : Longint; // Number of pixmap formats in list.
pixmap_format : PScreenFormat; // Pixmap format list.
private8 : Longint;
release : Longint; // Release of the server.
private9 : PXPrivate;
Figure 2: A somewhat more complex Xlib program that uses bit- private10 : PXPrivate;
maps, fonts, and communicates properly with the window manager. qlen : Longint; // Length of input event queue.
last_request_read : Cardinal; // Seq num last event read.
request : Cardinal; // Sequence number of last request.
Just to set the record straight, let me make it clear why I’m showing private11 : XPointer;
you these more complex programs: First, they help you understand X private12 : XPointer;
and how it works. Second, they show that Kylix programmers have private13 : XPointer;
access to this kind of low-level functionality if they need it. Xlib is private14 : XPointer;
max_request_size : Cardinal; // Max words in request.
a very technical API, and being able to access it directly is one of
db : PXrmHashBucketRec;
the features that helps to set Kylix apart from other programming private15 : TDisplayPrivate15Proc;
languages. Usually Xlib is available only to C and C++ programmers. display_name : PChar; // "host:display" string.
Kylix’s ability to execute a line-by-line translation of an Xlib program default_screen : Longint; // Default screen.
written in C helps to show the power of the Object Pascal language. nscreens : Longint; // Number of screens on this server.
screens : PScreen; // Pointer to list of screens.
motion_buffer : Cardinal; // Size of motion buffer.
Finally, if you understand how X works, then you can get a feeling for private16 : Cardinal;
the capabilities and limitations of a standard Kylix program. All Kylix min_keycode : Longint; // Minimum defined keycode.
programs are built on top of X, and if you understand how X works, max_keycode : Longint; // Maximum defined keycode.
private17 : XPointer;
then you know what you can and cannot do with Kylix. In general, if
private18 : XPointer;
you can do it in X, then you can do it in Kylix. The point I’m trying private19 : Longint;
to make here should be self evident to all experienced programmers. xdefaults : PChar; // Contents of defaults from server.
In particular, if you know the low-level APIs for a process, then your // There's more, but it's private to Xlib.
ability to use a wrapper around that API is significantly improved. end;
In a sense, both Qt and CLX are really nothing more than wrappers
around X. As a result, it pays to know a little about how X works. Figure 3: The Display record.
Getting Documentation on X
Before I show you the X program, I want to take a moment to discuss If you are running Red Hat, Mandrake, or many other standard
getting documentation on programming in X. Once or twice in my distributions, you can get the online docs for XFree86 by download-
text I will tell you to get additional information by consulting info or ing a file with a name like XFree86-devel-4.0.1-1.rpm. Remember
man. If I’m going to do that, I have to be sure you can actually find that you can find the current version number of X on your Red
the references to which I allude. Hence the need for this section. Hat system by typing rpm -q XFree86. If you want the version of
your X documentation, type rpm -q XFree86-devel. If the files are
To begin, I need to say a few words about the program called RPM, available on your system, pass rpm -qd to find the directories where
the Red Hat package manager. It’s the tool you use on many Linux the docs are stored, or -qi to get general information on the release.
distributions when you want to install or update a new program. For instance, type:
You are about to get the 30 second course on the subject. For more
information, see the books I mentioned earlier, or type man rpm at rpm -qi XFree86-devel
the Linux shell prompt.
Finally, you can pass in rpm -ql, where l stands for list. This will
To install a new package, go to the command prompt and type give you a list of files in the package.
rpm -i mypackage.rpm. To upgrade a package, type rpm -Uhv
mypackage.rpm at the command prompt. To delete or erase a pack- In general, you’ll know if the development docs are installed correctly
age, type rpm -e mypackage.rpm. There you have it — the world of if man gives you an entry when you type man XCreateWindow. If man
RPM in 30 seconds. Phew! Now let’s apply what we’ve learned about tells you that it knows nothing about XCreateWindow, then XFree86-
RPM to the world of Xlib programming. devel is either not installed on your system, or not installed correctly.
uses interface
QForms,
Main in 'Main.pas' { Form1 }; uses
SysUtils, Types, Classes, QGraphics, QControls, QForms,
{$R *.res} QDialogs, QStdCtrls;
begin type
Application.Initialize; TForm1 = class(TForm)
Application.CreateForm(TForm1, Form1); Button1: TButton;
Application.Run; ListBox1: TListBox;
end. procedure Button1Click(Sender: TObject);
end;
Figure 4: The .dpr file for the xdpyinfoGUI program initializes the
var
Kylix libraries and launches the main form of the program. Form1: TForm1;
implementation
Further documentation for X isn’t scarce. You can expand your
uses
horizons by going to http://www.xfree86.org/support.html, where
Xlib;
you’ll find detailed documentation, and an entire book on XFree86.
You might also wish to purchase the Xlib Programming Manual, {$R *.xfm}
Volume One, by Adrian Nye [O’Reilly & Associates, 1992], the first
in a series of technical books on this subject. procedure TForm1.Button1Click(Sender: TObject);
var
display: PDisplay;
A “Hello World” Program in X begin
This first program makes an end run around a number of the more display := PDisplay(Application.Display);
complex features of an X program. As a result, it’s relatively short, ListBox1.Items.Add('Display version: ' +
IntToStr(display.proto_major_version) + '.' +
weighing in at under 100 lines. It’s shown in Listing One (on page
IntToStr(display.proto_minor_version));
25). Note: The hundred lines of code in this program must be ListBox1.Items.Add('Release number: ' +
typed in nearly entirely by hand (if you don’t just download it). IntToStr(display.release));
The Kylix IDE will not generate more than the sparest outline of ListBox1.Items.Add('Vendor: ' + display.vendor);
the program for you. If you built the same program in Kylix using ListBox1.Items.Add('Display name: ' +
display.display_name);
CLX and the visual tools, however, the total number of lines would ListBox1.Items.Add('Network socket: ' +
be much fewer, and you would have to do almost no typing to IntToStr(display.fd));
create it. As I said, this is an extremely atypical Kylix program. ListBox1.Items.Add('Default screen: ' +
IntToStr(display.default_screen));
ListBox1.Items.Add('Number of screens: ' +
The program follows the basic structure of a simple console applica-
IntToStr(display.nscreens));
tion, though of course it runs in a window. By saying this, I mean ListBox1.Items.Add('Screen width: ' +
that it’s based on the following very simple program structure: IntToStr(display.screens.width));
ListBox1.Items.Add('Screen height: ' +
program simple; IntToStr(display.screens.height));
begin end;
end.
end.
The line of code that says begin is the entry point for the program,
Figure 5: The main form of the xdpyinfoGUI program presents
just as main is the entry point for a C program. Execution stops the user with information about the first X display on the system.
when the code reaches the line of code that reads end. The core of
the code in the program appears between the begin and end pairs
that represent the main block of the program. Additional functions functions at the command line by typing info FunctionName. For
and procedures can be added between the program statement and instance, typing info XDrawString at the shell prompt should get
the begin line. you Linux-based help on the XDrawString function. You can also
type man FunctionName to get help. If these commands don’t work
The program relies on a big unit named Xlib that contains defini- for you, then you either don’t have info or man installed, or you
tions of the structures, constants, and functions found in the Xlib don’t have the docs for Xlib installed. In either case, you should have
binary libraries. These libraries are, of course, written in C. As the RPM for these files available on your system.
I stated earlier, Kylix can have trouble calling libraries written in
C++. However, it can call functions written in C. There is no Obtaining the Display
significant difference in performance when calling a function writ- The first significant call in the Simple2 program looks like this:
ten in C from Object Pascal or from C. In other words, this X
program will have essentially the same performance characteristics display_name := XDisplayName(display_name);
as the same program written in C.
This function returns the name of the current display. Usually this
Most of the functions in this program are part of Xlib. Very few calls name is :0:0. (Additional information on displays and display names
are made to native Object Pascal functions. You can get help on X is presented in the previous article, in the section called “Attaching to
To create this simple program, open Kylix and choose File | New
Application. Drop a button and list box on the main form of the
program. Double-click on the button to create the Button1Click
method. Add the code for the method shown in Figure 5. Finally, add
the Xlib unit to the uses clause.
Note the code giving you access to the Display record talked about
throughout this section of the text. All Kylix programs initialize a
global Application object created automatically at program start up.
You can view part of this initialization process in Figure 4.
The files referenced in this article are available on the Delphi var
Informant Magazine Complete Works CD located in INFORM\2001\ xgcv: XGCValues;
JUN\DI200106CC. attributes: XSetWindowAttributes;
event: XEvent;
attributes_mask: Integer;
display_name: PChar;
done: Integer;
begin
display_name := nil;
display_name := XDisplayName(display_name);
display := XOpenDisplay(display_name);
if display = nil then begin
WriteLn('Could not open display: ', display_name);
Exit;
end;
WriteLn('Cuccessfully opened display ', display_name);
XSynchronize(display, 1);
attributes.event_mask := KeyPressMask or
KeyReleaseMask or ButtonPressMask or
ButtonReleaseMask or ExposureMask;
attributes.background_pixel :=
XBlackPixel(display, XDefaultScreen(display));
attributes.border_pixel :=
XBlackPixel(display, XDefaultScreen(display));
attributes_mask := CWBackPixel or
CWBorderPixel or CWEventMask;
window := XcreateWindow(display,
XDefaultRootWindow(display), 0, 0, 300, 300, 0,
XDefaultDepth(display, XDefaultScreen(display)),
InputOutput,
XDefaultVisual(display, XDefaultScreen(display)),
attributes_mask, @attributes);
WriteLn('Successfully created window\n');
XMapWindow(display, window);
WriteLn('Successfully mapped windows\n');
WriteLn('Attempting graphics context creation\n');
xgcv.foreground := $00FFFFFF;
xgcv.background := $00000000;
gc := XCreateGC(display, window,
(GCForeground or GCBackground), @xgcv);
WriteLn('Successfully created graphics context');
done := 0;
while done <> 1 do begin
XNextEvent(display, @event);
case (event.xtype) of
Expose:
begin
if event.xexpose.count <> 0 then
Break;
DrawMyString('The wondrous world of X!');
end;
KeyPress: done := 1;
ButtonPress: ;
end;
end;
Charlie Calvert is the author of eight books, including the best-selling Delphi XCloseDisplay(display);
Unleashed series. A speaker at eight Borland conferences, Charlie has also given end.
courses at many other sites around the globe. End Listing One
By Keith Wood
A Simple API for XML (SAX) parser breaks the input XML document into tokens, and
triggers events on attached handlers for each token (see “SAX for Delphi” in the
March 2000 Delphi Informant Magazine). These event handlers can process the events
however they choose, e.g. by looking for particular elements and their contents.
One of the extra abilities included in the latest Properties specific to the IMXWriter interface let you
Microsoft XML package (MSXML v3) is support control the appearance of the resulting document.
for SAX generation in its IMXWriter interface. Figure 1 shows the declaration for the interface.
An object created through this interface also
expresses each of the other handler interfaces. The output property retrieves or redirects the XML
Invoking handler methods on such an object document generated by the writer. If assigned an
causes it to generate an XML document that object that implements IStream, the output is sent
reproduces those events. The result is available directly to that stream. If not assigned a value, or if
through the output property, which can be refer- it’s assigned an empty string, the output appears as a
enced as a WideString value, or piped directly to string value when this property is read. Assigning an
an IStream object. empty string also clears the internal buffer in prepara-
tion for generating the next section of the document.
So, instead of instantiating a SAX parser and In this way, the memory requirements of the writer
supplying your own handlers, you can generate a are reduced. The output is also reset whenever a
document by creating an IMXWriter object and startDocument event occurs.
calling its handler methods as if you’re the parser.
An alternative use is to transform an existing Set or return the encoding scheme used by the
XML document by having a SAX parser read it, writer through the encoding property. If you’re
pass the events through a SAX filter (which does retrieving the output as a string value, this setting is
the actual transformation), and direct its output ignored since all strings are UTF-16 encoded.
to a writer.
Set the byteOrderMark property to True to have
IMXWriter Interface the writer generate a byte order mark for appropri-
Objects created through the CoClass for the ate encodings. When False, no byte order mark
IMXWriter interface also implement the is included. A byte order mark is never produced
IVBSAXContentHandler, IVBSAXDTDHandler, when output is retrieved as a string.
IVBSAXLexicalHandler, and IVBSAXDeclHandler
interfaces. You invoke these to generate an XML When set to True, the indent property causes the
document. In principle, piping the events from a output document to be formatted for improved
SAX parser directly to a writer produces exactly readability. Each level of elements is indented by
the same document on output as on input. How- one tab, and opening tags appear on a new line. If
ever, certain input deemed not significant by the set to False, the XML appears without breaks.
XML specification may be skipped or generated
differently. Also, the output encoding could be The standalone property controls the appearance
different from that of the input. of the standalone declaration in the XML prolog.
Clear the internal buffer to its output stream or string with the flush Given an attribute’s position (starting from zero), set its name(s) and
method. This happens automatically when the output property is value to the specified arguments through the setAttribute method. Use
accessed, or when the endDocument event occurs. the setAttributes method to copy the contents of another attribute
collection. It’s probably more efficient to reuse an existing object than
IMXAttributes Interface to generate a copy.
Generating an element requires that its attributes (if any) be specified.
The IVBSAXAttributes interface from SAX defines how to extract Update the local name for the specified attribute with the setLocalName
information about a set of attributes, but says nothing about how method. Similarly, alter the qualified name of the nominated attribute
to set up those details in the first place. The IMXAttributes interface through the setQName method. Use the setType method to change
Creating a Writer To demonstrate the chunking ability of the writer, the UpdateOutput
To demonstrate how the writer is used, you need to develop a method is called after each major section of the document is com-
generator that takes data from a database and formats it into pleted. This routine adds the generated XML to the memo onscreen
XML. The data in this example are the movie-watcher details via the output property, and then resets that property ready for the next
from my previous XML articles, which are stored in six tables in a section. Setting the indent property to True before calling any handlers
Paradox database. (It’s all available for download; see end of article causes the document to be formatted for improved readability.
for details.) Before trying to access the data, be sure to set up
a Paradox BDE alias named movie-watcher, and set the path to Defining the DTD
that of the example program and tables. Since the writer is based on the SAX2 specification, it implements
the two standard extensions: IVBSAXLexicalHandler and
Obtaining a SAX writer object is easy with the CoMXXMLWriter IVBSAXDeclHandler. The first of these lets you add the DTD decla-
CoClass. Once MSXML version 3 is installed (go to http:// ration itself through its startDTD method, specifying the document
msdn.microsoft.com/xml/default.asp if you don’t have it), the type and its public and system identifiers (see Figure 3). Following
MSXML2_tlb unit is included in your uses clause, and you call this call, and before the endDTD call, you use the DTD handler
on this class to generate a new writer via its Create method. Use interface to add any notations and unparsed entities.
CoMXXMLWriter30 instead to always produce an object from
this version of the XML package, rather than the latest version You could define the entire DTD internally, including element
returned by the previous CoClass. Although the IMXWriter inter- and attribute declarations, by invoking methods on the
face derives directly from IDispatch and makes no mention of the IVBSAXDeclHandler interface. Such calls must appear between
handler interfaces, the resulting object does implement them. The the startDTD and endDTD calls.
easiest way to refer to them from Delphi is to declare variables for
each desired interface, and use the as operator to cast the writer Adding Content
accordingly. See Listing One (on page 30) for the creation code. The main information in the generated document comes from calls
to the content handler’s methods, especially startElement/endElement
Also necessary is an instance of the IMXAttributes interface, which and Characters. Data for movies in the sample document appears
provides for the writing of attribute values into a storage area. within the confines of the main element. A movies element is started
Such objects also implement the IVBSAXAttributes interface that before stepping through each database record and producing its
Conclusion
Similar in structure to generating a document through one of the
DOM offerings, the SAX writer approach offers the usual SAX
advantage of reducing memory requirements. Only a single element
or text node appears at any one time. Furthermore, this technique
lets you create the entire document, including any internal DTD —
something that most DOMs don’t allow.
This article is adapted from material for the forthcoming book Delphi
Developer’s Guide to XML [Wordware, 2001].
Keith Wood is an Australian working his way around the United States. He’s
a systems engineer with Active Health Management, based near Boston, and a
freelance technical writer. Keith started using Borland’s products with Turbo Pascal
on a CP/M machine. His forthcoming book, Delphi Developer’s Guide to XML
[Wordware, 2001], covers many other aspects of XML. You can reach him via
e-mail at kbwood@compuserve.com.
By John C. Penman
IP Helper API
Part I: Retrieving Local Configuration Parameters and
Network Statistics
W ith the explosive growth of the Internet in recent years, networking has become
a fact of life on modern Windows platforms, as it has been on UNIX systems
long before the arrival of Windows. Like any system of software and hardware
working together, it needs maintenance to perform satisfactorily over time.
Thus, tools to manage TCP/IP networking were ery of data between peers. The User Datagram
developed for system administrators, power users, Protocol (UDP) is a protocol for transmission
and the plain curious. Of course, the tools of packets of data between peers with no mecha-
described in this article only represent a small part nisms for error recovery.
of the spectrum of network management tools.
Microsoft provides tools, which have their peers These tools use a set of functions known as the IP
on UNIX, including Linux systems, that run from Helper API. These functions are implemented in
the command line (see Figure 1). These tools work the IPHlpAPI.dll. This DLL should be present in
intimately with protocols such as IP, ICMP, TCP, the \System32 directory on Windows NT4/2000
and UDP. and in the \System directory on Windows 98/Me.
Note, however, that the API isn’t available for Win-
The Internet Protocol (IP), which is defined by dows 95.
RFC791, is the glue that makes the Internet pos-
sible. IP is responsible, among its many tasks, for In this two-part series of articles about the IP
the delivery of datagrams between the sender and Helper API, I’ll demonstrate how to use these
receiver. The Internet Control Message Protocol functions. Part I describes how to retrieve local
(ICMP), which sits on top of IP, is the protocol configuration parameters and network statistics
that manages flow control and error reporting for a machine. Part II will explore routing issues
for TCP/IP. The Transmission Control Protocol (Route.exe) and address resolution (ARP.exe).
(TCP) is a stream protocol that guarantees deliv-
As part of the JEDI (Joint Endeavor of Delphi
Innovators) effort, I used Dr. Bob’s HeadConv util-
Tool Platform(s) Description
ity to translate the C header files to their Delphi
IPConfig.exe Windows 98, NT4, Configures IP addresses and retrieves equivalents (see Figure 2). The main interface file is
and 2000 network adapter information. IPHlpAPI.pas; the other units define the constants
WinIPCfg.exe Windows 95 Configures IP addresses and retrieves and data structures.
network adapter information.
NetStat.exe Windows 98, NT4, Retrieves statistics for the TCP, UDP, IP, Marcel van Brakel, a leading figure in the JEDI
and 2000 and ICMP protocols. effort, fine-tuned the Delphi units, and made them
Route.exe Windows 98, NT4, Displays and configures the routing table. conform to Borland’s coding standards. These inter-
and 2000
face units should be available from the JEDI Web
ARP.exe Windows 98, NT4, Displays and configures the ARP table.
and 2000
site (www.delphi-jedi.org), and are also available
from the Delphi Informant Magazine Web site as
Figure 1: Command-line tools for TCP/IP network management. part of the demonstration projects for this article
Retrieving Adapter Information The function returns information on the adapters on the local
In addition to the IP configuration parameters, it’s also useful to deter- machine in the first parameter, pAdapterInfo, which is a pointer to
Because there are too many fields to examine Figure 13: Help information on how to use dIPConfig.
in this article, just a few important fields
will be examined. The function enumerates
the name (AdapterName), the physical
address (Address), and a short description
(Description) for each adapter on a machine.
In the past, it was a fairly difficult task to
obtain the address of a network card by
other means, but by using this call you can
obtain the address fairly easily. Before leaving
GetAdaptersInfo, take a look at the fields
relating to the Dynamic Host Configuration
Protocol (DHCP), which are DhcpEnabled,
DhcpServer, LeasedObtained, and LeaseExpires.
A call to IpRenewAddress or IpReleaseAddress targets a specific adapter, To add a new IP address, assign the Address parameter with the desired IP
and a specific IP address is assigned to a specific adapter. To select the address, the IpMask to the network mask, and the required index, IfIndex,
adapter, call the GetInterfaceInfo function to obtain the index to the obtained from GetAdaptersInfo. To delete an existing IP address, get the
adapter. In Windows, every adapter or network interface card (NIC) context number for that IP address. After calling GetAdaptersInfo, scan
has a unique index. The prototype for this function, which is also the IpAddressList field, which is a linked-list structure, to match the IP
defined in IPHlpAPI.pas, is as follows: address to delete. If there’s a match, get the context number from the
Context field, and pass it to the DeleteIPAddress function.
function GetInterfaceInfo(pIfTable: PIP_INTERFACE_INFO;
dwOutBufLen: PULONG): DWORD; stdcall; Network Statistics
The functions that retrieve statistics for the IP, ICMP, TCP, and UDP
The first parameter in the following code is a pointer to the protocols are straightforward, so they will not be described here. How-
IP_INTERFACE_INFO data structure. The second parameter speci- ever, there’s a project called dNetStat that encapsulates some of the
fies the size of the buffer required to return all information: functionality of the real world NetStat utility (available for download;
see end of article for details). For more information on how to retrieve
PIP_INTERFACE_INFO = ^_IP_INTERFACE_INFO; network statistics, consult the latest MSDN (Microsoft Developer
IP_INTERFACE_INFO = record Network) documentation.
NumAdapters : Longint;
Adapter : array [0..1-1] of IP_ADAPTER_INDEX_MAP;
end; The Projects
Take a look at the four projects developed for this series (see Figure
12). dIPConfig and dNetStat are discussed in detail here; routing and
The NumAdapters field is the number of adapters present on addressing issues will be covered in the next article. As you’ll discern from
the local machine. The second field, Adapter, is an array of Figure 12, the dIPConfig and dNetStat projects emulate the functionality
IP_ADAPTER_INDEX_MAP data structures: of IPConfig and NetStat programs provided by Microsoft. Like these
utilities, the Delphi projects are compiled and used as consoles. To obtain
PIpAdapterIndexMap = ^TIpAdapterIndexMap; help on how to use these command-line projects, simply call the utility:
_IP_ADAPTER_INDEX_MAP = record
Index: ULONG;
C:\dipconfig /?
Name: array [0..MAX_ADAPTER_NAME - 1] of WCHAR;
end;
This will generate a screen of help information, as shown in Figure 13.
As another example on how to use dIPConfig, the following com-
The array appears to have zero elements allocated, but that’s not so. mand adds a new IP address to the adapter:
The first parameter tells the utility that you want to add a new IP
address, 192.168.1.200, to the adapter whose interface is 16777219.
You must specify the subnet mask, which in this example is
255.255.255.0. Remember that to get the interface for the adapter to
add an IP address, you must call dIPConfig like this:
dIPConfig -pa
To delete the IP address, you must specify the context that’s associated
with the IP address you wish to remove. For example, to remove the IP
address just added in the previous example, call dIPConfig like this:
dIPConfig -d 192.168.1.200
dIPConfig -pn
Conclusion
You’ve now learned the rudiments of the IP Helper API, and found
out how to retrieve IP configuration details for any adapter on the
local machine. If you like, you can change some of the settings in the
configuration of adapters. In Part II, we’ll explore the other functions
in the IP Helper API to perform routing and address resolution. ∆
Additional Resources
UNIX Network Programming: Networking APIs: Sockets and XTI,
Vol. 1 by W. Richard Stevens [Prentice Hall, 1997].
Internetworking with TCP/IP: Design, Implementation, and Internals,
Vol. 2 by Douglas E. Comer and David Stevens [Prentice Hall, 1994].
Network Programming for Microsoft Windows by Anthony Jones
and Jim Ohlund [Microsoft Press, 1999].
The dIPConfig and dNetStat projects referenced in this article are avail-
able on the Delphi Informant Magazine Complete Works CD located in
INFORM\2001\JUN\DI200106JP.
By Bruno Sonnino
T his month, I’d like to revisit a couple of Delphi tips from previous columns,
and demonstrate how the same tasks can be accomplished with Kylix. The first
concerns a tip published in the December 2000 issue of Delphi Informant Magazine.
Rotating Text rotated at any angle. You can even draw text
The article “Fancy Menus, etc.” showed how mirrored horizontally and vertically.
to create a new font using the CreateFont and
CreateFontIndirect functions. These are Windows To draw rotated text, you must use the
API functions, so they aren’t available in Kylix. QPainter_rotate function. It’s declared like this:
In Delphi, all drawing is done using the TCanvas procedure QPainter_rotate(handle: QPainterH;
class. This hasn’t changed in Kylix, but many a: Double); cdecl;
_statfs = record
f_type: Integer; // Type of filesystem.
f_bsize: Integer; // Optimal transfer block size.
f_blocks: __fsblkcnt_t; // Total data blocks in fs.
f_bfree: __fsblkcnt_t; // Free blocks in fs.
f_bavail: __fsblkcnt_t; // Free blocks available.
f_files: __fsfilcnt_t; // Total file nodes in fs.
f_ffree: __fsfilcnt_t; // Free file nodes in fs.
f_fsid: __fsid_t; // File system ID.
f_namelen: Integer; // Maximum length of file names.
f_spare: packed array[0..6-1] of Integer;
end;
TStatFs = _statfs;
PStatFs = ^TStatFs;
Figure 5: The TStatFs structure. Figure 7: ListView with mounted drives, total, and available space.
Figure 6: List mounted drives with total and available space. mntent = record
mnt_fsname: PChar; // Device for file system.
mnt_dir: PChar; // Directory mounted on.
Each column can be separated by space or tab characters. Each line
mnt_type: PChar; // Type of filesystem.
contains the following information: mnt_opts: PChar; // Options for fs.
The first column is the name of the physical device to be mnt_freq: Integer; // Dump frequency (in days).
mounted. The first line in this example shows /dev/hda5, the mnt_passno: Integer; // Pass number for 'fsck'.
fifth partition of the first IDE drive. end;
TMountEntry = mntent;
The second column is the device mount point, the directory PMountEntry = ^TMountEntry;
of the device to be merged. For example, /dev/hda1 will be
mounted in /mnt/win. All subdirectories from this partition will
be subdirectories of /mnt/win. When you’ve finished using the file, you must call endmntent to close
The third column is the file system type. In this file we can see the it. The code in Figure 3 shows how to retrieve the drive information
swap type, indicating a Linux swap partition; the ext2 type, a Linux and fill a ListView control with the mounted drives.
file system with long file names; the iso9660 type, used in CD-
ROMs; and the vfat type, used with Windows Fat32 partitions. We open the file /etc/mtab with the function setmntent. It returns a
The fourth column shows the device mount options. Here you PIOFile, stored in the MntDrives variable. Then we loop through all
can have many options, separated by commas. For example, the drive entries using getmntent. When there are no more drives, getmntent
CD-ROM has the options user, ro, and noauto, indicating that returns nil. If the return value isn’t nil, we add a new item to the
the user can mount this device, it must be mounted read-only, ListView, filling it with the MountEnt structure values. At the end, we
and that it shouldn’t be mounted automatically at boot time. close the file with endmntent. Figure 4 shows an example of the results.
The first parameter is the file name of any file within the mounted
file system. You can even use the path name where the device was
mounted. The second parameter is a reference to a TStatFs structure
(see Figure 5). This structure will be filled with information when
the function returns.
The members that interest us are f_bsize, which shows the block size
for the device; f_blocks, which shows the total blocks; and f_bavail,
the available blocks in the device. To retrieve the available space in
all mounted drives, we’ll scan the /etc/mtab file as we did in the last
tip, and pass the mount point of every mounted device as the first
parameter to the statvs function. The code in Figure 6 shows how
to retrieve information about all mounted drives, and load the data
into a ListView control.
The code is similar to that of the last tip. For each drive we find
in /etc/mtab, we pass the mount point to statvs. If the number of
total blocks is greater than 0, we have a valid device and add it to
the ListView. The space is obtained by multiplying the number of
blocks by the block size, then dividing by 1,024, to display the size in
KB. Figure 7 shows the ListView with the mounted devices and their
respective total and available space.
Conclusion
We’ve seen how to use the QPainter class to draw text at many angles.
When Delphi 6 is available, you will be able to use the code for
this tip in Delphi and Kylix, just by recompiling the source code.
Even though we’re accessing capabilities not directly supported by the
VisualCLX TCanvas, the code is still portable.
A Brazilian, Bruno Sonnino has been developing with Delphi since its first version
in 1995. He has written the books 365 Delphi Tips, Kylix - Delphi for Linux, and
Developing Applications in Delphi 5, published in Portuguese. He can be reached
at sonnino@netmogi.com.br.
GExperts
The Indispensable Open-source Delphi Add-in
Y ou can live without GExperts, but after you use it, you won’t want to. Many of you
already agree with me, as GExperts won Delphi Informant Magazine’s Readers
Choice award for Best Open Source Product (see DI’s April 2001 issue). Quoting from
the introduction of the GExperts Web site: “GExperts is a set of tools built to increase
the productivity of Delphi and C++Builder programmers by adding several features to
the IDE.” That’s an understatement. GExperts makes a great deal of tedious coding a
snap, and enhances our favorite development tool in myriad ways.
GExperts
Figure 3: Select the Test button to get this dialog box. Web Site: http://www.gexperts.org
Price: Open source (free).
The ToDo List expert is a replacement for Delphi’s To-Do List. You
can categorize to do items by level of importance/severity. To add new this is the Editor expert, which provides hotkeys to perform actions
to-do items, you should type comments in your code like this: such as commenting and uncommenting code, adding the date (in
whatever format you specify), and locating and matching delimiters.
{ #ToDo1 Rewrite this code so that it actually works. }
{ #ToDo2 Add support for XML here later. } Conclusion
What are you waiting for? Download GExperts now. It’ll save you
The Code Proofreader allows you to specify that certain typos time and money, make you look good, and maybe even help you win
are automatically corrected. For friends and influence people. And who knows — you may be the
example, you can replace “:+” next person to enhance or extend this programming masterpiece. ∆
and “;+” with “:=”.
var
btnAbout: TBitBtn;
btnAbout := TBitBtn.Create(Self);
with btnAbout do begin
Name := 'btnAbout';
Parent := Self;
Left := 8;
Top := 340;
Width := 75;
Height := 25;
Caption := '&About';
Clay Shannon is an independent Delphi consultant, author, trainer, and mentor
TabOrder := 0; operating as Clay Shannon Custom Software. He’s available for remote develop-
OnClick := btnAboutClick; ment or short-term on-site assignments in the Coeur d’Alene, ID/Spokane, WA
// Glyph := // please assign
NumGlyphs := 2;
area. He’s the author of Developer’s Guide to Delphi Troubleshooting (Wordware
end; 1999), which is soon to be revised. To view his resume, point your browser to
http://www.sysadminsrus.net/clayshannon/ClayShannon.doc. You can e-mail him
Figure 5: GExperts generates the code you need to program- at bclayshannon@earthlink.net.
matically create the highlighted control.
By Clay Shannon
T oo many programmers are like F. Scott Fitzgerald, or even Marcel Proust, when it comes to their coding style,
but more closely resemble Ernest Hemingway when it comes to commenting. They have the comment:code ratio
backward. Comments should be amply sprinkled throughout the code, and be well-thought-out enough to actually
make sense to other coders without intimate knowledge of the source. Courteous coders don’t scrimp on commenting.
Oddly, the code itself is sometimes verbose. It goes on and on, when | Syntax Options | Complete Boolean eval checked (by default it isn’t).
a simple, concise statement or two would do. We’ve already covered
the subject of commenting code in this column (in the May 2000 Applying the first two suggestions, if the result of the two tests is a
issue), so let’s now tackle the subject of writing concise code using Boolean assignment, you can simplify a statement like this:
five specific examples.
if YouLikeDelphi then
One. Boolean assignment statements should be assigned directly. if YouLikeLinux then
Result := True;
That is to say, if you have an if/then/else statement where the if sets
a Boolean to True, and the else sets it to False (or vice versa), you
can make your code cleaner by using a single assignment. In other to this:
words, instead of writing:
Result := YouLikeDelphi and YouLikeLinux;
if SomethinAintRaht then
Result := True In brief, there’s usually no purpose for Result := True or Result := False
else
assignments in your code when they’re the result of a conditional test. Of
Result := False;
course, at times you will make direct assignments to Boolean values, most
often when initializing variables. Even then, however, it’s unnecessary to
you can reduce the code from four lines to one by writing this: initialize Boolean values to False, as they are thus initialized anyway by
Delphi. (Boolean members of objects are auto-initialized to False (0),
Result := SomethinAintRaht; integers to 0, strings to an empty string (''), etc.)
Two. In most cases, you can avoid if/then/if statements by combining Three. It’s unnecessary to write “= True” or “= False” when testing
the two tests into an and condition. For example, instead of this: a Boolean value. Thus, this condition:
You don’t have to worry about the second half of the test being executed Four. Another common operation that’s made unnecessarily
if the first one fails, as long as you don’t have Project | Options | Compiler complex by some programmers is concatenating strings by using
ShowMessage(Format(
'X coordinate is: %d Y coordinate is: %d', [X,Y]));
Five. The with statement is not only more efficient, but also often
makes code more readable. For example, compare this:
with this:
So there you have it. These five suggestions probably won’t knock
you off your feet with their insight, but they will certainly pro-
duce more readable — thus more maintainable — and efficient
code. ∆
T his is the first of a two-part discussion of the conceptual tools that make it possible for us to plan projects and
reuse common patterns during the development phase. The two approaches that seem to have captured the
attention of the development community are UML (Unified Modeling Language) and design patterns.
It’s been several years since I first heard about UML and design oriented development using a specific application — a word proces-
patterns. While enjoying lunch with several new friends at a Borland sor. This is very helpful and accomplishes a couple of important
Conference (I think it was the one in Nashville), others at the table objectives. First, it demonstrates in a concrete manner different con-
were wondering when there might be sessions on these topics at texts in which various patterns can be used. Second, it provides a lot
a Borland conference. We didn’t have to wait long. For the past of clues about making programming decisions using these patterns.
two years there have been a number of workshops and tutorials, The second part of the book discusses 23 patterns that fall into three
particularly on design patterns. All the ones I have attended have general categories: creational, having the responsibility of instantiat-
been outstanding, particularly those given by Mark Miller, who has ing classes; structural, providing various means of extending a class’
a knack for relating design patterns to programming tasks in general, functionality; and behavioral, concerned with a class’ specific tasks
and Delphi in particular. and areas of responsibility. Let’s briefly examine a few patterns to get
a better idea of how useful they can be in planning and writing an
Last summer after attending one of Mark’s sessions it occurred to me application.
that it was about time I learned more. This seemed particularly timely
since I was starting to work again on updating a large educational Consider the Singleton, a unique pattern whose purpose is to make
application and needed some conceptual tools to help make the certain that a class has one and only one instance. It has the addi-
whole job more manageable. I did an Internet search to see what tional responsibility of providing a global point of access to the
books were available, selected quite a few, and have read or at least instantiated class. One obvious place for using the Singleton would
heavily browsed all of them. In this column I will share with you my be in storing application-state data, so that when a user starts an
assessment of these works in my ongoing self-education. This month application it has the same visual appearance and data loaded as the
I will discuss the primary sources, to which I refer as “Classics,” even last time it was shut down. At the opposite end of the spectrum is the
though they were written in the mid-1990s. Next month I’ll discuss Flyweight, a pattern used to support large numbers of closely related
the secondary sources under “New Trends.” objects (such as character objects in a word-processing application)
with a minimum of overhead. This pattern can be used in many
Design patterns. Design patterns are intimately related to object- different contexts at the same time.
oriented programming. They’re based upon the recognition of certain
facts — facts that are quite subtle, but obvious once we realize them: There are patterns for creating families of objects (Abstract Factory),
1) that the objects we use in building a program can be very different creating new objects from a basic one (Prototype), and creating an
from each other in their fundamental purpose and design; and 2) we object that defers the instantiation of a specific class to its subclasses
might easily use the same or similar designs over and over without (Factory Method). This work presents each pattern in a very under-
realizing it. The realization of these facts by many developers during standable manner, with its purpose(s), its synonyms, its use in solving
the 1990s led to an interest in identifying these patterns. In 1995 programming problems, its constituents, its structure, and so forth.
this activity resulted in the publication of the seminal work on the The authors take great pains to make each pattern as understandable
topic, Design Patterns: Elements of Reusable Object-Oriented Software as possible.
[Addison-Wesley, ISBN 0-201-63361-2] by Erich Gamma, Richard
Helm, Ralph Johnson, and John Vlissides (who have since become Unified Modeling Language. Design patterns are of interest to devel-
known as “the gang of four”). opers, but probably no one else. The UML is different. Not only can
this tool be used to help us in modeling our software, it can also
Design Patterns discusses a great deal more than patterns. The first be used to communicate design decisions to others — our managers,
part of the book discusses the relationship of these patterns to object- clients, colleagues, etc. This should be obvious since models provide