Buy this Book
Print Book $39.99 Read it Now!
Print Book £20.95
Add to UK Cart
Reprint Licensing

Delphi in a Nutshell
Delphi in a Nutshell

By Ray Lischner
Price: $39.99 USD
£20.95 GBP

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Delphi Pascal
Delphi Pascal is an object-oriented extension of traditional Pascal. It is not quite a proper superset of ISO standard Pascal, but if you remember Pascal from your school days, you will easily pick up Delphi's extensions. Delphi is not just a fancy Pascal, though. Delphi adds powerful object-oriented features, without making the language too complicated. Delphi has classes and objects, exception handling, multithreaded programming, modular programming, dynamic and static linking, OLE automation, and much, much more.
This chapter describes Delphi's extensions to Pascal. You should already be familiar with traditional Pascal or one of the other popular extensions to Pascal, such as Object Pascal. If you already know Borland's Object Pascal from the Turbo Pascal products, you will need to learn a new object model (detailed in ), plus other new features.
Borland uses the name "Object Pascal" to refer to Delphi's programming language, but many other languages use the same name, which results in confusion. This book uses the name "Delphi Pascal" to refer to the Delphi programming language, leaving Object Pascal for the many other object-oriented variations of Pascal.
Delphi Pascal is a modular programming language, and the basic module is called a unit. To compile and link a Delphi program, you need a program source file and any number of additional units in source or object form. The program source file is usually called a project source file because the project can be a program or a library—that is, a dynamically linked library (DLL).
When Delphi links a program or library, it can statically link all the units into a single .exe or .dll file, or it can dynamically link to units that are in packages. A package is a special kind of DLL that contains one or more units and some extra logic that enables Delphi to hide the differences between a statically linked unit and a dynamically linked unit in a package. See the section Section 1.4," later in this chapter, for more information about packages.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Units
Delphi Pascal is a modular programming language, and the basic module is called a unit. To compile and link a Delphi program, you need a program source file and any number of additional units in source or object form. The program source file is usually called a project source file because the project can be a program or a library—that is, a dynamically linked library (DLL).
When Delphi links a program or library, it can statically link all the units into a single .exe or .dll file, or it can dynamically link to units that are in packages. A package is a special kind of DLL that contains one or more units and some extra logic that enables Delphi to hide the differences between a statically linked unit and a dynamically linked unit in a package. See the section Section 1.4," later in this chapter, for more information about packages.
Some units represent forms. A form is Delphi's term for a window you can edit with Delphi's GUI builder. A form description is stored in a .dfm file, which contains the form's layout, contents, and properties.
Every .dfm file has an associated .pas file, which contains the code for that form. Forms and .dfm files are part of Delphi's integrated development environment (IDE), but are not part of the formal Delphi Pascal language. Nonetheless, the language includes several features that exist solely to support Delphi's IDE and form descriptions. Read about these features in depth in .
A binary .dfm file is actually a 16-bit .res (Windows resource) file, which maintains compatibility with the first version of Delphi. Versions 2 and later produce only 32-bit programs, so Delphi's linker converts the .dfm resource to a 32-bit resource automatically. Thus, binary .dfm files are usually compatible with all versions of Delphi. Delphi 5 also supports textual .dfm files. These files are plain text and are not compatible with prior versions of Delphi, at least not without conversion back to the binary format. The only way to tell whether a
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Programs
A Delphi program looks similar to a traditional Pascal program, starting with the 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.
For compatibility with standard Pascal, Delphi allows a parameter list after the program name, but—like most modern Pascal compilers—it ignores the identifiers listed there.
In a GUI application, you cannot use the standard Pascal I/O procedures because there is no input device to read from and no output device to write to. Instead, you can compile a console application, which can read and write using standard Pascal I/O routines. (See , to learn about the $AppType directive, which tells Delphi to build a console or a GUI application.)
A program's 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.
Example 1.3. A Typical Program 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.
The 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 Application
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Libraries
A Delphi library compiles to a standard Windows DLL. A library source file looks the same as a program source file, except that it uses the library 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.
The main body of the library—its 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.
Example 1.4. DLL Attach and Detach Viewer
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.
When using a DLL, you must be careful about dynamic memory. Any memory allocated by a DLL is freed when the DLL is unloaded. Your application might retain pointers to that memory, though, which can cause access violations or worse problems if you aren't careful. The simplest solution is to use the ShareMem unit as the first unit in your application and in every library the application loads. The
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Packages
Delphi can link a unit statically with a program or library, or it can link units dynamically. To link dynamically to one or more units, you must put those units in a package, which is a special kind of DLL. When you write a program or library, you don't need to worry about how the units will be linked. If you decide to use a package, the units in the package are not linked into your .exe or .dll, but instead, Delphi compiles a reference to the package's DLL (which has the extension .bpl for Borland Package Library).
Packages avoid the problems of DLLs, namely, managing memory and class identities. Delphi keeps track of the classes defined in each unit and makes sure that the application and all associated packages use the same class identity for the same class, so the is and as operators work correctly.
Delphi's IDE uses packages to load components, custom forms, and other design-time units, such as property editors. When you write components, keep their design-time code in a design-time package, and put the actual component class in a runtime package. Applications that use your component can link statically with the component's .dcu file or link dynamically with the runtime package that contains your component. By keeping the design-time code in a separate package, you avoid linking any extraneous code into an application.
Note that the design-time package requires the runtime package because you cannot link one unit into multiple packages. Think of an application or library as a collection of units. You cannot include a unit more than once in a single program—it doesn't matter whether the units are linked statically or dynamically. Thus, if an application uses two packages, the same unit cannot be contained in both packages. That would be the equivalent of linking the unit twice.
To build a package, you need to create a .dpk file, or package source file. The .dpk file lists the units the package contains, and it also lists the other packages the new package requires. The IDE includes a convenient package editor, or you can edit the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Data Types
Delphi Pascal supports several extensions to the standard Pascal data types. Like any Pascal language, Delphi supports enumerations, sets, arrays, integer and enumerated subranges, records, and variant records. If you are accustomed to C or C++, make sure you understand these standard Pascal types, because they can save you time and headache. The differences include the following:
  • Instead of bit masks, sets are usually easier to read.
  • You can use pointers instead of arrays, but arrays are easier and offer bounds-checking.
  • Records are the equivalent of structures, and variant records are like unions.
