By Ray Lischner
Price: $39.99 USD
£20.95 GBP
Cover | Table of Contents | Colophon
program keyword and using a
begin-end block for the main
program. Delphi programs are usually short, though, because the real
work takes place in one or more separate units. In a GUI application,
for example, the main program usually calls an initialization
procedure, creates one or more forms (windows), and calls a procedure
for the Windows event loop.$AppType directive, which tells Delphi
to build a console or a GUI application.)uses declaration lists the units that make up the
program. Each unit name can be followed by an in
directive that specifies a filename. The IDE and compiler use the
filename to locate the units that make up the project. Units without
an in directive are usually library units, and are
not part of the project's source code. If a unit has an
associated form, the IDE also stores the form name in a comment.
Example 1.3 shows a typical program source file.program Typical;
uses
Forms,
Main in 'Main.pas' {MainForm},
MoreStuff in 'MoreStuff.pas' {Form2},
Utils in 'Utils.pas';
{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.CreateForm(TForm2, Form2);
Application.Run;
end.Forms unit is part of the standard Delphi
library, so it does not have an in directive and
source file. The other units have source filenames, so Delphi's
IDE manages those files as part of the project. To learn about the
$R compiler directive, see . The Applicationlibrary keyword instead of
program. A library typically has an
exports declaration, which lists the routines that
the DLL exports. The exports declaration is
optional, and if you intend to use a unit in a library, it's
usually best to put the exports declaration in the
unit, close to the subroutine you are exporting. If you don't
use the unit in a library, the exports declaration
has no impact.begin-end block—executes
each time the library is loaded into an application. Thus, you
don't need to write a DLL procedure to handle the
DLL_PROCESS_ATTACH event. For process detach and
thread events, though, you must write a handler. Assign the handler
to the DllProc variable. Delphi takes care of
registering the procedure with Windows, and Windows calls the
procedure when a process detaches or when a thread attaches or
detaches. Example 1.4 shows a simple DLL procedure.library Attacher;
uses Windows;
procedure Log(const Msg: string);
begin
MessageBox(0, PChar(Msg), 'Attacher', Mb_IconInformation + Mb_OK);
end;
procedure AttachDetachProc(Reason: Integer);
begin
case Reason of
Dll_Process_Detach: Log('Detach Process');
Dll_Thread_Attach: Log('Attach Thread');
Dll_Thread_Detach: Log('Detach Thread');
else Log('Unknown reason!');
end;
end;
begin
// This code runs each time the DLL is loaded into a new process.
Log('Attach Process');
DllProc := @AttachDetachProc;
end.ShareMem unit as the first unit in your
application and in every library the application loads. The
is and as operators work
correctly.
Integer. The
Integer type represents the natural size of an
integer, given the operating system and platform. Currently,
Integer represents a 32-bit integer, but you must
not rely on that. The future undoubtedly holds a 64-bit operating
system running on 64-bit hardware, and calling for a 64-bit
Integer type. To help cope with future changes,
Delphi defines some types whose size depends on the natural integer
size and other types whose sizes are fixed for all future versions of
Delphi. Table 1.2 lists the standard integer
types. The types marked with natural size might
change in future versions of Delphi, which means the range will also
change. The other types will always have the size and range shown.|
Type
|
Size
|
Range in Delphi 5
|
|---|---|---|
Integer |
natural
|
-2,147,483,648 .. 2,147,483,647
|
Cardinal |
|
// Return a unique number each time the function is called. function Counter: Integer; const Count: Integer = 0; begin Inc(Count); Result := Count; end;
var Count: Integer = 0; function Counter: Integer; begin Inc(Count); Result := Count; end;
var declaration instead of a typed constant. You
can force yourself to follow this good habit by disabling the
$J or $WriteableConst compiler
directive, which tells Delphi to treat all constants as constants.
The default, however, is to maintain backward compatibility and let
you change the value of a typed constant. See for more information about these compiler
directives.threadvar instead of var. The
difference is that a threadvar variable has a
separate value in each thread of a multithreaded application. An
ordinary variable has a single value that is shared among all
threads. A threadvar variable must be declared at
the unit level.
try-except-end
statement to catch the exception, or else Delphi calls
ExceptProc to handle the
exception.
try-except statement sets up an
exception handler that gets control when something goes wrong. The
try-finally statement does not
handle exceptions explicitly, but guarantees that the code in the
finally part of the statement always runs, even if
an exception is raised. Use
try-except to deal with errors.
Use try-finally when you have a
resource (such as allocated memory) that must be cleaned up properly,
no matter what happens. The
try-except statement is similar
to try-catch in C++ or Java. Standard C++ does not have
finally, but Java does. Some C++ compilers,
including Borland's, extend the C++ standard to add the same
functionality, e.g., with the __finally keyword.try-except statement can handle
all exceptions or only exceptions of a certain kind. Each
try-except statement can
declare many on sections, where each section
declares an exception class. Delphi searches the
on sections in order, trying to find an exception
class that matches, or is a superclass of, the exception
object's class. Example 1.10 shows an example
of how to use try-except.function ComputeSomething:
begin
try
PerformSomeDifficultComputation;
except
on Ex: EDivideByZero do
WriteLn('Divide by zero error');
on Ex: EOverflow do
WriteLn('Overflow error');
else
raise; // reraise the same exception, to be handled elsewhere
end;
end;Input and
Output files in a GUI application. To assign a
filename to a File or TextFile
variable, use AssignFile. Reset
and Rewrite work as they do in standard Pascal, or
you can use Append to open a file to append to its
end. The file must already exist. To close the file, use
CloseFile. Table 1.4 lists the
I/O procedures Delphi provides.|
Routine
|
Description
|
|---|---|
Append
|
Open an existing file for
appending.
|
AssignFile
or Assign
|
Assign a filename to a
File or
TextFile
variable.
|
BlockRead
|
Read data from a
file.
|
BlockWrite
|
Write data to a file.
|
CloseFile
or Close
|
Close an open
file.
|
Eof
|
Returns True for end of
file.
|
overload directive, for example:function AsString(Int: Integer): string; overload; function AsString(Float: Extended): string; overload; function AsString(Float: Extended; MinWidth: Integer):string; overload; function AsString(Bool: Boolean): string; overload;
Str := AsString(42); // call AsString(Integer) Str := AsString(42.0); // call AsString(Extended) Str := AsString(42.0, 8); // call AsString(Extended, Integer)
overload directive, but you
might need to use unit A's name to qualify calls to A's
version of the routine from unit B. A derived class that overloads a
method from an ancestor class should use the
overload directive.function AsString(Float: Extended): string; overload; function AsString(Float: Extended; MinWidth: Integer):string; overload;
TObject, all interfaces inherit from
IUnknown. The IUnknown
interface declares three methods: _AddRef,
_Release, and QueryInterface.
If you are familiar with COM, you will recognize these methods. The
first two methods manage reference counting for the lifetime of the
object that implements the interface. The third method accesses other
interfaces an object might implement._AddRef, _Release, and
QueryInterface methods is to inherit them from
TInterfacedObject or one of its derived classes,
but you are free to inherit from any other class if you wish to
define the methods yourself.
GetMem to allocate a new
instance of the record, you must call Initialize,
passing the record as an argument. Before calling
FreeMem, you must call
Finalize.TListView. You can do this by explicitly managing
the reference count. When storing the interface, be sure to cast it
to IUnknown, call _AddRef, and
cast the IUnknown reference to a raw pointer. When
extracting the data, type cast the pointer to
IUnknown. You can then use the
as operator to cast the interface to any desired
type, or just let Delphi release the interface. For convenience,
declare a couple of subroutines to do the dirty work for you, and you
can reuse these subroutines any time you need to retain an interface
reference. Example 2.15 shows an example of how you
can store an interface reference as the data associated with a list
view item.
// Cast an interface to a Pointer such that the reference // count is incremented and the interface will not be freed // until you call ReleaseIUnknown. function RefIUnknown(const Intf: IUnknown): Pointer; begin Intf._AddRef; // Increment the reference count. Result := Pointer(Intf); // Save the interface pointer. end; // Release the interface whose value is stored in the pointer P. procedure ReleaseIUnknown(P: Pointer); var Intf: IUnknown; begin Pointer(Intf) := P; // Delphi releases the interface when Intf goes out of scope. end; // When the user clicks the button, add an interface to the list. procedure TForm1.Button1Click(Sender: TObject); var Item: TListItem; begin Item := ListView1.Items.Add; Item.Caption := 'Stuff'; Item.Data := RefIUnknown(GetIntf as IUnknown); end; // When the list view is destroyed or the list item is destroyed // for any other reason, release the interface, too. procedure TForm1.ListView1Deletion(Sender: TObject; Item: TListItem); begin ReleaseIUnknown(Item.Data); end; // When the user selects the list view item, do something with the // associated interface. procedure TForm1.ListView1Click(Sender: TObject); var Intf: IMyInterface; begin Intf := IUnknown(ListView1.Selected.Data) as IMyInterface; Intf.DoSomethingUseful; end;
Application object receives Windows messages and
maps them to equivalent Delphi messages. In other words, Windows
messages are a special case of more general Delphi messages.TMessage), but if you find other uses for Delphi
messages, you don't need to feel so
constrained.
Dispatch method. Delphi looks
up the message number in the object's message table. The
message table contains pointers to all the message handlers that the
class defines. If the class does not define a message handler for the
message number, Delphi searches the parent class's message
table. The search continues until Delphi finds a message handler or
it reaches the TObject class. If the class and its
ancestor classes do not define a message handler for the message
number, Delphi calls the object's
DefaultHandler method. Window controls in the VCL
override Variants, dynamic
arrays, and interfaces automatically. For all other dynamically
allocated memory, you—the programmer—are in charge.
It's easy to be confused because it seems as though Delphi
automatically manages the memory of components, too, but that's
just a trick of the VCL.CreateThread function, you must set the
IsMultiThread variable to True.
For more information, see .
NewInstance to allocate and initialize the object.
You can override NewInstance to change the way
Delphi allocates memory for the object. For example, suppose you have
an application that frequently uses doubly linked lists. Instead of
using the general-purpose memory allocator for every node, it's
much faster to keep a chain of available nodes for reuse. Use
Delphi's memory manager only when the node list is empty. If
your application frequently allocates and frees nodes, this
special-purpose allocator can be faster than the general-purpose
allocator. Example 2.17 shows a simple implementation
of this scheme. (See for a thread-safe
version of this class.)object keyword. Old-style objects exist for
backward compatibility with Turbo Pascal, but they might be dropped
entirely from future versions of Delphi.object types are more like records than
new-style objects. Fields in an old-style object
are laid out in the same manner as in records. If the
object type does not have any virtual methods,
there is no hidden field for the VMT pointer, for example. Unlike
records, object types can use inheritance. Derived
fields appear after inherited fields. If a class declares a virtual
method, its first field is the VMT pointer, which appears after all
the inherited fields. (Unlike a new-style object, where the VMT
pointer is always first because TObject declares
virtual methods.)object type
cannot have any runtime type information. An old
object type cannot implement interfaces.object types than in new-style
class types. To create an instance of an old
object type, call the New
procedure. The newly allocated object is initialized to all zero. If
you declare a constructor, you can call it as part of the call to
New. Pass the constructor name and arguments as
the second argument to New. Similarly, you can
call a destructor when you call Dispose to free
the object instance. The destructor name and arguments are the second
argument to Dispose.
object instance dynamically. You can treat the
object type as a record type and declare
object-type variables as unit-level or local
variables. Delphi automatically initializes string, dynamic array,
and Variant fields, but does not initialize other
fields in the object instance.class types, exceptions in
old-style constructors do not automatically cause Delphi to free a
dynamically created object or call the destructor.
ClassType method returns a pointer to
the VMT.ClassType method returns a pointer to
the VMT.TVmt record. In other words,
you can convert a TClass class reference to a
pointer to a TVmt record by subtracting the size
of the record, as shown in Example 3.1.type
PVmt = ^TVmt;
TVmt = record
SelfPtr: TClass; // Points forward to the start
// of the VMT
// The following pointers point to other RTTI tables. If a class
// does not have a table, the pointer is nil. Thus, most classes
// have a nil IntfTable and AutoTable, for example.
IntfTable: PInterfaceTable; // Interface table
AutoTable: PAutoTable; // Automation table
InitTable: PInitTable; // Fields needing finalization
TypeInfo: PTypeInfo; // Properties & other info
FieldTable: PFieldTable; // Published fields
MethodTable: PMethodTable; // Published methods
DynMethodTable: PDynMethodTable; // List of dynamic methods
ClassName: PShortString; // Points to the class name
InstanceSize: LongInt; // Size of each object, in bytes
ClassParent: ^TClass; // Immediate base class
// The following fields point to special virtual methods that
// are inherited from TObject.
SafeCallException: Pointer;
AfterConstruction: Pointer;
BeforeDestruction: Pointer;
Dispatch: Pointer;
DefaultHandler: Pointer;
NewInstance: Pointer;
FreeInstance: Pointer;
Destroy: Pointer;
// Here begin the virtual method pointers.
// Each virtual method is stored as a code pointer, e.g.,
// VirtualMethodTable: array[1..Count] of Pointer;
// But the compiler does not store the count of the number of
// method pointers in the table.
end;
var
Vmt: PVmt;
begin
// To get a PVmt pointer from a class reference, cast the class
// reference to the PVmt type and subtract the size of the TVmt
// record. This is easily done with the Dec procedure:
Vmt := PVmt(SomeObject.ClassType);
Dec(Vmt);$M+
directive or by inheriting from a class that has RTTI. (See , for details.)Classes unit declares
TPersistent with the $M+
directive. TPersistent is usually used as a base
class for all Delphi classes that need published declarations. Note
that TComponent inherits from
TPersistent.nil or contains a
method reference. The method reference includes a pointer to the
method's entry point. (At design time, the IDE has no true
entry point, so it makes one up. At runtime, your application uses
the method's real entry point.) To store the value of an event
property, Delphi looks up the method address in the class's
RTTI, finds the corresponding method name, and stores the name in the
.dfm file. To load a .dfm
file, Delphi reads the method name and looks up the corresponding
method address from the class's
RTTI.
TypInfo unit declares several types and functions
that give you easy access to the published properties of an object
and other information. The Object Inspector relies on this
information to perform its magic. You can obtain a list of the
published properties of a class and get the name and type for each
property. Given an object reference, you can get or set the value of
any published property.TypeInfo function returns a pointer to a type
information record, but if you don't use the
TypInfo unit, you cannot access anything in that
record and must instead treat the result as an untyped
Pointer. The TypInfo unit
defines the real type, which is PTypeInfo, that
is, a pointer to a TTypeInfo record. The type
information record contains a type kind and the name of the type. The
type kind is an enumerated value that tells you what kind of type it
is: integer, floating point, string, etc.GetTypeData function, which returns a
PTypeData pointer. You can use the type data to
get the names of an enumerated literal, the limits of an ordinal
subrange, and more. Table 3.1 describes the data
for each type kind.
TTypeKind Literal |
Associated Data
|
|---|---|
tkArray
|
No associated data.
|
tkChar
|
Limits of character subrange.
|
tkClass
|
Class reference, parent class, unit where class is declared, and
published properties. |
type
TDynMethodTable = packed record
Count: Word;
Indexes: packed array[1..Count] of SmallInt;
Addresses: packed array[1..Count] of Pointer;
end;Indexes
array. The table is not sorted and the lookup is linear. Once a match
is found, the method at the corresponding address is invoked. If the
method number is not found, the search continues with the immediate
base class.Variants. When the object is destroyed, Delphi
must decrement the reference counts for strings, interfaces, dynamic
arrays, and free Variants and wide strings. To
keep track of this information, Delphi uses initialization records as
part of a class's RTTI. In fact, every record and array that
requires finalization has an associated initialization record, but
the compiler hides these records. The only ones you have access to
are those associated with an object's fields.TTypeKind
field to keep track of whether it is initializing a string, a record,
an array, etc.type
{ Initialization/finalization record }
PInitTable = ^TInitTable;
TInitRecord = packed record
InitTable: ^PInitTable;
Offset: LongWord; // Offset of field in object
end;
{ Initialization/finalization table }
TInitTable = packed record
{$MinEnumSize 1} // Ensure that TypeKind takes up 1 byte.
TypeKind: TTypeKind;
TypeName: packed ShortString;
DataSize: LongWord;
Count: LongWord;
// If TypeKind=tkArray, Count is the array size, but InitRecords
// has only one element; if the type kind is tkRecord, Count is the
// number of record members, and InitRecords[] has a
// record for each member. For all other types, Count=0.
InitRecords: array[1..Count] of TInitRecord;
end;OleAuto unit tells you the details of the
automated method table: The table starts with a 2-byte count,
followed by a list of automation records. Each record has a 4-byte
dispid (dispatch identifier), a pointer to a short string method
name, 4-bytes of flags, a pointer to a list of parameters, and a code
pointer. The parameter list starts with a 1-byte return type,
followed by a 1-byte count of parameters, and ends with a list of
1-byte parameter types. The parameter names are not stored. Example 3.8 shows the declarations for the automated
method table.const
{ Parameter type masks }
atTypeMask = $7F;
varStrArg = $48;
atByRef = $80;
MaxAutoEntries = 4095;
MaxAutoParams = 255;
type
TVmtAutoType = Byte;
{ Automation entry parameter list }
PAutoParamList = ^TAutoParamList;
TAutoParamList = packed record
ReturnType: TVmtAutoType;
Count: Byte;
Types: array[1..Count] of TVmtAutoType;
end;
{ Automation table entry }
PAutoEntry = ^TAutoEntry;
TAutoEntry = packed record
DispID: LongInt;
Name: PShortString;
Flags: LongInt; { Lower byte contains flags }
Params: PAutoParamList;
Address: Pointer;
end;
{ Automation table layout }
PAutoTable = ^TAutoTable;
TAutoTable = packed record
Count: LongInt;
Entries: array[1..Count] of TAutoEntry;
end;