The basic integer type is 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.
Table 1.2: Standard Integer Types
Type
Size
Range in Delphi 5
Integer
natural
-2,147,483,648 .. 2,147,483,647
Cardinal
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Variables and Constants
Unlike standard Pascal, Delphi lets you declare the type of a constant, and you can initialize a global variable to a constant value. Delphi also supports multithreaded applications by letting you declare variables that have distinct values in each thread of your application.
When you declare the type of a constant, Delphi sets aside memory for that constant and treats it as a variable. You can assign a new value to the "constant," and it keeps that value. In C and C++, this entity is called a static variable.
// Return a unique number each time the function is called.
function Counter: Integer;
const
  Count: Integer = 0;
begin
  Inc(Count);
  Result := Count;
end;
At the unit level, a variable retains its value in the same way, so you can declare it as a constant or as a variable. Another way to write the same function is as follows:
var
  Count: Integer = 0;
function Counter: Integer;
begin
  Inc(Count);
  Result := Count;
end;
The term "typed constant" is clearly a misnomer, and at the unit level, you should always use an initialized 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.
For local variables in a procedure or function, you cannot initialize variables, and typed constants are the only way to keep values that persist across different calls to the routine. You need to decide which is worse: using a typed constant or declaring the persistent variable at the unit level.
Delphi has a unique kind of variable, declared with 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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Exception Handling
Exceptions let you interrupt a program's normal flow of control. You can raise an exception in any function, procedure, or method. The exception causes control to jump to an earlier point in the same routine or in a routine farther back in the call stack. Somewhere in the stack must be a routine that uses a try-except-end statement to catch the exception, or else Delphi calls ExceptProc to handle the exception.
Delphi has two related statements for dealing with exceptions. The 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.
Like C++ and Java, Delphi's 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.
Example 1.10. Using try-except to Handle an Exception
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;
In a multithreaded application, each thread can maintain its own exception information and can raise exceptions independently from the other threads. See for details.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
File I/O
Traditional Pascal file I/O works in Delphi, but you cannot use the standard 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.
Table 1.4: File I/O Procedures and Functions
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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Functions and Procedures
Delphi supports several extensions to standard Pascal functions and procedures. You can overload routines by declaring multiple routines with the same name, but different numbers or types of parameters. You can declare default values for parameters, thereby making the parameters optional. Almost everything in this section applies equally to functions and procedures, so the term routine is used for both.
You can overload a routine name by declaring multiple routines with the same name, but with different arguments. To declare overloaded routines, use the 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;
When you call an overloaded routine, the compiler must be able to tell which routine you want to call. Therefore, the overloaded routines must take different numbers or types of arguments. For example, using the declarations above, you can tell which function to call just by comparing argument types:
Str := AsString(42);       // call AsString(Integer)
Str := AsString(42.0);     // call AsString(Extended)
Str := AsString(42.0, 8);  // call AsString(Extended, Integer)
Sometimes, unit A will declare a routine, and unit B uses unit A, but also declares a routine with the same name. The declaration in unit B does not need the 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.
Sometimes, you can use default parameters instead of overloaded routines. For example, consider the following overloaded routines:
function AsString(Float: Extended): string; overload;
function AsString(Float: Extended; MinWidth: Integer):string; overload;
Most likely, the first overloaded routine converts its floating-point argument to a string using a predefined minimum width, say, 1. In fact, you might even write the first
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: The Delphi Object Model
Delphi's support for object-oriented programming is rich and powerful. In addition to traditional classes and objects, Delphi also has interfaces (similar to those found in COM and Java), exception handling, and multithreaded programming. This chapter covers Delphi's object model in depth. You should already be familiar with standard Pascal and general principles of object-oriented programming.
Think of a class as a record on steroids. Like a record, a class describes a type that comprises any number of parts, called fields. Unlike a record, a class can also contain functions and procedures (called methods), and properties. A class can inherit from another class, in which case it inherits all the fields, methods, and properties of the ancestor class.
An object is a dynamic instance of a class. An object is always allocated dynamically, on the heap, so an object reference is like a pointer (but without the usual Pascal caret operator). When you assign an object reference to a variable, Delphi copies only the pointer, not the entire object. When your program finishes using an object, it must explicitly free the object. Delphi does not have any automatic garbage collection (but see the section Section 2.2," later in this chapter).
For the sake of brevity, the term object reference is often shortened to object, but in precise terms, the object is the chunk of memory where Delphi stores the values for all the object's fields. An object reference is a pointer to the object. The only way to use an object in Delphi is through an object reference. An object reference usually comes in the form of a variable, but it might also be a function or property that returns an object reference.
A class, too, is a distinct entity (as in Java, but unlike C++). Delphi's representation of a class is a read-only table of pointers to virtual methods and lots of information about the class. A class reference is a pointer to the table. (, describes in depth the layout of the class tables.) The most common use for a class reference is to create objects or to test the type of an object reference, but you can use class references in many other situations, including passing class references as routine parameters or returning a class reference from a function. The type of a class reference is called a
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Classes and Objects
Think of a class as a record on steroids. Like a record, a class describes a type that comprises any number of parts, called fields. Unlike a record, a class can also contain functions and procedures (called methods), and properties. A class can inherit from another class, in which case it inherits all the fields, methods, and properties of the ancestor class.
An object is a dynamic instance of a class. An object is always allocated dynamically, on the heap, so an object reference is like a pointer (but without the usual Pascal caret operator). When you assign an object reference to a variable, Delphi copies only the pointer, not the entire object. When your program finishes using an object, it must explicitly free the object. Delphi does not have any automatic garbage collection (but see the section Section 2.2," later in this chapter).
For the sake of brevity, the term object reference is often shortened to object, but in precise terms, the object is the chunk of memory where Delphi stores the values for all the object's fields. An object reference is a pointer to the object. The only way to use an object in Delphi is through an object reference. An object reference usually comes in the form of a variable, but it might also be a function or property that returns an object reference.
A class, too, is a distinct entity (as in Java, but unlike C++). Delphi's representation of a class is a read-only table of pointers to virtual methods and lots of information about the class. A class reference is a pointer to the table. (, describes in depth the layout of the class tables.) The most common use for a class reference is to create objects or to test the type of an object reference, but you can use class references in many other situations, including passing class references as routine parameters or returning a class reference from a function. The type of a class reference is called a metaclass.
Example 2.1 shows several class declarations. A class declaration is a type declaration that starts with the keyword
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Interfaces
An interface defines a type that comprises abstract virtual methods. Although a class inherits from a single base class, it can implement any number of interfaces. An interface is similar to an abstract class (that is, a class that has no fields and all of whose methods are abstract), but Delphi has extra magic to help you work with interfaces. Delphi's interfaces sometimes look like COM (Component Object Model) interfaces, but you don't need to know COM to use Delphi interfaces, and you can use interfaces for many other purposes.
You can declare a new interface by inheriting from an existing interface. An interface declaration contains method and property declarations, but no fields. Just as all classes inherit from 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.
When you declare a class that implements one or more interfaces, you must provide an implementation of all the methods declared in all the interfaces. The class can implement an interface's methods, or it can delegate the implementation to a property, whose value is an interface. The simplest way to implement the _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.
A class implements each of an interface's methods by declaring a method with the same name, arguments, and calling convention. Delphi automatically matches the class's methods with the interface's methods. If you want to use a different method name, you can redirect an interface method to a method with a different name. The redirected method must have the same arguments and calling convention as the interface method. This feature is especially important when a class implements multiple interfaces with identical method names. See the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Reference Counting
The previous section discusses how Delphi uses reference counting to manage the lifetime of interfaces. Strings and dynamic arrays also use reference counting to manage their lifetimes. The compiler generates appropriate code to keep track of when interface references, strings, and dynamic arrays are created and when the variables go out of scope and the objects, strings, and arrays must be destroyed.
Usually, the compiler can handle the reference counting automatically, and everything works the way the you expect it to. Sometimes, though, you need to give a hint to the compiler. For example, if you declare a record that contains a reference counted field, and you use 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.
Sometimes, you want to keep a reference to a string or interface after the variable goes out of scope, that is, at the end of the block where the variable is declared. For example, maybe you want to associate an interface with each item in a 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.
Example 2.15. Storing Interfaces in a List View
// 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;
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Messages
You should be familiar with Windows messages: user interactions and other events generate messages, which Windows sends to an application. An application processes messages one at a time to respond to the user and other events. Each kind of message has a unique number and two integer parameters. Sometimes a parameter is actually a pointer to a string or structure that contains more complex information. Messages form the heart of Windows event-driven architecture, and Delphi has a unique way of supporting Windows messages.
In Delphi, every object—not only window controls—can respond to messages. A message has an integer identifier and can contain any amount of additional information. In the VCL, the 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.
A Delphi message is a record where the first two bytes contain an integer message identifier, and the remainder of the record is programmer-defined. Delphi's message dispatcher never refers to any part of the message record past the message number, so you are free to store any amount or kind of information in a message record. By convention, the VCL always uses Windows-style message records (TMessage), but if you find other uses for Delphi messages, you don't need to feel so constrained.
To send a message to an object, fill in the message identifier and the rest of the message record and call the object's 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
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Memory Management
Delphi manages the memory and lifetime of strings, 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.
Memory management is thread-safe, provided you use Delphi's classes or functions to create the threads. If you go straight to the Windows API and the CreateThread function, you must set the IsMultiThread variable to True. For more information, see .
Ordinarily, when you construct an object, Delphi calls 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.)
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Old-Style Object Types
In addition to class types, Delphi supports an obsolete type that uses the object keyword. Old-style objects exist for backward compatibility with Turbo Pascal, but they might be dropped entirely from future versions of Delphi.
Old-style 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.)
An old-style object type can have private, protected, and public sections, but not published or automated sections. Because it cannot have a published section, an old object type cannot have any runtime type information. An old object type cannot implement interfaces.
Constructors and destructors work differently in old-style 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.
You don't have to allocate an old-style 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.
Unlike new-style class types, exceptions in old-style constructors do not automatically cause Delphi to free a dynamically created object or call the destructor.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Runtime Type Information
Delphi's Integrated Development Environment (IDE) depends on information provided by the compiler. This information, called Runtime Type Information (RTTI), describes some aspects of classes and other types. It's not a full reflection system such as you find in Java, but it's more complete than type identifiers in C++. For ordinary, everyday use of Delphi, you can ignore the details of RTTI and just let Delphi do its thing. Sometimes, though, you need to look under the hood and understand exactly how RTTI works.
The only difference between a published declaration and a public declaration is RTTI. Delphi stores RTTI for published fields, methods, and properties, but not for public, protected, or private declarations. Although the primary purpose of RTTI is to publish declarations for the IDE and for saving and loading .dfm files, the RTTI tables include other kinds of information. For example, virtual and dynamic methods, interfaces, and automated declarations are part of a class's RTTI. Most types also have RTTI called type information. This chapter explains all the details of RTTI.
The Virtual Method Table (VMT) stores pointers to all the virtual methods declared for a class and its base classes. The layout of the VMT is the same as in most C++ implementations (including Borland C++ and C++ Builder) and is the same format required for COM, namely a list of pointers to methods. Each virtual method of a class or its ancestor classes has an entry in the VMT.
Each class has a unique VMT. Even if a class does not define any of its own virtual methods, but only inherits methods from its base class, it has its own VMT that lists all the virtual methods it inherits. Because each VMT lists every virtual method, Delphi can compile calls to virtual methods as quick lookups in the VMT. Because each class has its own VMT, Delphi uses the VMT to identify a class. In fact, a class reference is really a pointer to a class's VMT, and the ClassType method returns a pointer to the VMT.
In addition to a table of virtual methods, the VMT includes other information about a class, such as the class name, a pointer to the VMT for the base class, and pointers to many other RTTI tables. The other RTTI pointers appear before the first virtual method in the VMT. Example 3.1 shows a record layout that is equivalent to the VMT. The actual list of virtual methods begins after the end of the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Virtual Method Table
The Virtual Method Table (VMT) stores pointers to all the virtual methods declared for a class and its base classes. The layout of the VMT is the same as in most C++ implementations (including Borland C++ and C++ Builder) and is the same format required for COM, namely a list of pointers to methods. Each virtual method of a class or its ancestor classes has an entry in the VMT.
Each class has a unique VMT. Even if a class does not define any of its own virtual methods, but only inherits methods from its base class, it has its own VMT that lists all the virtual methods it inherits. Because each VMT lists every virtual method, Delphi can compile calls to virtual methods as quick lookups in the VMT. Because each class has its own VMT, Delphi uses the VMT to identify a class. In fact, a class reference is really a pointer to a class's VMT, and the ClassType method returns a pointer to the VMT.
In addition to a table of virtual methods, the VMT includes other information about a class, such as the class name, a pointer to the VMT for the base class, and pointers to many other RTTI tables. The other RTTI pointers appear before the first virtual method in the VMT. Example 3.1 shows a record layout that is equivalent to the VMT. The actual list of virtual methods begins after the end of the 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.
Example 3.1. Structure of a VMT
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);
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Published Declarations
The only difference between a published declaration and a public one is that a published declaration tells the compiler to store information in the VMT. Only certain kinds of information can be stored, so published declarations face a number of restrictions:
  • In order to declare any published fields, methods, or properties, a class must have RTTI enabled by using the $M+ directive or by inheriting from a class that has RTTI. (See , for details.)
  • Fields must be of class type (no other types are allowed). The class type must have RTTI enabled.
  • Array properties cannot be published. The type of a published property cannot be a pointer, record, or array. If it is a set type, it must be small enough to be stored in an integer. In the current release of Delphi, that means the set can have no more than 32 members.
  • The published section cannot contain more than one overloaded method with each name. You can overload methods, but only one of the overloaded methods can be published.
The 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.
Delphi stores the names and addresses of published methods in a class's RTTI. The IDE uses this information to store the values of event properties in a .dfm file. In the IDE, each event property is either 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.
A class's RTTI stores only the published methods for that class, and not for any ancestor classes. Thus, to look up a method name or address, the lookup might fail for a derived class, in which case, the lookup continues with the base class. The
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The TypInfo Unit
The 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.
The 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.
Some types have additional type data, as returned by the 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.
Table 3.1: Type Kinds and Their Data
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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Virtual and Dynamic Methods
The VMT stores a list of pointers for virtual methods and another table in the VMT, which this section refers to as the dynamic method table, lists both dynamic methods and message handlers.
The compiler generates a small negative number for each dynamic method. This negative number is just like a message number for a message handler. To avoid conflicts with message handlers, the compiler does not let you compile a message handler whose message number falls into the range of dynamic method numbers. Once the compiler has done its work, though, any distinction between dynamic methods and message handlers is lost. They both sit in the same table and nothing indicates whether one entry is for a dynamic method and another is for a message handler.
The dynamic method table lists only the dynamic methods and message handlers that a class declares; it does not include any methods inherited from ancestor classes. The dynamic method table starts with a 2-byte count of the number of dynamic methods and message handlers, followed by a list of 2-byte method numbers, followed by a list of 4-byte method pointers. The dynamic method table is organized in this fashion (instead of having a list of records, where each record has a method number and pointer) to speed up searching for a method number. Example 3.6 shows the logical layout of a dynamic method table. As with the other tables, you cannot compile this record, because it is not real Pascal, just a description of what a dynamic method table looks like.
Example 3.6. The Layout of a Dynamic Method Table
type
  TDynMethodTable = packed record
    Count: Word;
    Indexes: packed array[1..Count] of SmallInt;
    Addresses: packed array[1..Count] of Pointer;
  end;
Dispatching a message or calling a dynamic method requires a lookup of the method or message number in the 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.
The only time you should even consider using dynamic methods is when all of the following conditions apply:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Initialization and Finalization
When Delphi constructs an object, it automatically initializes strings, dynamic arrays, interfaces, and 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.
A VMT points to an initialization table. The table contains a list of initialization records. Because arrays and records can be nested, each initialization record contains a pointer to another initialization table, which can contain initialization records, and so on. An initialization table uses a TTypeKind field to keep track of whether it is initializing a string, a record, an array, etc.
An initialization table begins with the type kind (1 byte), followed by the type name as a short string, a 4-byte size of the data being initialized, a 4-byte count for initialization records, and then an array of zero or more initialization records. An initialization record is just a pointer to a nested initialization table, followed by a 4-byte offset for the field that must be initialized. Example 3.7 shows the logical layout of the initialization table and record, but the declarations depict the logical layout without being true Pascal code.
Example 3.7. The Layout of the Initialization Table and Record
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;
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Automated Methods
The automated section of a class declaration is now obsolete because it is easier to create a COM automation server with Delphi's type library editor, using interfaces. Nonetheless, the compiler currently supports automated declarations for backward compatibility. A future version of the compiler might drop support for automated declarations.
The 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.
Example 3.8. The Layout of 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;
Additional content appearing in this section has been removed.
Purchase this book now or r