Delphi 7 Developer Guide
Delphi 7 Developer Guide
Language Guide
Borland®
Delphi ™
iii
Case statements . . . . . . . . . . . . . . . 4-25 Procedural types . . . . . . . . . . . . . . . . . 5-30
Control loops . . . . . . . . . . . . . . . . 4-27 Procedural types in statements
Repeat statements. . . . . . . . . . . . . . 4-27 and expressions . . . . . . . . . . . . . . . 5-32
While statements . . . . . . . . . . . . . . 4-27 Variant types. . . . . . . . . . . . . . . . . . . . 5-33
For statements. . . . . . . . . . . . . . . . 4-28 Variant type conversions . . . . . . . . . . . 5-34
Blocks and scope . . . . . . . . . . . . . . . . . . 4-29 Variants in expressions . . . . . . . . . . . . 5-35
Blocks . . . . . . . . . . . . . . . . . . . . . . 4-30 Variant arrays . . . . . . . . . . . . . . . . . 5-36
Scope. . . . . . . . . . . . . . . . . . . . . . . 4-30 OleVariant . . . . . . . . . . . . . . . . . . . 5-36
Naming conflicts . . . . . . . . . . . . . . 4-31 Type compatibility and identity . . . . . . . . . 5-37
Type identity . . . . . . . . . . . . . . . . . . 5-37
Chapter 5 Type compatibility . . . . . . . . . . . . . . 5-38
Data types, variables, and constants 5-1 Assignment-compatibility . . . . . . . . . . 5-38
About types. . . . . . . . . . . . . . . . . . . . . 5-1 Declaring types . . . . . . . . . . . . . . . . . . 5-39
Simple types . . . . . . . . . . . . . . . . . . . . 5-3 Variables . . . . . . . . . . . . . . . . . . . . . . 5-40
Ordinal types . . . . . . . . . . . . . . . . . . 5-3 Declaring variables . . . . . . . . . . . . . . 5-40
Integer types. . . . . . . . . . . . . . . . . 5-4 Absolute addresses . . . . . . . . . . . . 5-41
Character types . . . . . . . . . . . . . . . 5-5 Dynamic variables . . . . . . . . . . . . . 5-42
Boolean types . . . . . . . . . . . . . . . . 5-6 Thread-local variables . . . . . . . . . . . 5-42
Enumerated types . . . . . . . . . . . . . 5-6 Declared constants . . . . . . . . . . . . . . . . 5-42
Subrange types . . . . . . . . . . . . . . . 5-8 True constants . . . . . . . . . . . . . . . . . 5-43
Real types . . . . . . . . . . . . . . . . . . . . 5-10 Constant expressions . . . . . . . . . . . 5-44
String types . . . . . . . . . . . . . . . . . . . . . 5-11 Resource strings . . . . . . . . . . . . . . 5-45
Short strings. . . . . . . . . . . . . . . . . . . 5-12 Typed constants . . . . . . . . . . . . . . . . 5-45
Long strings . . . . . . . . . . . . . . . . . . . 5-13 Array constants . . . . . . . . . . . . . . 5-45
WideString . . . . . . . . . . . . . . . . . . . 5-13 Record constants . . . . . . . . . . . . . . 5-46
About extended character sets . . . . . . 5-13 Procedural constants . . . . . . . . . . . 5-46
Working with null-terminated strings . . . . 5-14 Pointer constants. . . . . . . . . . . . . . 5-47
Using pointers, arrays,
and string constants . . . . . . . . . . . 5-14 Chapter 6
Mixing Delphi strings Procedures and functions 6-1
and null-terminated strings . . . . . . . 5-16 Declaring procedures and functions . . . . . . . 6-2
Structured types . . . . . . . . . . . . . . . . . . 5-17 Procedure declarations . . . . . . . . . . . . . 6-2
Sets . . . . . . . . . . . . . . . . . . . . . . . . 5-18 Function declarations . . . . . . . . . . . . . . 6-3
Arrays . . . . . . . . . . . . . . . . . . . . . . 5-19 Calling conventions . . . . . . . . . . . . . . . 6-5
Static arrays . . . . . . . . . . . . . . . . . 5-19 Forward and interface declarations . . . . . . 6-6
Dynamic arrays . . . . . . . . . . . . . . . 5-20 External declarations . . . . . . . . . . . . . . 6-6
Array types and assignments . . . . . . . 5-22 Linking to object files . . . . . . . . . . . . 6-7
Records . . . . . . . . . . . . . . . . . . . . . 5-23 Importing functions from libraries. . . . . 6-7
Variant parts in records . . . . . . . . . . 5-24 Overloading procedures and functions . . . . 6-8
File types . . . . . . . . . . . . . . . . . . . . 5-26 Local declarations . . . . . . . . . . . . . . . 6-11
Pointers and pointer types . . . . . . . . . . . . 5-27 Nested routines . . . . . . . . . . . . . . 6-11
Overview of pointers . . . . . . . . . . . . . 5-27 Parameters . . . . . . . . . . . . . . . . . . . . . 6-11
Pointer types . . . . . . . . . . . . . . . . . . 5-29 Parameter semantics . . . . . . . . . . . . . 6-12
Character pointers . . . . . . . . . . . . . 5-29 Value and variable parameters . . . . . . 6-12
Type-checked pointers . . . . . . . . . . . 5-29 Constant parameters . . . . . . . . . . . 6-13
Other standard pointer types . . . . . . . 5-29 Out parameters. . . . . . . . . . . . . . . 6-14
Untyped parameters. . . . . . . . . . . . 6-14
iv
String parameters. . . . . . . . . . . . . . . . 6-15 Class references . . . . . . . . . . . . . . . . . . 7-24
Array parameters. . . . . . . . . . . . . . . . 6-16 Class-reference types . . . . . . . . . . . . . 7-24
Open array parameters. . . . . . . . . . . 6-16 Constructors and class references . . . . 7-25
Variant open array parameters . . . . . . 6-18 Class operators . . . . . . . . . . . . . . . . 7-25
Default parameters . . . . . . . . . . . . . . . 6-19 The is operator . . . . . . . . . . . . . . . 7-26
Default parameters and The as operator. . . . . . . . . . . . . . . 7-26
overloaded routines. . . . . . . . . . . . 6-20 Class methods . . . . . . . . . . . . . . . . . 7-26
Default parameters in forward Exceptions . . . . . . . . . . . . . . . . . . . . . 7-27
and interface declarations . . . . . . . . 6-20 When to use exceptions. . . . . . . . . . . . 7-27
Calling procedures and functions . . . . . . . . 6-20 Declaring exception types . . . . . . . . . . 7-28
Open array constructors . . . . . . . . . . . . 6-21 Raising and handling exceptions . . . . . . 7-29
Try...except statements . . . . . . . . . . 7-30
Chapter 7 Re-raising exceptions . . . . . . . . . . . 7-32
Classes and objects 7-1 Nested exceptions . . . . . . . . . . . . . 7-33
Class types . . . . . . . . . . . . . . . . . . . . . 7-2 Try...finally statements . . . . . . . . . . 7-33
Inheritance and scope . . . . . . . . . . . . . 7-3 Standard exception classes and routines . . 7-34
TObject and TClass . . . . . . . . . . . . . 7-3
Compatibility of class types . . . . . . . . 7-3 Chapter 8
Object types . . . . . . . . . . . . . . . . . 7-4 Standard routines and I/O 8-1
Visibility of class members . . . . . . . . . . 7-4 File input and output . . . . . . . . . . . . . . . . 8-1
Private, protected, and Text files . . . . . . . . . . . . . . . . . . . . . 8-3
public members . . . . . . . . . . . . . . 7-5 Untyped files. . . . . . . . . . . . . . . . . . . 8-4
Published members. . . . . . . . . . . . . 7-5 Text file device drivers . . . . . . . . . . . . . . . 8-4
Automated members . . . . . . . . . . . . 7-6 Device functions . . . . . . . . . . . . . . . . . 8-5
Forward declarations and The Open function . . . . . . . . . . . . . . 8-5
mutually dependent classes . . . . . . . . . 7-6 The InOut function . . . . . . . . . . . . . 8-6
Fields . . . . . . . . . . . . . . . . . . . . . . . . 7-7 The Flush function . . . . . . . . . . . . . . 8-6
Methods. . . . . . . . . . . . . . . . . . . . . . . 7-8 The Close function . . . . . . . . . . . . . . 8-6
Method declarations Handling null-terminated strings . . . . . . . . . 8-6
and implementations. . . . . . . . . . . . . 7-8 Wide-character strings . . . . . . . . . . . . . 8-7
Inherited . . . . . . . . . . . . . . . . . . . 7-9 Other standard routines . . . . . . . . . . . . . . 8-8
Self . . . . . . . . . . . . . . . . . . . . . . 7-9
Method binding . . . . . . . . . . . . . . . . 7-10 Part II
Static methods. . . . . . . . . . . . . . . . 7-10 Special topics
Virtual and dynamic methods. . . . . . . 7-11
Abstract methods . . . . . . . . . . . . . . 7-12 Chapter 9
Overloading methods . . . . . . . . . . . . . 7-12
Constructors . . . . . . . . . . . . . . . . . . 7-13 Libraries and packages 9-1
Destructors . . . . . . . . . . . . . . . . . . . 7-15 Calling dynamically loadable libraries . . . . . . 9-1
Message methods . . . . . . . . . . . . . . . 7-15 Static loading . . . . . . . . . . . . . . . . . 9-2
Implementing message methods . . . . . 7-16 Dynamic loading. . . . . . . . . . . . . . . 9-2
Message dispatching . . . . . . . . . . . . 7-17 Writing dynamically loadable libraries. . . . . . 9-4
Properties . . . . . . . . . . . . . . . . . . . . . . 7-17 The exports clause. . . . . . . . . . . . . . . . 9-6
Property access . . . . . . . . . . . . . . . . . 7-18 Library initialization code . . . . . . . . . . . 9-7
Array properties . . . . . . . . . . . . . . . . 7-20 Global variables in a library . . . . . . . . . . 9-8
Index specifiers . . . . . . . . . . . . . . . . . 7-21 Libraries and system variables. . . . . . . . . 9-8
Storage specifiers . . . . . . . . . . . . . . . . 7-22
Property overrides and redeclarations . . . . 7-23
v
Exceptions and runtime Chapter 11
errors in libraries . . . . . . . . . . . . . . . 9-9
Shared-memory manager
Memory management 11-1
The memory manager (Windows only) . . . . 11-1
(Windows only) . . . . . . . . . . . . . . . . 9-9
Variables . . . . . . . . . . . . . . . . . . . . 11-2
Packages . . . . . . . . . . . . . . . . . . . . . . 9-10
Internal data formats . . . . . . . . . . . . . . . 11-3
Package declarations and source files . . . . 9-10
Integer types . . . . . . . . . . . . . . . . . . 11-3
Naming packages . . . . . . . . . . . . . . 9-11
Character types . . . . . . . . . . . . . . . . 11-3
The requires clause . . . . . . . . . . . . . 9-12
Boolean types . . . . . . . . . . . . . . . . . 11-3
The contains clause . . . . . . . . . . . . . 9-12
Enumerated types . . . . . . . . . . . . . . . 11-3
Compiling packages . . . . . . . . . . . . . . 9-13
Real types . . . . . . . . . . . . . . . . . . . 11-4
Generated files . . . . . . . . . . . . . . . 9-13
The Real48 type . . . . . . . . . . . . . . 11-4
Package-specific compiler
The Single type. . . . . . . . . . . . . . . 11-4
directives . . . . . . . . . . . . . . . . . . 9-13
The Double type . . . . . . . . . . . . . . 11-5
Package-specific command-line
The Extended type. . . . . . . . . . . . . 11-5
compiler switches . . . . . . . . . . . . . 9-14
The Comp type. . . . . . . . . . . . . . . 11-5
The Currency type . . . . . . . . . . . . . 11-5
Chapter 10 Pointer types . . . . . . . . . . . . . . . . . . 11-5
Object interfaces 10-1 Short string types . . . . . . . . . . . . . . . 11-5
Interface types . . . . . . . . . . . . . . . . . . . 10-1 Long string types . . . . . . . . . . . . . . . 11-6
IInterface and inheritance . . . . . . . . . . . 10-2 Wide string types (Windows) . . . . . . . . 11-6
Interface identification . . . . . . . . . . . . . 10-3 Set types . . . . . . . . . . . . . . . . . . . . 11-7
Calling conventions for interfaces . . . . . . 10-3 Static array types . . . . . . . . . . . . . . . 11-7
Interface properties. . . . . . . . . . . . . . . 10-4 Dynamic array types . . . . . . . . . . . . . 11-7
Forward declarations . . . . . . . . . . . . . 10-4 Record types . . . . . . . . . . . . . . . . . . 11-8
Implementing interfaces . . . . . . . . . . . . . 10-4 File types . . . . . . . . . . . . . . . . . . . . 11-9
Method resolution clauses. . . . . . . . . . . 10-5 Procedural types. . . . . . . . . . . . . . . .11-10
Changing inherited implementations . . . . 10-6 Class types . . . . . . . . . . . . . . . . . . .11-10
Implementing interfaces by delegation . . . 10-7 Class reference types . . . . . . . . . . . . .11-11
Delegating to an Variant types . . . . . . . . . . . . . . . . . .11-12
interface-type property . . . . . . . . . . 10-7
Delegating to a class-type property . . . . 10-8 Chapter 12
Interface references . . . . . . . . . . . . . . . . 10-9
Interface assignment-compatibility. . . . . 10-10
Program control 12-1
Parameters and function results. . . . . . . . . 12-1
Interface typecasts . . . . . . . . . . . . . . 10-10
Parameter passing. . . . . . . . . . . . . . . 12-1
Interface querying . . . . . . . . . . . . 10-10
Register saving conventions . . . . . . . 12-3
Automation objects (Windows only) . . . . . 10-11
Function results . . . . . . . . . . . . . . . . 12-3
Dispatch interface types
Method calls . . . . . . . . . . . . . . . . . . 12-4
(Windows only) . . . . . . . . . . . . . . . 10-11
Constructors and destructors. . . . . . . 12-4
Dispatch interface methods
Exit procedures . . . . . . . . . . . . . . . . . . 12-5
(Windows only) . . . . . . . . . . . . . 10-11
Dispatch interface properties . . . . . . 10-12
Accessing Automation objects
(Windows only) . . . . . . . . . . . . . . . 10-12
Automation object
method-call syntax . . . . . . . . . . . 10-12
Dual interfaces (Windows only) . . . . . . 10-13
vi
Chapter 13 Expression elements . . . . . . . . . . . . . 13-9
Constants . . . . . . . . . . . . . . . . . . 13-9
Inline assembly code 13-1 Registers . . . . . . . . . . . . . . . . . .13-11
The asm statement . . . . . . . . . . . . . . . . . 13-1
Symbols . . . . . . . . . . . . . . . . . . .13-11
Register use . . . . . . . . . . . . . . . . . . . 13-2
Expression classes . . . . . . . . . . . . . . 13-13
Assembler statement syntax . . . . . . . . . . . 13-2
Expression types. . . . . . . . . . . . . . . 13-15
Labels . . . . . . . . . . . . . . . . . . . . . . 13-2
Expression operators . . . . . . . . . . . . 13-16
Instruction opcodes . . . . . . . . . . . . . . 13-3
Assembly procedures and functions . . . . . 13-18
RET instruction sizing . . . . . . . . . . . 13-3
Automatic jump sizing . . . . . . . . . . . 13-3
Assembly directives . . . . . . . . . . . . . . 13-4
Appendix A
Operands . . . . . . . . . . . . . . . . . . . . 13-7 Delphi grammar A-1
Expressions . . . . . . . . . . . . . . . . . . . . . 13-8
Differences between Delphi Index I-1
and assembler expressions . . . . . . . . . 13-8
vii
Tables
4.1 Equivalent symbols . . . . . . . . . . . . . 4-2 8.2 Null-terminated string functions . . . . . . 8-6
4.2 Reserved words . . . . . . . . . . . . . . . 4-3 8.3 Other standard routines . . . . . . . . . . . 8-8
4.3 Directives . . . . . . . . . . . . . . . . . . 4-4 9.1 Compiled package files . . . . . . . . . . 9-13
4.4 Binary arithmetic operators . . . . . . . . 4-7 9.2 Package-specific compiler directives . . . 9-13
4.5 Unary arithmetic operators . . . . . . . . . 4-7 9.3 Package-specific command-line
4.6 Boolean operators . . . . . . . . . . . . . . 4-8 compiler switches. . . . . . . . . . . . . . 9-14
4.7 Logical (bitwise) operators . . . . . . . . . 4-9 11.1 Long string dynamic memory
4.8 String operators . . . . . . . . . . . . . . . 4-9 layout . . . . . . . . . . . . . . . . . . . . 11-6
4.9 Character-pointer operators . . . . . . . . 4-10 11.2 Wide string dynamic memory
4.10 Set operators . . . . . . . . . . . . . . . . . 4-11 layout (Windows). . . . . . . . . . . . . . 11-6
4.11 Relational operators . . . . . . . . . . . . . 4-11 11.3 Dynamic array memory layout . . . . . . 11-7
4.12 Precedence of operators. . . . . . . . . . . 4-13 11.4 Type alignment masks . . . . . . . . . . . 11-8
5.1 Generic integer types for 32-bit 11.5 Virtual method table layout . . . . . . . .11-11
implementations of Delphi . . . . . . . . . 5-4 13.1 Built-in assembler reserved words . . . . 13-7
5.2 Fundamental integer types . . . . . . . . . 5-4 13.2 String examples and their values . . . . 13-10
5.3 Fundamental real types . . . . . . . . . . . 5-10 13.3 CPU registers . . . . . . . . . . . . . . . .13-11
5.4 Generic real types . . . . . . . . . . . . . . 5-10 13.4 Symbols recognized by the
5.5 String types. . . . . . . . . . . . . . . . . . 5-11 built-in assembler. . . . . . . . . . . . . 13-12
5.6 Selected pointer types declared 13.5 Predefined type symbols. . . . . . . . . 13-16
in System and SysUtils . . . . . . . . . . . 5-29 13.6 Precedence of built-in
5.7 Variant type conversion rules . . . . . . . 5-34 assembler expression operators . . . . . 13-16
6.1 Calling conventions . . . . . . . . . . . . . 6-5 13.7 Definitions of built-in assembler
8.1 Input and output procedures expression operators . . . . . . . . . . . 13-16
and functions. . . . . . . . . . . . . . . . . 8-1
viii
Chapter
1
Introduction
Chapter1
Using Delphi
The Delphi Language Guide is written to describe the Delphi language for use on either
the Linux or Windows operating systems. Differences in the language relating to
platform dependencies are noted where necessary.
Delphi application developers write and compile their code in the integrated
development environment (IDE). Working in the IDE allows the product to handle
many details of setting up projects and source files, such as maintenance of
dependency information among units. Delphi programming products may enforce
certain constraints on program organization that are not, strictly speaking, part of the
language specification. For example, certain file and program-naming conventions
can be avoided if you write your programs outside of the IDE and compile them from
the command prompt.
Introduction 1-1
Other sources of information
This manual generally assumes that you are working in the IDE and that you are
building applications that use the Borland Component Library (CLX). Occasionally,
however, Delphi-specific rules are distinguished from rules that apply to Object
Pascal programming.
Typographical conventions
Identifiers—that is, names of constants, variables, types, fields, properties,
procedures, functions, programs, units, libraries, and packages—appear in italics in
the text. Delphi operators, reserved words, and directives are in boldface type.
Example code and text that you would type literally (into a file or at the command
prompt) are in monospaced type.
In displayed program listings, reserved words and directives appear in boldface, just
as they do in the text:
function Calculate(X, Y: Integer): Integer;
begin
ƒ
end;
This is how the Code editor displays reserved words and directives, if you have the
Syntax Highlight option turned on.
Some program listings, like the previous example, contain ellipsis marks (... or ƒ). The
ellipses represent additional code that would be included in an actual file. They are
not meant to be copied literally.
In syntax descriptions, italics indicate placeholders for which, in real code, you would
substitute syntactically valid constructions. For example, the heading of the previous
function declaration could be represented as
function functionName(argumentList): returnType;
Syntax descriptions can also contain ellipsis marks (...) and subscripts:
function functionName(arg1, ..., argn): ReturnType;
Introduction 1-3
1-4 Delphi Language Guide
Part
I
Basic language description
Part I
The chapters in Part I present the essential language elements required for most
programming tasks. These chapters include:
• Chapter 2, “Overview”
• Chapter 3, “Programs and units”
• Chapter 4, “Syntactic elements”
• Chapter 5, “Data types, variables, and constants”
• Chapter 6, “Procedures and functions”
• Chapter 7, “Classes and objects”
• Chapter 8, “Standard routines and I/O”
2
Overview
Chapter2
Program organization
Programs are usually divided into source-code modules called units. Most programs
begin with a heading, which specifies a name for the program. The heading is
followed by an optional uses clause, then a block of declarations and statements. The
uses clause lists units that are linked into the program; these units, which can be
shared by different programs, often have uses clauses of their own.
The uses clause provides the compiler with information about dependencies among
modules. Because this information is stored in the modules themselves, most Delphi
language programs do not require makefiles, header files, or preprocessor “include”
directives. (The Project Manager generates a makefile each time a project is loaded in
the IDE, but saves these files only for project groups that include more than one
project.)
For further discussion of program structure and dependencies, see Chapter 3,
“Programs and units”.
Overview 2-1
Program organization
Various tools in the IDE store data in files of other types. Desktop settings (.dsk or
.desk) files contain information about the arrangement of windows and other
configuration options; desktop settings can be project-specific or environment-wide.
These files have no direct effect on compilation.
Compiler-generated files
The first time you build an application or a dynamically linkable library, the compiler
produces a compiled unit (.dcu on Windows, and .dcu or .dpu on Linux) file for each
new unit used in your project; all the .dcu/.dpu files in your project are then linked
to create a single executable or shared library file. The first time you build a package,
the compiler produces a file for each new unit contained in the package, and then
creates both a .dcp and a package file. (For more information about libraries and
packages, see Chapter 9.) If you use the –GD switch, the linker generates a map file
and a .drc file; the .drc file, which contains string resources, can be compiled into a
resource file.
When you build a project, individual units are not recompiled unless their source
(.pas) files have changed since the last compilation, their .dcu/.dpu files cannot be
found, you explicitly tell the compiler to reprocess them, or the interface of the unit
depends on another unit which has been changed. In fact, it is not necessary for a
unit’s source file to be present at all, as long as the compiler can find the compiled
unit file and that unit has no dependencies on other units that have changed.
Example programs
The examples that follow illustrate basic features of Delphi programming. The
examples show simple applications that would not normally be compiled from the
IDE; you can compile them from the command line.
{$APPTYPE CONSOLE}
begin
MyMessage := 'Hello world!';
Writeln(MyMessage);
end.
Overview 2-3
Example programs
The first line declares a program called Greeting. The {$APPTYPE CONSOLE} directive tells
the compiler that this is a console application, to be run from the command line. The
next line declares a variable called MyMessage, which holds a string. (Delphi has
genuine string data types.) The program then assigns the string “Hello world!” to the
variable MyMessage, and sends the contents of MyMessage to the standard output
using the Writeln procedure. (Writeln is defined implicitly in the System unit, which
the compiler automatically includes in every application.)
You can type this program into a file called Greeting.pas or Greeting.dpr and
compile it by entering
DCC32 Greeting
on a Windows-based system, or
dcc Greeting
on a Linux-based system. The resulting executable prints the message “Hello world!”
Note In the previous and subsequent examples for Linux, dcc must be in your path, or you
must enter the full path when executing the dcc executable.
Aside from its simplicity, this example differs in several important ways from
programs that you are likely to write with Borland development tools. First, it is a
console application. Borland development tools are most often used to write
applications with graphical interfaces; hence, you would not ordinarily call Writeln.
Moreover, the entire example program (save for Writeln) is in a single file. In a typical
GUI application, the program heading—the first line of the example—would be
placed in a separate project file that would not contain any of the actual application
logic, other than a few calls to routines defined in unit files.
{$APPTYPE CONSOLE}
uses Unit1;
begin
PrintMessage('Hello World!');
end.
The first line declares a program called Greeting, which, once again, is a console
application. The uses Unit1; clause tells the compiler that Greeting includes a unit
called Unit1. Finally, the program calls the PrintMessage procedure, passing to it the
string “Hello World!” Where does the PrintMessage procedure come from? It’s
defined in Unit1. Here’s the source code for Unit1, which must be saved in a file
called Unit1.pas:
unit Unit1;
interface
procedure PrintMessage(msg: string);
implementation
procedure PrintMessage(msg: string);
begin
Writeln(msg);
end;
end.
Unit1 defines a procedure called PrintMessage that takes a single string as an
argument and sends the string to the standard output. (In Pascal, routines that do not
return a value are called procedures. Routines that return a value are called functions.)
Notice that PrintMessage is declared twice in Unit1. The first declaration, under the
reserved word interface, makes PrintMessage available to other modules (such as
Greeting) that use Unit1. The second declaration, under the reserved word
implementation, actually defines PrintMessage.
You can now compile Greeting from the command line by entering
DCC32 Greeting
on a Windows-based system, or
dcc Greeting
on a Linux-based system. There’s no need to include Unit1 as a command-line
argument. When the compiler processes Greeting.dpr, it automatically looks for unit
files that the Greeting program depends on. The resulting executable does the same
thing as our first example: it prints the message “Hello world!”
A native application
Our next example is an application built using the Component Library for Cross-
Platform (CLX) components in the IDE. This program uses automatically generated
form and resource files, so you won’t be able to compile it from the source code
alone. But it illustrates important features of the Delphi Language. In addition to
multiple units, the program uses classes and objects, which are discussed in
Chapter 7, “Classes and objects”.
The program includes a project file and two new unit files. First, the project file:
program Greeting; { comments are enclosed in braces }
uses
QForms,
Unit1 in ‘Unit1.pas’ {Form1},
Unit2 in ‘Unit2.pas’ {Form2};
{$R *.res} { this directive links the project's resource file }
Overview 2-5
Example programs
begin
{ calls to Application }
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TForm2, Form2);
Application.Run;
end.
Once again, our program is called Greeting. It uses three units: QForms, which is part
of CLX; Unit1, which is associated with the application’s main form (Form1); and
Unit2, which is associated with another form (Form2).
The program makes a series of calls to an object named Application, which is an
instance of the TApplication class defined in the Forms unit. (Every project has an
automatically generated Application object.) Two of these calls invoke a TApplication
method named CreateForm. The first call to CreateForm creates Form1, an instance of
the TForm1 class defined in Unit1. The second call to CreateForm creates Form2, an
instance of the TForm2 class defined in Unit2.
Unit1 looks like this:
unit Unit1;
interface
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.xfm} { this directive links Unit1's form file and would be .dfm for a Windows-
specific application }
end.
Unit1 creates a class named TForm1 (derived from TForm) and an instance of this
class, Form1. TForm1 includes a button—Button1, an instance of TButton—and a
procedure named TForm1.Button1Click that is called at runtime whenever the user
presses Button1. TForm1.Button1Click hides Form1 and it displays Form2 (the call to
Form2.ShowModal).
NOTE In the previous example, Form2.ShowModal relies on the use of auto-created forms.
While this is fine for example code, using auto-created forms is actively discouraged.
Form2 is defined in Unit2:
unit Unit2;
interface
type
TForm2 = class(TForm)
Label1: TLabel;
CancelButton: TButton;
procedure CancelButtonClick(Sender: TObject);
end;
var
Form2: TForm2;
implementation
uses Unit1;
{$R *.xfm}
end.
Unit2 creates a class named TForm2 and an instance of this class, Form2. TForm2
includes a button (CancelButton, an instance of TButton) and a label (Label1, an
instance of TLabel). You can’t see this from the source code, but Label1 displays a
caption that reads “Hello world!” The caption is defined in Form2’s form file,
Unit2.xfm.
TForm2 declares and defines a method CancelButtonClick which will be invoked at
runtime whenever the user presses CancelButton; it closes Form2. This procedure
(along with Unit1’s TForm1.Button1Click) is known as an event handler because it
responds to events that occur while the program is running. Event handlers are
assigned to specific events by the form files for Form1 and Form2.
Overview 2-7
When the Greeting program starts, Form1 is displayed and Form2 is invisible. (By
default, only the first form created in the project file is visible at runtime. This is
called the project’s main form.) When the user presses the button on Form1, Form2,
displays the “Hello world!” greeting. When the user presses the CancelButton or the
Close button on the title bar, Form2 closes.
The following example shows the project file for a program called Editor.
1 program Editor;
2
3 uses
4 QForms, {cross-platform Form}
5 REAbout in 'REAbout.pas' {AboutBox},
6 REMain in 'REMain.pas' {MainForm};
7
8 {$R *.res}
9
10 begin
11 Application.Title := 'Text Editor';
12 Application.CreateForm(TMainForm, MainForm);
13 Application.Run;
14 end.
Line 1 contains the program heading. The uses clause is on lines 3 through 6. Line 8 is
a compiler directive that links the project’s resource file into the program. Lines 10
through 14 contain the block of statements that are executed when the program runs.
Finally, the project file, like all source files, ends with a period.
This is, in fact, a fairly typical project file. Project files are usually short, since most of
a program’s logic resides in its unit files. Project files are generated and maintained
automatically, and it is seldom necessary to edit them manually.
The block
The block contains a simple or structured statement that is executed when the
program runs. In most programs, the block consists of a compound statement—
bracketed between the reserved words begin and end—whose component
statements are simply method calls to the project’s Application object (most projects
have an Application variable that holds an instance of TApplication, TWebApplication,
or TServiceApplication). The block can also contain declarations of constants, types,
variables, procedures, and functions; these declarations must precede the statement
part of the block.
interface
implementation
initialization
{ Initialization section goes here }
finalization
{ Finalization section goes here }
end.
The unit must conclude with the word end followed by a period.
Unit names must be unique within a project. Even if their unit files are in different
directories, two units with the same name cannot be used in a single program.
So, for example, if you have defined data structures that need to be initialized, you
can do this in the initialization section.
For units in the interface uses list, the initialization sections of units used by a client
are executed in the order in which the units appear in the client’s uses clause.
If such an explicit reference appears in the project file, other source files can refer to
the unit with a simple uses clause that does not need to match case:
uses Myunit;
For more information about the placement and content of the uses clause, see
“Multiple and indirect unit references” on page 3-7 and “Circular unit references” on
page 3-8.
In the uses clause of a unit, you cannot use in to tell the compiler where to find a
source file. Every unit must be in the compiler’s search path. Moreover, unit names
must match the names of their source files.
Syntactic elements
Chapter4
4
The Delphi Language uses the ASCII character set, including the letters A through Z
and a through z, the digits 0 through 9, and other standard characters. It is not case-
sensitive. The space character (ASCII 32) and the control characters (ASCII 0 through
31—including ASCII 13, the return or end-of-line character) are called blanks.
Fundamental syntactic elements, called tokens, combine to form expressions,
declarations, and statements. A statement describes an algorithmic action that can be
executed within a program. An expression is a syntactic unit that occurs within a
statement and denotes a value. A declaration defines an identifier (such as the name of
a function or variable) that can be used in expressions and statements, and, where
appropriate, allocates memory for the identifier.
Special symbols
Special symbols are non-alphanumeric characters, or pairs of such characters, that
have fixed meanings. The following single characters are special symbols.
# $ & ' ( ) * + , – . / : ; < = > @ [ ] ^ { }
The following character pairs are also special symbols.
(* (. *) .) .. // := <= >= <>
The left bracket [ is equivalent to the character pair of left parenthesis and period (.
The right bracket ] is equivalent to the character pair of period and right parenthesis
.)
The left brace { is equivalent to the character pair of left parenthesis and asterisk (*.
The right brace } is equivalent to the character pair of right parenthesis and asterisk *)
Note %, ?, \, !, " (double quotation marks), _ (underscore), | (pipe), and ~ (tilde) are not
special characters.
Identifiers
Identifiers denote constants, variables, fields, types, properties, procedures,
functions, programs, units, libraries, and packages. An identifier can be of any
length, but only the first 255 characters are significant. An identifier must begin with
a letter or an underscore (_) and cannot contain spaces; letters, digits, and
underscores are allowed after the first character. Reserved words cannot be used as
identifiers.
Since the Delphi Language is case-insensitive, an identifier like CalculateValue could
be written in any of these ways:
CalculateValue
calculateValue
calculatevalue
CALCULATEVALUE
On Linux, the only identifiers for which case is important are unit names. Since unit
names correspond to file names, inconsistencies in case can sometimes affect
compilation (see “Unit references and the uses clause” on page 3-5).
Qualified identifiers
When you use an identifier that has been declared in more than one place, it is
sometimes necessary to qualify the identifier. The syntax for a qualified identifier is
identifier1.identifier2
where identifier1 qualifies identifier2. For example, if two units each declare a variable
called CurrentValue, you can specify that you want to access the CurrentValue in Unit2
by writing
Unit2.CurrentValue
Qualifiers can be iterated. For example,
Form1.Button1.Click
calls the Click method in Button1 of Form1.
If you don’t qualify an identifier, its interpretation is determined by the rules of scope
described in “Blocks and scope” on page 4-29.
Reserved words
The following reserved words cannot be redefined or used as identifiers.
In addition to the words in Table 4.2, private, protected, public, published, and
automated act as reserved words within object type declarations, but are otherwise
treated as directives. The words at and on also have special meanings, and should be
treated as reserved words.
Directives
Directives are words that are sensitive in specific locations within source code.
Directives have special meanings in the Delphi language, but, unlike reserved words,
appear only in contexts where user-defined identifiers cannot occur. Hence—
although it is inadvisable to do so—you can define an identifier that looks exactly
like a directive.
Numerals
Integer and real constants can be represented in decimal notation as sequences of
digits without commas or spaces, and prefixed with the + or – operator to indicate
sign. Values default to positive (so that, for example, 67258 is equivalent to +67258) and
must be within the range of the largest predefined real or integer type.
Numerals with decimal points or exponents denote reals, while other numerals
denote integers. When the character E or e occurs within a real, it means “times ten to
the power of”. For example, 7E–2 means 7 × 10–2, and 12.25e+6 and 12.25e6 both mean
12.25 × 106.
The dollar-sign prefix indicates a hexadecimal numeral—for example, $8F.
Hexadecimal numbers without a preceding '-' unary operator are taken to be positive
values. During an assignment, if a hexadecimal value lies outside the range of the
receiving type an error is raised, except in the case of the Integer (32-bit Integer)
where a warning is raised. In this case, values exceeding the positive range for
Integer are taken to be negative numbers in a manner consistent with 2's complement
integer representation.
For more information about real and integer types, see Chapter 5, “Data types,
variables, and constants”. For information about the data types of numerals, see
“True constants” on page 5-43.
Labels
A label is a standard Delphi language identifier with the exception that, unlike other
identifiers, labels can start with a digit. Numeric labels can include no more than ten
digits—that is, a numeral between 0 and 9999999999.
Labels are used in goto statements. For more information about goto statements and
labels, see “Goto statements” on page 4-20.
Character strings
A character string, also called a string literal or string constant, consists of a quoted
string, a control string, or a combination of quoted and control strings. Separators can
occur only within quoted strings.
A quoted string is a sequence of up to 255 characters from the extended ASCII
character set, written on one line and enclosed by apostrophes. A quoted string with
nothing between the apostrophes is a null string. Two sequential apostrophes in a
quoted string denote a single character, namely an apostrophe. For example,
'BORLAND' { BORLAND }
'You''ll see' { You'll see }
'''' { ' }
'' { null string }
' ' { a space }
A control string is a sequence of one or more control characters, each of which consists
of the # symbol followed by an unsigned integer constant from 0 to 255 (decimal or
hexadecimal) and denotes the corresponding ASCII character. The control string
#89#111#117
is equivalent to the quoted string
'You'
You can combine quoted strings with control strings to form larger character strings.
For example, you could use
'Line 1'#13#10'Line 2'
to put a carriage-return–line-feed between “Line 1” and “Line 2”. However, you
cannot concatenate two quoted strings in this way, since a pair of sequential
apostrophes is interpreted as a single character. (To concatenate quoted strings, use
the + operator described in “String operators” on page 4-9, or simply combine them
into a single quoted string.)
A character string’s length is the number of characters in the string. A character string
of any length is compatible with any string type and with the PChar type. A character
string of length 1 is compatible with any character type, and, when extended syntax
is enabled ({$X+}), a character string of length n ≥ 1 is compatible with zero-based
arrays and packed arrays of n characters. For more information about string types,
see Chapter 5, “Data types, variables, and constants”.
Expressions
An expression is a construction that returns a value. For example,
X { variable }
@X { address of a variable }
15 { integer constant }
InterestRate { variable }
Calc(X,Y) { function call }
X * Y { product of X and Y }
Z / (1 - Z) { quotient of Z and (1 - Z) }
X = 1.5 { Boolean }
C in Range1 { Boolean }
not Done { negation of a Boolean }
['a','b','c'] { set }
Char(48) { value typecast }
The simplest expressions are variables and constants (described in Chapter 5, “Data
types, variables, and constants”). More complex expressions are built from simpler
ones using operators, function calls, set constructors, indexes, and typecasts.
Operators
Operators behave like predefined functions that are part of the the Delphi language.
For example, the expression (X + Y) is built from the variables X and Y—called
operands—with the + operator; when X and Y represent integers or reals, (X + Y)
returns their sum. Operators include @, not, ^, *, /, div, mod, and, shl, shr, as, +, –, or,
xor, =, >, <, <>, <=, >=, in, and is.
The operators @, not, and ^ are unary (taking one operand). All other operators are
binary (taking two operands), except that + and – can function as either a unary or
binary operator. A unary operator always precedes its operand (for example, -B),
except for ^, which follows its operand (for example, P^). A binary operator is placed
between its operands (for example, A = 7).
Some operators behave differently depending on the type of data passed to them. For
example, not performs bitwise negation on an integer operand and logical negation
on a Boolean operand. Such operators appear below under multiple categories.
Except for ^, is, and in, all operators can take operands of type Variant. For details,
see “Variant types” on page 5-33.
The sections that follow assume some familiarity with Delphi data types. For
information about data types, see Chapter 5, “Data types, variables, and constants”.
For information about operator precedence in complex expressions, see “Operator
precedence rules” on page 4-13.
Arithmetic operators
Arithmetic operators, which take real or integer operands, include +, –, *, /, div, and
mod.
• The mod operator returns the remainder obtained by dividing its operands. In
other words, x mod y = x – (x div y) * y.
• A runtime error occurs when y is zero in an expression of the form x/y, x div y, or
x mod y.
Boolean operators
The Boolean operators not, and, or, and xor take operands of any Boolean type and
return a value of type Boolean.
These operations are governed by standard rules of Boolean logic. For example, an
expression of the form x and y is True if and only if both x and y are True.
Use the $B compiler directive to control evaluation mode. The default state is {$B–},
which enables short-circuit evaluation. To enable complete evaluation locally, add
the {$B+} directive to your code. You can also switch to complete evaluation on a
project-wide basis by selecting Complete Boolean Evaluation in the Compiler
Options dialog (all source units will need to be recompiled).
Note If either operand involves a variant, the compiler always performs complete
evaluation (even in the {$B–} state).
String operators
The relational operators =, <>, <, >, <=, and >= all take string operands (see
“Relational operators” on page 4-11). The + operator concatenates two strings.
Pointer operators
The relational operators <, >, <=, and >= can take operands of type PChar and
PWideChar (see “Relational operators” on page 4-11). The following operators also
take pointers as operands. For more information about pointers, see “Pointers and
pointer types” on page 5-27.
The ^ operator dereferences a pointer. Its operand can be a pointer of any type except
the generic Pointer, which must be typecast before dereferencing.
P = Q is True just in case P and Q point to the same address; otherwise, P <> Q is True.
You can use the + and – operators to increment and decrement the offset of a
character pointer. You can also use – to calculate the difference between the offsets of
two character pointers. The following rules apply.
• If I is an integer and P is a character pointer, then P + I adds I to the address given
by P; that is, it returns a pointer to the address I characters after P. (The expression
I + P is equivalent to P + I.) P – I subtracts I from the address given by P; that is, it
returns a pointer to the address I characters before P. This is true for PChar
pointers; for PWideChar pointers P + I adds SizeOf(WideChar) to P.
• If P and Q are both character pointers, then P – Q computes the difference between
the address given by P (the higher address) and the address given by Q (the lower
address); that is, it returns an integer denoting the number of characters between P
and Q. P + Q is not defined.
Set operators
The following operators take sets as operands.
Relational operators
Relational operators are used to compare two operands. The operators =, <>, <=, and
>= also apply to sets (see “Set operators” on page 4-11); = and <> also apply to
pointers (see “Pointer operators” on page 4-10).
For most simple types, comparison is straightforward. For example, I = J is True just
in case I and J have the same value, and I <> J is True otherwise. The following rules
apply to relational operators.
• Operands must be of compatible types, except that a real and an integer can be
compared.
• Strings are compared according to the ordinal values that make up the characters
that make up the string. Character types are treated as strings of length 1.
• Two packed strings must have the same number of components to be compared.
When a packed string with n components is compared to a string, the packed
string is treated as a string of length n.
• Use the operators <, >, <=, and >= to compare PChar (and PWideChar) operands
only if the two pointers point within the same character array.
• The operators = and <> can take operands of class and class-reference types. With
operands of a class type, = and <> are evaluated according the rules that apply to
pointers: C = D is True just in case C and D point to the same instance object, and C
<> D is True otherwise. With operands of a class-reference type, C = D is True just
in case C and D denote the same class, and C <> D is True otherwise. This does not
compare the data stored in the classes. For more information about classes, see
Chapter 7, “Classes and objects”.
Class operators
The operators as and is take classes and instance objects as operands; as operates on
interfaces as well. For more information, see Chapter 7, “Classes and objects” and
Chapter 10, “Object interfaces”.
The relational operators = and <> also operate on classes. See “Relational operators”
on page 4-11.
The @ operator
The @ operator returns the address of a variable, or of a function, procedure, or
method; that is, @ constructs a pointer to its operand. For more information about
pointers, see “Pointers and pointer types” on page 5-27. The following rules apply to
@.
• If X is a variable, @X returns the address of X. (Special rules apply when X is a
procedural variable; see “Procedural types in statements and expressions” on
page 5-32.) The type of @X is Pointer if the default {$T–} compiler directive is in
effect. In the {$T+} state, @X is of type ^T, where T is the type of X (This distinction
is important for assignment compatibility, see “Assignment-compatibility” on
page 5-38).
• If F is a routine (a function or procedure), @F returns F’s entry point. The type of
@F is always Pointer.
Without parentheses, however, the compiler follows operator precedence rules and
reads it as
(X = (Y or X)) = Z
which results in a compilation error unless Z is Boolean.
Parentheses often make code easier to write and to read, even when they are, strictly
speaking, superfluous. Thus the first example could be written as
X + (Y * Z)
Here the parentheses are unnecessary (to the compiler), but they spare both
programmer and reader from having to think about operator precedence.
Function calls
Because functions return a value, function calls are expressions. For example, if
you’ve defined a function called Calc that takes two integer arguments and returns an
integer, then the function call Calc(24, 47) is an integer expression. If I and J are
integer variables, then I + Calc(J, 8) is also an integer expression. Examples of
function calls include
Sum(A, 63)
Maximum(147, J)
Sin(X + Y)
Eof(F)
Volume(Radius, Height)
GetValue
TSomeObject.SomeMethod(I,J);
For more information about functions, see Chapter 6, “Procedures and functions”.
Set constructors
A set constructor denotes a set-type value. For example,
[5, 6, 7, 8]
denotes the set whose members are 5, 6, 7, and 8. The set constructor
[ 5..8 ]
could also denote the same set.
The syntax for a set constructor is
[ item1, ..., itemn ]
where each item is either an expression denoting an ordinal of the set’s base type or a
pair of such expressions with two dots (..) in between. When an item has the form x..y,
it is shorthand for all the ordinals in the range from x to y, including y; but if x is
greater than y, then x..y, the set [x..y], denotes nothing and is the empty set. The set
constructor [ ] denotes the empty set, while [x] denotes the set whose only member
is the value of x.
Indexes
Strings, arrays, array properties, and pointers to strings or arrays can be indexed. For
example, if FileName is a string variable, the expression FileName[3] returns the third
character in the string denoted by FileName, while FileName[I + 1] returns the
character immediately after the one indexed by I. For information about strings, see
“String types” on page 5-11. For information about arrays and array properties, see
“Arrays” on page 5-19 and “Array properties” on page 7-20.
Typecasts
It is sometimes useful to treat an expression as if it belonged to different type. A
typecast allows you to do this by, in effect, temporarily changing an expression’s
type. For example, Integer('A') casts the character A as an integer.
The syntax for a typecast is
typeIdentifier(expression)
If the expression is a variable, the result is called a variable typecast; otherwise, the
result is a value typecast. While their syntax is the same, different rules apply to the
two kinds of typecast.
Value typecasts
In a value typecast, the type identifier and the cast expression must both be ordinal or
pointer types. Examples of value typecasts include
Integer('A')
Char(48)
Boolean(0)
Color(2)
Longint(@Buffer)
The resulting value is obtained by converting the expression in parentheses. This
may involve truncation or extension if the size of the specified type differs from that
of the expression. The expression’s sign is always preserved.
The statement
I := Integer('A');
assigns the value of Integer('A'), which is 65, to the variable I.
A value typecast cannot be followed by qualifiers and cannot appear on the left side
of an assignment statement.
Variable typecasts
You can cast any variable to any type, provided their sizes are the same and you do
not mix integers with reals. (To convert numeric types, rely on standard functions
like Int and Trunc.) Examples of variable typecasts include
Char(I)
Boolean(Count)
TSomeDefinedType(MyVariable)
Variable typecasts can appear on either side of an assignment statement. Thus
var MyChar: char;
ƒ
Shortint(MyChar) := 122;
assigns the character z (ASCII 122) to MyChar.
You can cast variables to a procedural type. For example, given the declarations
type Func = function(X: Integer): Integer;
var
F: Func;
P: Pointer;
N: Integer;
you can make the following assignments.
F := Func(P); { Assign procedural value in P to F }
Func(P) := F; { Assign procedural value in F to P }
@F := P; { Assign pointer value in P to F }
P := @F; { Assign pointer value in F to P }
N := F(N); { Call function via F }
N := Func(P)(N); { Call function via P }
Variable typecasts can also be followed by qualifiers, as illustrated in the following
example.
type
TByteRec = record
Lo, Hi: Byte;
end;
TWordRec = record
Low, High: Word;
end;
PByte = ^Byte;
var
B: Byte;
W: Word;
L: Longint;
P: Pointer;
begin
W := $1234;
B := TByteRec(W).Lo;
TByteRec(W).Hi := 0;
L := $01234567;
W := TWordRec(L).Low;
B := TByteRec(TWordRec(L).Low).Hi;
B := PByte(L)^;
end;
In this example, TByteRec is used to access the low- and high-order bytes of a word,
and TWordRec to access the low- and high-order words of a long integer. You could
call the predefined functions Lo and Hi for the same purpose, but a variable typecast
has the advantage that it can be used on the left side of an assignment statement.
For information about typecasting pointers, see “Pointers and pointer types” on
page 5-27. For information about casting class and interface types, see “The as
operator” on page 7-26 and “Interface typecasts” on page 10-10.
Declarations
The names of variables, constants, types, fields, properties, procedures, functions,
programs, units, libraries, and packages are called identifiers. (Numeric constants like
26057 are not identifiers.) Identifiers must be declared before you can use them; the
only exceptions are a few predefined types, routines, and constants that the compiler
understands automatically, the variable Result when it occurs inside a function block,
and the variable Self when it occurs inside a method implementation.
A declaration defines an identifier and, where appropriate, allocates memory for it.
For example,
var Size: Extended;
declares a variable called Size that holds an Extended (real) value, while
function DoThis(X, Y: string): Integer;
declares a function called DoThis that takes two strings as arguments and returns an
integer. Each declaration ends with a semicolon. When you declare several variables,
constants, types, or labels at the same time, you need only write the appropriate
reserved word once:
var
Size: Extended;
Quantity: Integer;
Description: string;
The syntax and placement of a declaration depend on the kind of identifier you are
defining. In general, declarations can occur only at the beginning of a block or at the
beginning of the interface or implementation section of a unit (after the uses clause).
Specific conventions for declaring variables, constants, types, functions, and so forth
are explained in the chapters on those topics.
Hinting Directives
The “hint” directives platform, deprecated, and library may be appended to any
declaration. These directives will produce warnings at compile time. Hint directives
can be applied to type declarations, variable declarations, class and structure
declarations, field declarations within classes or records, procedure, function and
method declarations, and unit declarations.
When a hint directive appears in a unit declaration, it means that the hint applies to
everything in the unit. For example, the Windows 3.1 style OleAuto.pas unit on
Windows is completely deprecated. Any reference to that unit or any symbol in that
unit will produce a deprecation message.
The platform hinting directive on a symbol or unit indicates that it may not exist or
that the implementation may vary considerably on different platforms. The library
hinting directive on a symbol or unit indicates that the code may not exist or the
implementation may vary considerably on different library architectures.
The platform and library directives do not specify which platform or library. If your
goal is writing platform-independent code, you do not need to know which platform
a symbol is specific to; it is sufficient that the symbol be marked as specific to some
platform to let you know it may cause problems for your goal of portability.
In the case of a procedure or function declaration, the hint directive should be
separated from the rest of the declaration with a semicolon. Examples:
procedure SomeOldRoutine; stdcall; deprecated;
var VersionNumber: Real library;
type AppError = class(Exception)
ƒ
end platform;
When source code is compiled in the {$HINTS ON} {$WARNINGS ON} state, each
reference to an identifier declared with one of these directives generates an
appropriate hint or warning. Use platform to mark items that are specific to a
particular operating environment (such as Windows or Linux), deprecated to
indicate that an item is obsolete or supported only for backward compatibility, and
library to flag dependencies on a particular library or component framework (such
as CLX).
Statements
Statements define algorithmic actions within a program. Simple statements—like
assignments and procedure calls—can combine to form loops, conditional
statements, and other structured statements.
Multiple statements within a block, and in the initialization or finalization section of
a unit, are separated by semicolons.
Simple statements
A simple statement doesn’t contain any other statements. Simple statements include
assignments, calls to procedures and functions, and goto jumps.
Assignment statements
An assignment statement has the form
variable := expression
where variable is any variable reference—including a variable, variable typecast,
dereferenced pointer, or component of a structured variable—and expression is any
assignment-compatible expression (within a function block, variable can be replaced
with the name of the function being defined. See Chapter 6, “Procedures and
functions”). The := symbol is sometimes called the assignment operator.
An assignment statement replaces the current value of variable with the value of
expression. For example,
I := 3;
assigns the value 3 to the variable I. The variable reference on the left side of the
assignment can appear in the expression on the right. For example,
I := I + 1;
increments the value of I. Other assignment statements include
X := Y + Z;
Done := (I >= 1) and (I < 100);
Hue1 := [Blue, Succ(C)];
I := Sqr(J) - I * K;
Shortint(MyChar) := 122;
TByteRec(W).Hi := 0;
MyString[I] := 'A';
SomeArray[I + 1] := P^;
TMyObject.SomeProperty := True;
When you use a function call in this way, its return value is discarded.
For more information about procedures and functions, see Chapter 6, “Procedures
and functions”.
Goto statements
A goto statement, which has the form
goto label
transfers program execution to the statement marked by the specified label. To mark
a statement, you must first declare the label. Then precede the statement you want to
mark with the label and a colon:
label: statement
Declare labels like this:
label label;
You can declare several labels at once:
label label1, ..., labeln;
A label can be any valid identifier or any numeral between 0 and 9999.
The label declaration, marked statement, and goto statement must belong to the same
block. (See “Blocks and scope” on page 4-29.) Hence it is not possible to jump into or
out of a procedure or function. Do not mark more than one statement in a block with
the same label.
For example,
label StartHere;
ƒ
StartHere: Beep;
goto StartHere;
creates an infinite loop that calls the Beep procedure repeatedly.
Aditionally, it is not possible to jump into or out of a try/finally or try/except
statement.
The goto statement is generally discouraged in structured programming. It is,
however, sometimes used as a way of exiting from nested loops, as in the following
example.
procedure FindFirstAnswer;
var X, Y, Z, Count: Integer;
label FoundAnAnswer;
begin
Count := SomeConstant;
for X := 1 to Count do
for Y := 1 to Count do
for Z := 1 to Count do
if ... { some condition holds on X, Y, and Z } then
goto FoundAnAnswer;
FoundAnAnswer:
ƒ { code to execute when an answer is found }
end;
Notice that we are using goto to jump out of a nested loop. Never jump into a loop or
other structured statement, since this can have unpredictable effects.
Structured statements
Structured statements are built from other statements. Use a structured statement
when you want to execute other statements sequentially, conditionally, or
repeatedly.
• A compound or with statement simply executes a sequence of constituent
statements.
• A conditional statement—that is, an if or case statement—executes at most one of
its constituents, depending on specified criteria.
• Loop statements—including repeat, while, and for loops—execute a sequence of
constituent statements repeatedly.
• A special group of statements—including raise, try...except, and try...finally
constructions—create and handle exceptions. For information about exception
generation and handling, see “Exceptions” on page 7-27.
Compound statements
A compound statement is a sequence of other (simple or structured) statements to be
executed in the order in which they are written. The compound statement is
bracketed by the reserved words begin and end, and its constituent statements are
separated by semicolons. For example:
begin
Z := X;
X := Y;
Y := Z;
end;
The last semicolon before end is optional. So we could have written this as
begin
Z := X;
X := Y;
Y := Z
end;
Compound statements are essential in contexts where Delphi syntax requires a single
statement. In addition to program, function, and procedure blocks, they occur within
other structured statements, such as conditionals or loops. For example:
begin
I := SomeConstant;
while I > 0 do
begin
ƒ
I := I - 1;
end;
end;
You can write a compound statement that contains only a single constituent
statement; like parentheses in a complex term, begin and end sometimes serve to
disambiguate and to improve readability. You can also use an empty compound
statement to create a block that does nothing:
begin
end;
With statements
A with statement is a shorthand for referencing the fields of a record or the fields,
properties, and methods of an object. The syntax of a with statement is
with obj do statement
or
with obj1, ..., objn do statement
where obj is an expression yielding a reference to a record, object instance, class
instance, interface or class type (metaclass) instance, and statement is any simple or
structured statement. Within statement, you can refer to fields, properties, and
methods of obj using their identifiers alone—without qualifiers.
For example, given the declarations
type TDate = record
Day: Integer;
Month: Integer;
Year: Integer;
end;
var OrderDate: TDate;
you could write the following with statement.
with OrderDate do
if Month = 12 then
begin
Month := 1;
Year := Year + 1;
end
else
Month := Month + 1;
This is equivalent to
if OrderDate.Month = 12 then
begin
OrderDate.Month := 1;
OrderDate.Year := OrderDate.Year + 1;
end
else
OrderDate.Month := OrderDate.Month + 1;
If the interpretation of obj involves indexing arrays or dereferencing pointers, these
actions are performed once, before statement is executed. This makes with statements
efficient as well as concise. It also means that assignments to a variable within
statement cannot affect the interpretation of obj during the current execution of the
with statement.
Each variable reference or method name in a with statement is interpreted, if
possible, as a member of the specified object or record. If there is another variable or
method of the same name that you want to access from the with statement, you need
to prepend it with a qualifier, as in the following example.
with OrderDate do
begin
Year := Unit1.Year
ƒ
end;
When multiple objects or records appear after with, the entire statement is treated
like a series of nested with statements. Thus
with obj1, obj2, ..., objn do statement
is equivalent to
with obj1 do
with obj2 do
ƒ
with objn do
statement
In this case, each variable reference or method name in statement is interpreted, if
possible, as a member of objn; otherwise it is interpreted, if possible, as a member of
objn–1; and so forth. The same rule applies to interpreting the objs themselves, so that,
for instance, if objn is a member of both obj1 and obj2, it is interpreted as obj2.objn.
If statements
There are two forms of if statement: if...then and the if...then...else. The syntax of an
if...then statement is
if expression then statement
where expression returns a Boolean value. If expression is True, then statement is
executed; otherwise it is not. For example,
if J <> 0 then Result := I/J;
is equivalent to
if ... { expression1 } then
begin
if ... { expression2 } then
... { statement1 }
else
... { statement2 }
end;
The rule is that nested conditionals are parsed starting from the innermost
conditional, with each else bound to the nearest available if on its left. To force the
compiler to read our example in the second way, you would have to write it
explicitly as
if ... { expression1 } then
begin
if ... { expression2 } then
... { statement1 }
end
else
... { statement2 } ;
Case statements
The case statement may provide a readable alternative to deeply nested if
conditionals. A case statement has the form
case selectorExpression of
caseList1: statement1;
ƒ
caseListn: statementn;
end
where selectorExpression is any expression of an ordinal type (string types are invalid)
and each caseList is one of the following:
• A numeral, declared constant, or other expression that the compiler can evaluate
without executing your program. It must be of an ordinal type compatible with
selectorExpression. Thus 7, True, 4 + 5 * 3, 'A', and Integer('A') can all be used as
caseLists, but variables and most function calls cannot. (A few built-in functions
like Hi and Lo can occur in a caseList. See “Constant expressions” on page 5-44.)
• A subrange having the form First..Last, where First and Last both satisfy the
criterion above and First is less than or equal to Last.
• A list having the form item1, ..., itemn, where each item satisfies one of the criteria
above.
Each value represented by a caseList must be unique in the case statement; subranges
and lists cannot overlap. A case statement can have a final else clause:
case selectorExpression of
caseList1: statement1;
ƒ
caseListn: statementn;
else
statements;
end
where statements is a semicolon-delimited sequence of statements. When a case
statement is executed, at most one of statement1 ... statementn is executed. Whichever
caseList has a value equal to that of selectorExpression determines the statement to be
used. If none of the caseLists has the same value as selectorExpression, then the
statements in the else clause (if there is one) are executed.
The case statement
case I of
1..5: Caption := 'Low';
6..9: Caption := 'High';
0, 10..99: Caption := 'Out of range';
else
Caption := '';
end;
is equivalent to the nested conditional
if I in [1..5] then
Caption := 'Low'
else if I in [6..10] then
Caption := 'High'
else if (I = 0) or (I in [10..99]) then
Caption := 'Out of range'
else
Caption := '';
Other examples of case statements:
case MyColor of
Red: X := 1;
Green: X := 2;
Blue: X := 3;
Yellow, Orange, Black: X := 0;
end;
case Selection of
Done: Form1.Close;
Compute: CalculateTotal(UnitCost, Quantity);
else
Beep;
end;
Control loops
Loops allow you to execute a sequence of statements repeatedly, using a control
condition or variable to determine when the execution stops. Delphi has three kinds
of control loop: repeat statements, while statements, and for statements.
You can use the standard Break and Continue procedures to control the flow of a
repeat, while, or for statement. Break terminates the statement in which it occurs,
while Continue begins executing the next iteration of the sequence. For more
information about these procedures, see the online Help.
Repeat statements
The syntax of a repeat statement is
repeat statement1; ...; statementn; until expression
where expression returns a Boolean value. (The last semicolon before until is
optional.) The repeat statement executes its sequence of constituent statements
continually, testing expression after each iteration. When expression returns True, the
repeat statement terminates. The sequence is always executed at least once because
expression is not evaluated until after the first iteration.
Examples of repeat statements include
repeat
K := I mod J;
I := J;
J := K;
until J = 0;
repeat
Write('Enter a value (0..9): ');
Readln(I);
until (I >= 0) and (I <= 9);
While statements
A while statement is similar to a repeat statement, except that the control condition is
evaluated before the first execution of the statement sequence. Hence, if the condition
is false, the statement sequence is never executed.
The syntax of a while statement is
while expression do statement
where expression returns a Boolean value and statement can be a compound statement.
The while statement executes its constituent statement repeatedly, testing expression
before each iteration. As long as expression returns True, execution continues.
For statements
A for statement, unlike a repeat or while statement, requires you to specify explicitly
the number of iterations you want the loop to go through. The syntax of a for
statement is
for counter := initialValue to finalValue do statement
or
for counter := initialValue downto finalValue do statement
where
• counter is a local variable (declared in the block containing the for statement) of
ordinal type, without any qualifiers.
• initialValue and finalValue are expressions that are assignment-compatible with
counter.
• statement is a simple or structured statement that does not change the value of
counter.
The for statement assigns the value of initialValue to counter, then executes statement
repeatedly, incrementing or decrementing counter after each iteration. (The for...to
syntax increments counter, while the for...downto syntax decrements it.) When
counter returns the same value as finalValue, statement is executed once more and the
for statement terminates. In other words, statement is executed once for every value
in the range from initialValue to finalValue. If initialValue is equal to finalValue,
statement is executed exactly once. If initialValue is greater than finalValue in a for...to
statement, or less than finalValue in a for...downto statement, then statement is never
executed. After the for statement terminates (provided this was not forced by a break
or an exit procedure), the value of counter is undefined.
For purposes of controlling execution of the loop, the expressions initialValue and
finalValue are evaluated only once, before the loop begins. Hence the for...to
statement is almost, but not quite, equivalent to this while construction:
begin
counter := initialValue;
while counter <= finalValue do
begin
statement;
counter := Succ(counter);
end;
end
The difference between this construction and the for...to statement is that the while
loop reevaluates finalValue before each iteration. This can result in noticeably slower
performance if finalValue is a complex expression, and it also means that changes to
the value of finalValue within statement can affect execution of the loop.
Examples of for statements:
for I := 2 to 63 do
if Data[I] > Max then
Max := Data[I];
for I := ListBox1.Items.Count - 1 downto 0 do
ListBox1.Items[I] := UpperCase(ListBox1.Items[I]);
for I := 1 to 10 do
for J := 1 to 10 do
begin
X := 0;
for K := 1 to 10 do
X := X + Mat1[I, K] * Mat2[K, J];
Mat[I, J] := X;
end;
for C := Red to Blue do Check(C);
Scope
An identifier, such as a variable or function name, can be used only within the scope
of its declaration. The location of a declaration determines its scope. An identifier
declared within the declaration of a program, function, or procedure has a scope
limited to the block in which it is declared. An identifier declared in the interface
section of a unit has a scope that includes any other units or programs that use the
unit where the declaration occurs. Identifiers with narrower scope—especially
identifiers declared in functions and procedures—are sometimes called local, while
identifiers with wider scope are called global.
Naming conflicts
When one block encloses another, the former is called the outer block and the latter the
inner block. If an identifier declared in an outer block is redeclared in an inner block,
the inner declaration takes precedence over the outer one and determines the
meaning of the identifier for the duration of the inner block. For example, if you
declare a variable called MaxValue in the interface section of a unit, and then declare
another variable with the same name in a function declaration within that unit, any
unqualified occurrences of MaxValue in the function block are governed by the
second, local declaration. Similarly, a function declared within another function
creates a new, inner scope in which identifiers used by the outer function can be
redeclared locally.
The use of multiple units further complicates the definition of scope. Each unit listed
in a uses clause imposes a new scope that encloses the remaining units used and the
program or unit containing the uses clause. The first unit in a uses clause represents
the outermost scope and each succeeding unit represents a new scope inside the
previous one. If two or more units declare the same identifier in their interface
sections, an unqualified reference to the identifier selects the declaration in the
innermost scope—that is, in the unit where the reference itself occurs, or, if that unit
doesn’t declare the identifier, in the last unit in the uses clause that does declare the
identifier.
About types
There are several ways to categorize Delphi data types:
• Some types are predefined (or built-in); the compiler recognizes these automatically,
without the need for a declaration. Almost all of the types documented in this
language reference are predefined. Other types are created by declaration; these
include user-defined types and the types defined in the product libraries.
• Types can be classified as either fundamental or generic. The range and format of a
fundamental type is the same in all implementations of the Delphi language,
regardless of the underlying CPU and operating system. The range and format of
a generic type is platform-specific and could vary across different
implementations. Most predefined types are fundamental, but a handful of
integer, character, string, and pointer types are generic. It’s a good idea to use
generic types when possible, since they provide optimal performance and
Simple types
Simple types, which include ordinal types and real types, define ordered sets of
values.
Ordinal types
Ordinal types include integer, character, Boolean, enumerated, and subrange types. An
ordinal type defines an ordered set of values in which each value except the first has
a unique predecessor and each value except the last has a unique successor. Further,
each value has an ordinality which determines the ordering of the type. In most cases,
if a value has ordinality n, its predecessor has ordinality n–1 and its successor has
ordinality n+1.
• For integer types, the ordinality of a value is the value itself.
• Subrange types maintain the ordinalities of their base types.
• For other ordinal types, by default the first value has ordinality 0, the next value
has ordinality 1, and so forth. The declaration of an enumerated type can explicitly
override this default.
Several predefined functions operate on ordinal values and type identifiers. The most
important of them are summarized below.
For example, High(Byte) returns 255 because the highest value of type Byte is 255, and
Succ(2) returns 3 because 3 is the successor of 2.
The standard procedures Inc and Dec increment and decrement the value of an
ordinal variable. For example, Inc(I) is equivalent to I := Succ(I) and, if I is an
integer variable, to I := I + 1.
Integer types
An integer type represents a subset of the whole numbers. The generic integer types
are Integer and Cardinal; use these whenever possible, since they result in the best
performance for the underlying CPU and operating system. The table below gives
their ranges and storage formats for the current 32-bit Delphi compiler.
Fundamental integer types include Shortint, Smallint, Longint, Int64, Byte, Word, and
Longword.
When you increment the last value or decrement the first value of an integer type, the
result wraps around the beginning or end of the range. For example, the Shortint type
has the range –128..127; hence, after execution of the code
var I: Shortint;
ƒ
I := High(Shortint);
I := I + 1;
the value of I is –128. If compiler range-checking is enabled, however, this code
generates a runtime error.
Character types
The fundamental character types are AnsiChar and WideChar. AnsiChar values are
byte-sized (8-bit) characters ordered according to the locale character set which is
possibly multibyte. AnsiChar was originally modeled after the ANSI character set
(thus its name) but has now been broadened to refer to the current locale character
set.
WideChar characters use more than one byte to represent every character. In the
current implementations, WideChar is word-sized (16-bit) characters ordered
according to the Unicode character set (note that it could be longer in future
implementations). The first 256 Unicode characters correspond to the ANSI
characters.
Note On Linux, wchar_t widechar is 32 bits per character. The 16-bit Unicode standard
that Delphi WideChars support is a subset of the 32-bit UCS standard supported by
Linux and the GNU libraries. Delphi WideChar data must be widened to 32 bits per
character before it can be passed to an OS function as wchar_t.
The generic character type is Char, which is equivalent to AnsiChar. Because the
implementation of Char is subject to change, it’s a good idea to use the standard
function SizeOf rather than a hard-coded constant when writing programs that may
need to handle characters of different sizes.
A string constant of length 1, such as 'A', can denote a character value. The
predefined function Chr returns the character value for any integer in the range of
AnsiChar or WideChar; for example, Chr(65) returns the letter A.
Character values, like integers, wrap around when decremented or incremented past
the beginning or end of their range (unless range-checking is enabled). For example,
after execution of the code
var
Letter: Char;
I: Integer;
begin
Letter := High(Letter);
for I := 1 to 66 do
Inc(Letter);
end;
Letter has the value A (ASCII 65).
For more information about Unicode characters, see “About extended character sets”
on page 5-13 and “Working with null-terminated strings” on page 5-14.
Boolean types
The four predefined Boolean types are Boolean, ByteBool, WordBool, and LongBool.
Boolean is the preferred type. The others exist to provide compatibility with other
languages and operating system libraries.
A Boolean variable occupies one byte of memory, a ByteBool variable also occupies
one byte, a WordBool variable occupies two bytes (one word), and a LongBool variable
occupies four bytes (two words).
Boolean values are denoted by the predefined constants True and False. The
following relationships hold.
A value of type ByteBool, LongBool, or WordBool is considered True when its ordinality
is nonzero. If such a value appears in a context where a Boolean is expected, the
compiler automatically converts any value of nonzero ordinality to True.
The previous remarks refer to the ordinality of Boolean values, not to the values
themselves. In Delphi, Boolean expressions cannot be equated with integers or reals.
Hence, if X is an integer variable, the statement
if X then ...;
generates a compilation error. Casting the variable to a Boolean type is unreliable,
but each of the following alternatives will work.
if X <> 0 then ...; { use longer expression that returns Boolean value }
var OK: Boolean { use Boolean variable }
ƒ
if X <> 0 then OK := True;
if OK then ...;
Enumerated types
An enumerated type defines an ordered set of values by simply listing identifiers that
denote these values. The values have no inherent meaning. To declare an
enumerated type, use the syntax
type typeName = (val1, ..., valn)
where typeName and each val are valid identifiers. For example, the declaration
type Suit = (Club, Diamond, Heart, Spade);
defines an enumerated type called Suit whose possible values are Club, Diamond,
Heart, and Spade, where Ord(Club) returns 0, Ord(Diamond) returns 1, and so forth.
When you declare an enumerated type, you are declaring each val to be a constant of
type typeName. If the val identifiers are used for another purpose within the same
scope, naming conflicts occur. For example, suppose you declare the type
type TSound = (Click, Clack, Clock);
Unfortunately, Click is also the name of a method defined for TControl and all of the
objects in CLX that descend from it. So if you’re writing an application and you
create an event handler like
procedure TForm1.DBGrid1Enter(Sender: TObject);
var Thing: TSound;
begin
ƒ
Thing := Click;
ƒ
end;
you’ll get a compilation error; the compiler interprets Click within the scope of the
procedure as a reference to TForm’s Click method. You can work around this by
qualifying the identifier; thus, if TSound is declared in MyUnit, you would use
Thing := MyUnit.Click;
A better solution, however, is to choose constant names that are not likely to conflict
with other identifiers. Examples:
type
TSound = (tsClick, tsClack, tsClock);
TMyColor = (mcRed, mcBlue, mcGreen, mcYellow, mcOrange);
Answer = (ansYes, ansNo, ansMaybe);
You can use the (val1, ..., valn) construction directly in variable declarations, as if it
were a type name:
var MyCard: (Club, Diamond, Heart, Spade);
But if you declare MyCard this way, you can’t declare another variable within the
same scope using these constant identifiers. Thus
var Card1: (Club, Diamond, Heart, Spade);
var Card2: (Club, Diamond, Heart, Spade);
generates a compilation error. But
var Card1, Card2: (Club, Diamond, Heart, Spade);
compiles cleanly, as does
type Suit = (Club, Diamond, Heart, Spade);
var
Card1: Suit;
Card2: Suit;
Subrange types
A subrange type represents a subset of the values in another ordinal type (called the
base type). Any construction of the form Low..High, where Low and High are constant
expressions of the same ordinal type and Low is less than High, identifies a subrange
type that includes all values between Low and High. For example, if you declare the
enumerated type
type TColors = (Red, Blue, Green, Yellow, Orange, Purple, White, Black);
you can then define a subrange type like
type TMyColors = Green..White;
Here TMyColors includes the values Green, Yellow, Orange, Purple, and White.
You can use numeric constants and characters (string constants of length 1) to define
subrange types:
type
SomeNumbers = -128..127;
Caps = 'A'..'Z';
When you use numeric or character constants to define a subrange, the base type is
the smallest integer or character type that contains the specified range.
The LowerBound..UpperBound construction itself functions as a type name, so you can
use it directly in variable declarations. For example,
var SomeNum: 1..500;
declares an integer variable whose value can be anywhere in the range from 1 to 500.
The ordinality of each value in a subrange is preserved from the base type. (In the
first example, if Color is a variable that holds the value Green, Ord(Color) returns 2
regardless of whether Color is of type TColors or TMyColors.) Values do not wrap
around the beginning or end of a subrange, even if the base is an integer or character
type; incrementing or decrementing past the boundary of a subrange simply converts
the value to the base type. Hence, while
type Percentile = 0..99;
var I: Percentile;
ƒ
I := 100;
produces an error,
ƒ
I := 99;
Inc(I);
assigns the value 100 to I (unless compiler range-checking is enabled).
The use of constant expressions in subrange definitions introduces a syntactic
difficulty. In any type declaration, when the first meaningful character after = is a left
parenthesis, the compiler assumes that an enumerated type is being defined. Hence
the code
const
X = 50;
Y = 10;
type
Scale = (X - Y) * 2..(X + Y) * 2;
produces an error. Work around this problem by rewriting the type declaration to
avoid the leading parenthesis:
type
Scale = 2 * (X - Y)..(X + Y) * 2;
Real types
A real type defines a set of numbers that can be represented with floating-point
notation. The table below gives the ranges and storage formats for the fundamental
real types.
Note The six-byte Real48 type was called Real in earlier versions of Object Pascal. If you are
recompiling code that uses the older, six-byte Real type in Delphi, you may want to
change it to Real48. You can also use the {$REALCOMPATIBILITY ON} compiler
directive to turn Real back into the six-byte type.
The following remarks apply to fundamental real types.
• Real48 is maintained for backward compatibility. Since its storage format is not
native to the Intel processor architecture, it results in slower performance than
other floating-point types.
• Extended offers greater precision than other real types but is less portable. Be
careful using Extended if you are creating data files to share across platforms.
• The Comp (computational) type is native to the Intel processor architecture and
represents a 64-bit integer. It is classified as a real, however, because it does not
behave like an ordinal type. (For example, you cannot increment or decrement a
Comp value.) Comp is maintained for backward compatibility only. Use the Int64
type for better performance.
• Currency is a fixed-point data type that minimizes rounding errors in monetary
calculations. It is stored as a scaled 64-bit integer with the four least significant
digits implicitly representing decimal places. When mixed with other real types in
assignments and expressions, Currency values are automatically divided or
multiplied by 10000.
String types
A string represents a sequence of characters. Delphi supports the following
predefined string types.
AnsiString, sometimes called the long string, is the preferred type for most purposes.
String types can be mixed in assignments and expressions; the compiler
automatically performs required conversions. But strings passed by reference to a
function or procedure (as var and out parameters) must be of the appropriate type.
Strings can be explicitly cast to a different string type (see “Typecasts” on page 4-15).
The reserved word string functions like a generic type identifier. For example,
var S: string;
creates a variable S that holds a string. In the default {$H+} state, the compiler
interprets string (when it appears without a bracketed number after it) as AnsiString.
Use the {$H–} directive to turn string into ShortString.
The standard function Length returns the number of characters in a string. The
SetLength procedure adjusts the length of a string. See the online Help for details.
Comparison of strings is defined by the ordering of the characters in corresponding
positions. Between strings of unequal length, each character in the longer string
without a corresponding character in the shorter string takes on a greater-than value.
For example, “AB” is greater than “A”; that is, 'AB' > 'A' returns True. Zero-length
strings hold the lowest values.
You can index a string variable just as you would an array. If S is a string variable
and i an integer expression, S[i] represents the ith character—or, strictly speaking,
the ith byte—in S. For a ShortString or AnsiString, S[i] is of type AnsiChar; for a
WideString, S[i] is of type WideChar. For single-byte (Western) locales, MyString[2] :=
'A'; assigns the value A to the second character of MyString. The following code uses
the standard AnsiUpperCase function to convert MyString to uppercase.
var I: Integer;
begin
I := Length(MyString);
while I > 0 do
begin
MyString[I] := AnsiUpperCase(MyString[I]);
I := I - 1;
end;
end;
Be careful indexing strings in this way, since overwriting the end of a string can cause
access violations. Also, avoid passing long-string indexes as var parameters, because
this results in inefficient code.
You can assign the value of a string constant—or any other expression that returns a
string—to a variable. The length of the string changes dynamically when the
assignment is made. Examples:
MyString := 'Hello world!';
MyString := 'Hello ' + 'world';
MyString := MyString + '!';
MyString := ' '; { space }
MyString := ''; { empty string }
For more information, see “Character strings” on page 4-5 and “String operators” on
page 4-9.
Short strings
A ShortString is 0 to 255 characters long. While the length of a ShortString can change
dynamically, its memory is a statically allocated 256 bytes; the first byte stores the
length of the string, and the remaining 255 bytes are available for characters. If S is a
ShortString variable, Ord(S[0]), like Length(S), returns the length of S; assigning a
value to S[0], like calling SetLength, changes the length of S. ShortString is maintained
for backward compatibility only.
The Delphi language supports short-string types—in effect, subtypes of ShortString—
whose maximum length is anywhere from 0 to 255 characters. These are denoted by a
bracketed numeral appended to the reserved word string. For example,
var MyString: string[100];
creates a variable called MyString whose maximum length is 100 characters. This is
equivalent to the declarations
type CString = string[100];
var MyString: CString;
Variables declared in this way allocate only as much memory as the type requires—
that is, the specified maximum length plus one byte. In our example, MyString uses
101 bytes, as compared to 256 bytes for a variable of the predefined ShortString type.
When you assign a value to a short-string variable, the string is truncated if it exceeds
the maximum length for the type.
The standard functions High and Low operate on short-string type identifiers and
variables. High returns the maximum length of the short-string type, while Low
returns zero.
Long strings
AnsiString, also called a long string, represents a dynamically allocated string whose
maximum length is limited only by available memory.
A long-string variable is a pointer occupying four bytes of memory. When the
variable is empty—that is, when it contains a zero-length string—the pointer is nil
and the string uses no additional storage. When the variable is nonempty, it points a
dynamically allocated block of memory that contains the string value. The eight
bytes before the location contain a 32-bit length indicator and a 32-bit reference
count. This memory is allocated on the heap, but its management is entirely
automatic and requires no user code.
Because long-string variables are pointers, two or more of them can reference the
same value without consuming additional memory. The compiler exploits this to
conserve resources and execute assignments faster. Whenever a long-string variable
is destroyed or assigned a new value, the reference count of the old string (the
variable’s previous value) is decremented and the reference count of the new value
(if there is one) is incremented; if the reference count of a string reaches zero, its
memory is deallocated. This process is called reference-counting. When indexing is
used to change the value of a single character in a string, a copy of the string is made
if—but only if—its reference count is greater than one. This is called copy-on-write
semantics.
WideString
The WideString type represents a dynamically allocated string of 16-bit Unicode
characters. In most respects it is similar to AnsiString. On Win32, WideString is
compatible with the COM BSTR type.
Note Under Win32 WideString values are not reference-counted. Under Linux, they are.
system supports Unicode (UCS-2). The Linux operating system supports UCS-4, a
superset of UCS-2. Borland’s RAD products support UCS-2 on both platforms.
The Delphi language supports single-byte and multibyte characters and strings
through the Char, PChar, AnsiChar, PAnsiChar, and AnsiString types. Indexing of
multibyte strings is not reliable, since S[i] represents the ith byte (not necessarily the
ith character) in S. However, the standard string-handling functions have multibyte-
enabled counterparts that also implement locale-specific ordering for characters.
(Names of multibyte functions usually start with Ansi-. For example, the multibyte
version of StrPos is AnsiStrPos.) Multibyte character support is operating-system
dependent and based on the current locale.
Delphi supports Unicode characters and strings through the WideChar, PWideChar,
and WideString types.
The StrUpper function illustrates the use of pointer indexing to iterate through a null-
terminated string:
function StrUpper(Dest, Source: PChar; MaxLen: Integer): PChar;
var
I: Integer;
begin
I := 0;
while (I < MaxLen) and (Source[I] <> #0) do
begin
Dest[I] := UpCase(Source[I]);
Inc(I);
end;
Dest[I] := #0;
Result := Dest;
end;
Structured types
Instances of a structured type hold more than one value. Structured types include
sets, arrays, records, and files as well as class, class-reference, and interface types. (For
information about class and class-reference types, see Chapter 7, “Classes and
objects.” For information about interfaces, see Chapter 10, “Object interfaces”).
Except for sets, which hold ordinal values only, structured types can contain other
structured types; a type can have unlimited levels of structuring.
By default, the values in a structured type are aligned on word or double-word
boundaries for faster access. When you declare a structured type, you can include the
reserved word packed to implement compressed data storage. For example,
type TNumbers = packed array[1..100] of Real;
Using packed slows data access and, in the case of a character array, affects type
compatibility (for more information, see Chapter 11, “Memory management”).
Sets
A set is a collection of values of the same ordinal type. The values have no inherent
order, nor is it meaningful for a value to be included twice in a set.
The range of a set type is the power set of a specific ordinal type, called the base type;
that is, the possible values of the set type are all the subsets of the base type,
including the empty set. The base type can have no more than 256 possible values,
and their ordinalities must fall between 0 and 255. Any construction of the form
set of baseType
where baseType is an appropriate ordinal type, identifies a set type.
Because of the size limitations for base types, set types are usually defined with
subranges. For example, the declarations
type
TSomeInts = 1..250;
TIntSet = set of TSomeInts;
create a set type called TIntSet whose values are collections of integers in the range
from 1 to 250. You could accomplish the same thing with
type TIntSet = set of 1..250;
Given this declaration, you can create a sets like this:
var Set1, Set2: TIntSet;
ƒ
Set1 := [1, 3, 5, 7, 9];
Set2 := [2, 4, 6, 8, 10]
You can also use the set of ... construction directly in variable declarations:
var MySet: set of 'a'..'z';
ƒ
MySet := ['a','b','c'];
Other examples of set types include
set of Byte
set of (Club, Diamond, Heart, Spade)
set of Char;
The in operator tests set membership:
if 'a' in MySet then ... { do something } ;
Every set type can hold the empty set, denoted by []. For more information about
sets, see “Set constructors” on page 4-14 and “Set operators” on page 4-11.
Arrays
An array represents an indexed collection of elements of the same type (called the
base type). Because each element has a unique index, arrays, unlike sets, can
meaningfully contain the same value more than once. Arrays can be allocated
statically or dynamically.
Static arrays
Static array types are denoted by constructions of the form
array[indexType1, ..., indexTypen] of baseType
where each indexType is an ordinal type whose range does not exceed 2GB. Since the
indexTypes index the array, the number of elements an array can hold is limited by
the product of the sizes of the indexTypes. In practice, indexTypes are usually integer
subranges.
In the simplest case of a one-dimensional array, there is only a single indexType. For
example,
var MyArray: array[1..100] of Char;
declares a variable called MyArray that holds an array of 100 character values. Given
this declaration, MyArray[3] denotes the third character in MyArray. If you create a
static array but don’t assign values to all its elements, the unused elements are still
allocated and contain random data; they are like uninitialized variables.
A multidimensional array is an array of arrays. For example,
type TMatrix = array[1..10] of array[1..50] of Real;
is equivalent to
type TMatrix = array[1..10, 1..50] of Real;
Whichever way TMatrix is declared, it represents an array of 500 real values. A
variable MyMatrix of type TMatrix can be indexed like this: MyMatrix[2,45]; or like
this: MyMatrix[2][45]. Similarly,
packed array[Boolean,1..10,TShoeSize] of Integer;
is equivalent to
packed array[Boolean] of packed array[1..10] of packed array[TShoeSize] of Integer;
The standard functions Low and High operate on array type identifiers and variables.
They return the low and high bounds of the array’s first index type. The standard
function Length returns the number of elements in the array’s first dimension.
A one-dimensional, packed, static array of Char values is called a packed string.
Packed-string types are compatible with string types and with other packed-string
types that have the same number of elements. See “Type compatibility and identity”
on page 5-37.
An array type of the form array[0..x] of Char is called a zero-based character array.
Zero-based character arrays are used to store null-terminated strings and are
compatible with PChar values. See “Working with null-terminated strings” on
page 5-14.
Dynamic arrays
Dynamic arrays do not have a fixed size or length. Instead, memory for a dynamic
array is reallocated when you assign a value to the array or pass it to the SetLength
procedure. Dynamic-array types are denoted by constructions of the form
array of baseType
For example,
var MyFlexibleArray: array of Real;
declares a one-dimensional dynamic array of reals. The declaration does not allocate
memory for MyFlexibleArray. To create the array in memory, call SetLength. For
example, given the previous declaration,
SetLength(MyFlexibleArray, 20);
allocates an array of 20 reals, indexed 0 to 19. Dynamic arrays are always integer-
indexed, always starting from 0.
Dynamic-array variables are implicitly pointers and are managed by the same
reference-counting technique used for long strings. To deallocate a dynamic array,
assign nil to a variable that references the array or pass the variable to Finalize; either
of these methods disposes of the array, provided there are no other references to it.
Dynamic arrays are automatically released when their reference-count drops to zero.
Dynamic arrays of length 0 have the value nil. Do not apply the dereference operator
(^) to a dynamic-array variable or pass it to the New or Dispose procedure.
If X and Y are variables of the same dynamic-array type, X := Y points X to the same
array as Y. (There is no need to allocate memory for X before performing this
operation.) Unlike strings and static arrays, COPY-ON-WRITE is not employed for
dynamic arrays, so they are not automatically copied before they are written to. For
example, after this code executes,
var
A, B: array of Integer;
begin
SetLength(A, 1);
A[0] := 1;
B := A;
B[0] := 2;
end;
the value of A[0] is 2. (If A and B were static arrays, A[0] would still be 1.)
Assigning to a dynamic-array index (for example, MyFlexibleArray[2] := 7) does not
reallocate the array. Out-of-range indexes are not reported at compile time.
In contrast, to make an independent copy of a dynamic array, you must use the
global Copy function:
var
A, B: array of Integer;
begin
SetLength(A, 1);
A[0] := 1;
B := Copy(A);
B[0] := 2; { B[0] <> A[0] }
end;
When dynamic-array variables are compared, their references are compared, not
their array values. Thus, after execution of the code
var
A, B: array of Integer;
begin
SetLength(A, 1);
SetLength(B, 1);
A[0] := 2;
B[0] := 2;
end;
A = B returns False but A[0] = B[0] returns True.
To truncate a dynamic array, pass it to SetLength, or pass it to Copy and assign the
result back to the array variable. (The SetLength procedure is usually faster.) For
example, if A is a dynamic array, A := SetLength(A, 0, 20) truncates all but the first 20
elements of A.
Once a dynamic array has been allocated, you can pass it to the standard functions
Length, High, and Low. Length returns the number of elements in the array, High
returns the array’s highest index (that is, Length–1), and Low returns 0. In the case of a
zero-length array, High returns –1 (with the anomalous consequence that High <
Low).
Note In some function and procedure declarations, array parameters are represented as
array of baseType, without any index types specified. For example,
function CheckStrings(A: array of string): Boolean;
This indicates that the function operates on all arrays of the specified base type,
regardless of their size, how they are indexed, or whether they are allocated statically
or dynamically. See “Open array parameters” on page 6-16.
Records
A record (analogous to a structure in some languages) represents a heterogeneous set
of elements. Each element is called a field; the declaration of a record type specifies a
name and type for each field. The syntax of a record type declaration is
type recordTypeName = record
fieldList1: type1;
ƒ
fieldListn: typen;
end
where recordTypeName is a valid identifier, each type denotes a type, and each fieldList
is a valid identifier or a comma-delimited list of identifiers. The final semicolon is
optional.
For example, the following declaration creates a record type called TDateRec.
type
TDateRec = record
Year: Integer;
Month: (Jan, Feb, Mar, Apr, May, Jun,
Jul, Aug, Sep, Oct, Nov, Dec);
Day: 1..31;
end;
Each TDateRec contains three fields: an integer value called Year, a value of an
enumerated type called Month, and another integer between 1 and 31 called Day. The
identifiers Year, Month, and Day are the field designators for TDateRec, and they behave
like variables. The TDateRec type declaration, however, does not allocate any
memory for the Year, Month, and Day fields; memory is allocated when you
instantiate the record, like this:
var Record1, Record2: TDateRec;
This variable declaration creates two instances of TDateRec, called Record1 and
Record2.
You can access the fields of a record by qualifying the field designators with the
record’s name:
Record1.Year := 1904;
Record1.Month := Jun;
Record1.Day := 16;
type
TShapeList = (Rectangle, Triangle, Circle, Ellipse, Other);
TFigure = record
case TShapeList of
Rectangle: (Height, Width: Real);
Triangle: (Side1, Side2, Angle: Real);
Circle: (Radius: Real);
Ellipse, Other: ();
end;
For each record instance, the compiler allocates enough memory to hold all the fields
in the largest variant. The optional tag and the constantLists (like Rectangle, Triangle,
and so forth in the last example) play no role in the way the compiler manages the
fields; they are there only for the convenience of the programmer.
The second reason for variant parts is that they let you treat the same data as
belonging to different types, even in cases where the compiler would not allow a
typecast. For example, if you have a 64-bit Real as the first field in one variant and a
32-bit Integer as the first field in another, you can assign a value to the Real field and
then read back the first 32 bits of it as the value of the Integer field (passing it, say, to a
function that requires integer parameters).
File types
A file a sequence of elements of the same type. Standard I/O routines use the
predefined TextFile or Text type, which represents a file containing characters
organized into lines. For more information about file input and output, see
Chapter 8, “Standard routines and I/O”.
To declare a file type, use the syntax
type fileTypeName = file of type
where fileTypeName is any valid identifier and type is a fixed-size type. Pointer
types—whether implicit or explicit—are not allowed, so a file cannot contain
dynamic arrays, long strings, classes, objects, pointers, variants, other files, or
structured types that contain any of these.
For example,
type
PhoneEntry = record
FirstName, LastName: string[20];
PhoneNumber: string[15];
Listed: Boolean;
end;
PhoneList = file of PhoneEntry;
declares a file type for recording names and telephone numbers.
You can also use the file of ... construction directly in a variable declaration. For
example,
var List1: file of PhoneEntry;
The word file by itself indicates an untyped file:
var DataFile: file;
For more information, see “Untyped files” on page 8-4.
Files are not allowed in arrays or records.
Overview of pointers
To see how pointers work, look at the following example.
1 var
2 X, Y: Integer; // X and Y are Integer variables
3 P: ^Integer; // P points to an Integer
4 begin
5 X := 17; // assign a value to X
6 P := @X; // assign the address of X to P
7 Y := P^; // dereference P; assign the result to Y
8 end;
Line 2 declares X and Y as variables of type Integer. Line 3 declares P as a pointer to
an Integer value; this means that P can point to the location of X or Y. Line 5 assigns a
value to X, and line 6 assigns the address of X (denoted by @X) to P. Finally, line 7
retrieves the value at the location pointed to by P (denoted by ^P) and assigns it to Y.
After this code executes, X and Y have the same value, namely 17.
The @ operator, which we have used here to take the address of a variable, also
operates on functions and procedures. For more information, see “The @ operator”
on page 4-12 and “Procedural types in statements and expressions” on page 5-32.
The symbol ^ has two purposes, both of which are illustrated in our example. When
it appears before a type identifier—
^typeName
—it denotes a type that represents pointers to variables of type typeName. When it
appears after a pointer variable—
pointer^
—it dereferences the pointer; that is, it returns the value stored at the memory address
held by the pointer.
Our example may seem like a roundabout way of copying the value of one variable
to another—something that we could have accomplished with a simple assignment
statement. But pointers are useful for several reasons. First, understanding pointers
will help you to understand the Delphi language, since pointers often operate behind
the scenes in code where they don’t appear explicitly. Any data type that requires
large, dynamically allocated blocks of memory uses pointers. Long-string variables,
for instance, are implicitly pointers, as are class instance variables. Moreover, some
advanced programming techniques require the use of pointers.
Finally, pointers are sometimes the only way to circumvent Delphi’s strict data
typing. By referencing a variable with an all-purpose Pointer, casting the Pointer to a
more specific type, and then dereferencing it, you can treat the data stored by any
variable as if it belonged to any type. For example, the following code assigns data
stored in a real variable to an integer variable.
type
PInteger = ^Integer;
var
R: Single;
I: Integer;
P: Pointer;
PI: PInteger;
begin
ƒ
P := @R;
PI := PInteger(P);
I := PI^;
end;
Of course, reals and integers are stored in different formats. This assignment simply
copies raw binary data from R to I, without converting it.
In addition to assigning the result of an @ operation, you can use several standard
routines to give a value to a pointer. The New and GetMem procedures assign a
memory address to an existing pointer, while the Addr and Ptr functions return a
pointer to a specified address or variable.
Dereferenced pointers can be qualified and can function as qualifiers, as in the
expression P1^.Data^.
The reserved word nil is a special constant that can be assigned to any pointer. When
nil is assigned to a pointer, the pointer doesn’t reference anything.
Pointer types
You can declare a pointer to any type, using the syntax
type pointerTypeName = ^type
When you define a record or other data type, it’s a common practice also to define a
pointer to that type. This makes it easy to manipulate instances of the type without
copying large blocks of memory.
Standard pointer types exist for many purposes. The most versatile is Pointer, which
can point to data of any kind. But a Pointer variable cannot be dereferenced; placing
the ^ symbol after a Pointer variable causes a compilation error. To access the data
referenced by a Pointer variable, first cast it to another pointer type and then
dereference it.
Character pointers
The fundamental types PAnsiChar and PWideChar represent pointers to AnsiChar and
WideChar values, respectively. The generic PChar represents a pointer to a Char (that
is, in its current implementation, to an AnsiChar). These character pointers are used to
manipulate null-terminated strings. (See “Working with null-terminated strings” on
page 5-14.)
Type-checked pointers
The $T compiler directive controls the types of pointer values generated by the @
operator. This directive takes the form of:
{$T+} or {$T-}
In the {$T-} state, the result type of the @ operator is always an untyped pointer that
is compatible with all other pointer types. When @ is applied to a variable reference
in the {$T+} state, the type of the result is ^T, where T is compatible only with
pointers to the type of the variable.
Table 5.6 Selected pointer types declared in System and SysUtils (continued)
Pointer type Points to variables of type
PShortString ShortString. Useful when porting legacy code that uses the old PString
type.
PTextBuf TTextBuf (declared in SysUtils). TTextBuf is the internal buffer type in a
TTextRec file record.)
PVarRec TVarRec (declared in System)
PVariant Variant
PWideString WideString
PWordArray TWordArray (declared in SysUtils). Used to typecast dynamically
allocated memory for arrays of 2-byte values.
Procedural types
Procedural types allow you to treat procedures and functions as values that can be
assigned to variables or passed to other procedures and functions. For example,
suppose you define a function called Calc that takes two integer parameters and
returns an integer:
function Calc(X,Y: Integer): Integer;
You can assign the Calc function to the variable F:
var F: function(X,Y: Integer): Integer;
F := Calc;
If you take any procedure or function heading and remove the identifier after the
word procedure or function, what’s left is the name of a procedural type. You can
use such type names directly in variable declarations (as in the previous example) or
to declare new types:
type
TIntegerFunction = function: Integer;
TProcedure = procedure;
TStrProc = procedure(const S: string);
TMathFunc = function(X: Double): Double;
var
F: TIntegerFunction; { F is a parameterless function that returns an integer }
Proc: TProcedure; { Proc is a parameterless procedure }
SP: TStrProc; { SP is a procedure that takes a string parameter }
M: TMathFunc; { M is a function that takes a Double (real) parameter
and returns a Double }
procedure FuncProc(P: TIntegerFunction); { FuncProc is a procedure whose only parameter
is a parameterless integer-valued function }
The previous variables are all procedure pointers—that is, pointers to the address of a
procedure or function. If you want to reference a method of an instance object (see
Chapter 7, “Classes and objects”), you need to add the words of object to the
procedural type name. For example
type
TMethod = procedure of object;
TNotifyEvent = procedure(Sender: TObject) of object;
These types represent method pointers. A method pointer is really a pair of pointers;
the first stores the address of a method, and the second stores a reference to the object
the method belongs to. Given the declarations
type
TNotifyEvent = procedure(Sender: TObject) of object;
TMainForm = class(TForm)
procedure ButtonClick(Sender: TObject);
ƒ
end;
var
MainForm: TMainForm;
OnClick: TNotifyEvent
we could make the following assignment.
OnClick := MainForm.ButtonClick;
Two procedural types are compatible if they have
• the same calling convention,
• the same return value (or no return value), and
• the same number of parameters, with identically typed parameters in
corresponding positions. (Parameter names do not matter.)
Procedure pointer types are always incompatible with method pointer types. The
value nil can be assigned to any procedural type.
Nested procedures and functions (routines declared within other routines) cannot be
used as procedural values, nor can predefined procedures and functions. If you want
to use a predefined routine like Length as a procedural value, write a wrapper for it:
function FLength(S: string): Integer;
begin
Result := Length(S);
end;
The @ operator can also be used to assign an untyped pointer value to a procedural
variable. For example,
var StrComp: function(Str1, Str2: PChar): Integer;
ƒ
@StrComp := GetProcAddress(KernelHandle, 'lstrcmpi');
calls the GetProcAddress function and points StrComp to the result.
Any procedural variable can hold the value nil, which means that it points to
nothing. But attempting to call a nil-valued procedural variable is an error. To test
whether a procedural variable is assigned, use the standard function Assigned:
if Assigned(OnClick) then OnClick(X);
Variant types
Sometimes it is necessary to manipulate data whose type varies or cannot be
determined at compile time. In these cases, one option is to use variables and
parameters of type Variant, which represent values that can change type at runtime.
Variants offer greater flexibility but consume more memory than regular variables,
and operations on them are slower than on statically bound types. Moreover, illicit
operations on variants often result in runtime errors, where similar mistakes with
regular variables would have been caught at compile time. You can also create
custom variant types.
By default, Variants can hold values of any type except records, sets, static arrays,
files, classes, class references, and pointers. In other words, variants can hold
anything but structured types and pointers. They can hold interfaces, whose methods
and properties can be accessed through them. (See Chapter 10, “Object interfaces”.)
They can hold dynamic arrays, and they can hold a special kind of static array called
a variant array. (See “Variant arrays” on page 5-36.) Variants can mix with other
variants and with integer, real, string, and Boolean values in expressions and
assignments; the compiler automatically performs type conversions.
Variants that contain strings cannot be indexed. That is, if V is a variant that holds a
string value, the construction V[1] causes a runtime error.
You can define custom Variants that extend the Variant type to hold arbitrary values.
For example, you can define a Variant string type that allows indexing or that holds a
particular class reference, record type, or static array. Custom Variant types are
defined by creating descendants to the TCustomVariantType class.
Note This, and almost all variant functionality, is implemented in the Variants unit.
A variant occupies 16 bytes of memory and consists of a type code and a value, or
pointer to a value, of the type specified by the code. All variants are initialized on
creation to the special value Unassigned. The special value Null indicates unknown or
missing data.
The standard function VarType returns a variant’s type code. The varTypeMask
constant is a bit mask used to extract the code from VarType’s return value, so that,
for example,
VarType(V) and varTypeMask = varDouble
returns True if V contains a Double or an array of Double. (The mask simply hides the
first bit, which indicates whether the variant holds an array.) The TVarData record
type defined in the System unit can be used to typecast variants and gain access to
their internal representation.
Out-of-range assignments often result in the target variable getting the highest value
in its range. Invalid variant operations, assignments or casts raise an EVariantError
exception or an exception class decending from EVariantError.
Special conversion rules apply to the TDateTime real type declared in the System unit.
When a TDateTime is converted to any other type, it treated as a normal Double. When
an integer, real, or Boolean is converted to a TDateTime, it is first converted to a
Double, then read as a date-time value. When a string is converted to a TDateTime, it is
interpreted as a date-time value using the regional settings. When an Unassigned
value is converted to TDateTime, it is treated like the real or integer value 0.
Converting a Null value to TDateTime raises an exception.
On Windows, if a variant references a COM interface, any attempt to convert it reads
the object’s default property and converts that value to the requested type. If the
object has no default property, an exception is raised.
Variants in expressions
All operators except ^, is, and in take variant operands. Except for comparisons,
which always return a Boolean result, any operation on a variant value returns a
variant result. If an expression combines variants with statically-typed values, the
statically-typed values are automatically converted to variants.
This is not true for comparisons, where any operation on a Null variant produces a
Null variant. For example:
V := Null + 3;
assigns a Null variant to V. By default, comparisons treat the Null variant as a unique
value that is less than any other value. For example:
If Null > -3 Then... Else...;
In this example, the Else part of the If statement will be executed. This behavior can
be changed by setting the NullEqualityRule and NullMagnitudeRule global
variables. Refer to the CLX online documentation for more information.
Variant arrays
You cannot assign an ordinary static array to a variant. Instead, create a variant array
by calling either of the standard functions VarArrayCreate or VarArrayOf. For
example,
V: Variant;
ƒ
V := VarArrayCreate([0,9], varInteger);
creates a variant array of integers (of length 10) and assigns it to the variant V. The
array can be indexed using V[0], V[1], and so forth, but it is not possible to pass a
variant array element as a var parameter. Variant arrays are always indexed with
integers.
The second parameter in the call to VarArrayCreate is the type code for the array’s
base type. For a list of these codes, see the online Help on VarType. Never pass the
code varString to VarArrayCreate; to create a variant array of strings, use varOleStr.
Variants can hold variant arrays of different sizes, dimensions, and base types. The
elements of a variant array can be of any type allowed in variants except ShortString
and AnsiString, and if the base type of the array is Variant, its elements can even be
heterogeneous. Use the VarArrayRedim function to resize a variant array. Other
standard routines that operate on variant arrays include VarArrayDimCount,
VarArrayLowBound, VarArrayHighBound, VarArrayRef, VarArrayLock, and
VarArrayUnlock.
Note Variant arrays of custom variants are not supported, as instances of custom variants
can be added to a VarVariant variant array.
When a variant containing a variant array is assigned to another variant or passed as
a value parameter, the entire array is copied. Don’t perform such operations
unnecessarily, since they are memory-inefficient.
OleVariant
The OleVariant type exists on both the Windows and Linux platforms. The main
difference between Variant and OleVariant is that Variant can contain data types that
only the current application knows what to do with. OleVariant can only contain the
data types defined as compatible with OLE Automation which means that the data
types that can be passed between programs or across the network without worrying
about whether the other end will know how to handle the data.
When you assign a Variant that contains custom data (such as a Delphi string, or a
one of the new custom variant types) to an OleVariant, the runtime library tries to
convert the Variant into one of the OleVariant standard data types (such as a Delphi
string converts to an OLE BSTR string). For example, if a variant containing an
AnsiString is assigned to an OleVariant, the AnsiString becomes a WideString. The
same is true when passing a Variant to an OleVariant function parameter.
Type identity
Type identity is almost straightforward. When one type identifier is declared using
another type identifier, without qualification, they denote the same type. Thus, given
the declarations
type
T1 = Integer;
T2 = T1;
T3 = Integer;
T4 = T2;
T1, T2, T3, T4, and Integer all denote the same type. To create distinct types, repeat the
word type in the declaration. For example,
type TMyInteger = type Integer;
creates a new type called TMyInteger which is not identical to Integer.
Language constructions that function as type names denote a different type each time
they occur. Thus the declarations
type
TS1 = set of Char;
TS2 = set of Char;
create two distinct types, TS1 and TS2. Similarly, the variable declarations
var
S1: string[10];
S2: string[10];
create two variables of distinct types. To create variables of the same type, use
var S1, S2: string[10];
or
type MyString = string[10];
var
S1: MyString;
S2: MyString;
Type compatibility
Every type is compatible with itself. Two distinct types are compatible if they satisfy
at least one of the following conditions.
• They are both real types.
• They are both integer types.
• One type is a subrange of the other.
• Both types are subranges of the same type.
• Both are set types with compatible base types.
• Both are packed-string types with the same number of characters.
• One is a string type and the other is a string, packed-string, or Char type.
• One type is Variant and the other is an integer, real, string, character, or Boolean
type.
• Both are class, class-reference, or interface types, and one type is derived from the
other.
• One type is PChar or PWideChar and the other is a zero-based character array of the
form array[0..n] of PChar or PWideChar.
• One type is Pointer (an untyped pointer) and the other is any pointer type.
• Both types are (typed) pointers to the same type and the {$T+} compiler directive
is in effect.
• Both are procedural types with the same result type, the same number of
parameters, and type-identity between parameters in corresponding positions.
Assignment-compatibility
Assignment-compatibility is not a symmetric relation. An expression of type T2 can
be assigned to a variable of type T1 if the value of the expression falls in the range of
T1 and at least one of the following conditions is satisfied.
• T1 and T2 are of the same type, and it is not a file type or structured type that
contains a file type at any level.
• T1 and T2 are compatible ordinal types.
• T1 and T2 are both real types.
• T1 is a real type and T2 is an integer type.
• T1 is PChar, PWideChar or any string type and the expression is a string constant.
• T1 and T2 are both string types.
• T1 is a string type and T2 is a Char or packed-string type.
• T1 is a long string and T2 is PChar or PWideChar.
Declaring types
A type declaration specifies an identifier that denotes a type. The syntax for a type
declaration is
type newTypeName = type
where newTypeName is a valid identifier. For example, given the type declaration
type TMyString = string;
you can make the variable declaration
var S: TMyString;
A type identifier’s scope doesn’t include the type declaration itself (except for pointer
types). So you cannot, for example, define a record type that uses itself recursively.
When you declare a type that is identical to an existing type, the compiler treats the
new type identifier as an alias for the old one. Thus, given the declarations
type TValue = Real;
var
X: Real;
Y: TValue;
X and Y are of the same type; at runtime, there is no way to distinguish TValue from
Real. This is usually of little consequence, but if your purpose in defining a new type
is to utilize runtime type information—for example, to associate a property editor
with properties of a particular type—the distinction between “different name” and
“different type” becomes important. In this case, use the syntax
type newTypeName = type type
For example,
type TValue = type Real;
forces the compiler to create a new, distinct type called TValue.
For var parameters, types of formal and actual must be identical. For example,
type
TMyType = type Integer
procedure p(var t:TMyType);
begin end;
procedure x;
var
m: TMyType;
i: Integer;
begin
p(m); //Works
p(i); //Error! Types of formal and actual must be identical.
end;
Note This only applies to var parameters, not to const or by-value parameters
Variables
A variable is an identifier whose value can change at runtime. Put differently, a
variable is a name for a location in memory; you can use the name to read or write to
the memory location. Variables are like containers for data, and, because they are
typed, they tell the compiler how to interpret the data they hold.
Declaring variables
The basic syntax for a variable declaration is
var identifierList: type;
where identifierList is a comma-delimited list of valid identifiers and type is any valid
type. For example,
var I: Integer;
declares a variable I of type Integer, while
var X, Y: Real;
declares two variables—X and Y—of type Real.
Consecutive variable declarations do not have to repeat the reserved word var:
var
X, Y, Z: Double;
I, J, K: Integer;
Digit: 0..9;
Okay: Boolean;
Variables declared within a procedure or function are sometimes called local, while
other variables are called global. Global variables can be initialized at the same time
they are declared, using the syntax
var identifier: type = constantExpression;
where constantExpression is any constant expression representing a value of type type.
(For more information about constant expressions, see “Constant expressions” on
page 5-44.) Thus the declaration
var I: Integer = 7;
is equivalent to the declaration and statement
var I: Integer;
ƒ
I := 7;
Multiple variable declarations (such as var X, Y, Z: Real;) cannot include
initializations, nor can declarations of variant and file-type variables.
If you don’t explicitly initialize a global variable, the compiler initializes it to 0. Local
variables, in contrast, cannot be initialized in their declarations and contain random
data until a value is assigned to them.
When you declare a variable, you are allocating memory which is freed
automatically when the variable is no longer used. In particular, local variables exist
only until the program exits from the function or procedure in which they are
declared. For more information about variables and memory management, see
Chapter 11, “Memory management”.
Absolute addresses
You can create a new variable that resides at the same address as another variable. To
do so, put the directive absolute after the type name in the declaration of the new
variable, followed by the name of an existing (previously declared) variable. For
example,
var
Str: string[32];
StrLen: Byte absolute Str;
specifies that the variable StrLen should start at the same address as Str. Since the first
byte of a short string contains the string’s length, the value of StrLen is the length of
Str.
You cannot initialize a variable in an absolute declaration or combine absolute with
any other directives.
Dynamic variables
You can create dynamic variables by calling the GetMem or New procedure. Such
variables are allocated on the heap and are not managed automatically. Once you
create one, it is your responsibility ultimately to free the variable’s memory; use
FreeMem to destroy variables created by GetMem and Dispose to destroy variables
created by New. Other standard routines that operate on dynamic variables include
ReallocMem, AllocMem, Initialize, Finalize, StrAlloc, and StrDispose.
Long strings, wide strings, dynamic arrays, variants, and interfaces are also heap-
allocated dynamic variables, but their memory is managed automatically.
Thread-local variables
Thread-local (or thread) variables are used in multithreaded applications. A thread-
local variable is like a global variable, except that each thread of execution gets its
own private copy of the variable, which cannot be accessed from other threads.
Thread-local variables are declared with threadvar instead of var. For example,
threadvar X: Integer;
Thread-variable declarations
• cannot occur within a procedure or function.
• cannot include initializations.
• cannot specify the absolute directive.
Dynamic variables that are ordinarily managed by the compiler—long strings, wide
strings, dynamic arrays, variants, and interfaces—can be declared with threadvar,
but the compiler does not automatically free the heap-allocated memory created by
each thread of execution. If you use these data types in thread variables, it is your
responsibility to dispose of their memory from within the thread, before the thread
terminates. For example,
threadvar S: AnsiString;
S := 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
ƒ
S := ''; // free the memory used by S
Note Use of such constructs is discouraged.
You can free a variant by setting it to Unassigned and an interface or dynamic array by
setting it to nil.
Declared constants
Several different language constructions are referred to as “constants”. There are
numeric constants (also called numerals) like 17, and string constants (also called
character strings or string literals) like 'Hello world!'; for information about numeric
and string constants, see Chapter 4, “Syntactic elements”. Every enumerated type
defines constants that represent the values of that type. There are predefined
constants like True, False, and nil. Finally, there are constants that, like variables, are
created individually by declaration.
Declared constants are either true constants or typed constants. These two kinds of
constant are superficially similar, but they are governed by different rules and used
for different purposes.
True constants
A true constant is a declared identifier whose value cannot change. For example,
const MaxValue = 237;
declares a constant called MaxValue that returns the integer 237. The syntax for
declaring a true constant is
const identifier = constantExpression
where identifier is any valid identifier and constantExpression is an expression that the
compiler can evaluate without executing your program. (See “Constant expressions”
on page 5-44 for more information.)
If constantExpression returns an ordinal value, you can specify the type of the declared
constant using a value typecast. For example
const MyNumber = Int64(17);
declares a constant called MyNumber, of type Int64, that returns the integer 17.
Otherwise, the type of the declared constant is the type of the constantExpression.
• If constantExpression is a character string, the declared constant is compatible with
any string type. If the character string is of length 1, it is also compatible with any
character type.
• If constantExpression is a real, its type is Extended. If it is an integer, its type is given
by the table below.
Table 5.8
Range of constant Range of constant Type
(hexadecimal) (decimal)
–$8000000000000000..–$80000001 –263..–2147483649 Int64
–$80000000..–$8001 –2147483648..–32769 Integer
–$8000..–$81 –32768..–129 Smallint
–$80..–1 –128..–1 Shortint
0..$7F 0..127 0..127
$80..$FF 128..255 Byte
$0100..$7FFF 256..32767 0..32767
$8000..$FFFF 32768..65535 Word
$10000..$7FFFFFFF 65536..2147483647 0..2147483647
$80000000..$FFFFFFFF 2147483648..4294967295 Cardinal
$100000000..$7FFFFFFFFFFFFFFF 4294967296..263–1 Int64
Constant expressions
A constant expression is an expression that the compiler can evaluate without
executing the program in which it occurs. Constant expressions include numerals;
character strings; true constants; values of enumerated types; the special constants
True, False, and nil; and expressions built exclusively from these elements with
operators, typecasts, and set constructors. Constant expressions cannot include
variables, pointers, or function calls, except calls to the following predefined
functions:
Typed constants
Typed constants, unlike true constants, can hold values of array, record, procedural,
and pointer types. Typed constants cannot occur in constant expressions.
Declare a typed constant like this:
const identifier: type = value
where identifier is any valid identifier, type is any type except files and variants, and
value is an expression of type type. For example,
const Max: Integer = 100;
In most cases, value must be a constant expression; but if type is an array, record,
procedural, or pointer type, special rules apply.
Array constants
To declare an array constant, enclose the values of the array’s elements, separated by
commas, in parentheses at the end of the declaration. These values must be
represented by constant expressions. For example,
const Digits: array[0..9] of Char = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
declares a typed constant called Digits that holds an array of characters.
Zero-based character arrays often represent null-terminated strings, and for this
reason string constants can be used to initialize character arrays. So the previous
declaration can be more conveniently represented as
const Digits: array[0..9] of Char = '0123456789';
To define a multidimensional array constant, enclose the values of each dimension in
a separate set of parentheses, separated by commas. For example,
type TCube = array[0..1, 0..1, 0..1] of Integer;
const Maze: TCube = (((0, 1), (2, 3)), ((4, 5), (6,7)));
Record constants
To declare a record constant, specify the value of each field—as fieldName: value,
with the field assignments separated by semicolons—in parentheses at the end of the
declaration. The values must be represented by constant expressions. The fields must
be listed in the order in which they appear in the record type declaration, and the tag
field, if there is one, must have a value specified; if the record has a variant part, only
the variant selected by the tag field can be assigned values.
Examples:
type
TPoint = record
X, Y: Single;
end;
TVector = array[0..1] of TPoint;
TMonth = (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
TDate = record
D: 1..31;
M: TMonth;
Y: 1900..1999;
end;
const
Origin: TPoint = (X: 0.0; Y: 0.0);
Line: TVector = ((X: -3.1; Y: 1.5), (X: 5.8; Y: 3.0));
SomeDay: TDate = (D: 2; M: Dec; Y: 1960);
Record constants cannot contain file-type values at any level.
Procedural constants
To declare a procedural constant, specify the name of a function or procedure that is
compatible with the declared type of the constant. For example,
function Calc(X, Y: Integer): Integer;
begin
ƒ
end;
type TFunction = function(X, Y: Integer): Integer;
const MyFunction: TFunction = Calc;
Pointer constants
When you declare a pointer constant, you must initialize it to a value that can be
resolved—at least as a relative address—at compile time. There are three ways to do
this: with the @ operator, with nil, and (if the constant is of type PChar or PWideChar)
with a string literal. For example, if I is a global variable of type Integer, you can
declare a constant like
const PI: ^Integer = @I;
The compiler can resolve this because global variables are part of the code segment.
So are functions and global constants:
const PF: Pointer = @MyFunction;
Because string literals are allocated as global constants, you can initialize a PChar
constant with a string literal:
const WarningStr: PChar = 'Warning!';
Procedure declarations
A procedure declaration has the form
procedure procedureName(parameterList); directives;
localDeclarations;
begin
statements
end;
where procedureName is any valid identifier, statements is a sequence of statements
that execute when the procedure is called, and (parameterList), directives;, and
localDeclarations; are optional.
• For information about the parameterList, see “Parameters” on page 6-11.
• For information about directives, see “Calling conventions” on page 6-5, “Forward
and interface declarations” on page 6-6, “External declarations” on page 6-6,
“Overloading procedures and functions” on page 6-8, and “Writing dynamically
loadable libraries” on page 9-4. If you include more than one directive, separate
them with semicolons.
• For information about localDeclarations, which declares local identifiers, see “Local
declarations” on page 6-11.
Here is an example of a procedure declaration:
procedure NumString(N: Integer; var S: string);
var
V: Integer;
begin
V := Abs(N);
S := '';
repeat
S := Chr(V mod 10 + Ord('0')) + S;
V := V div 10;
until V = 0;
if N < 0 then S := '-' + S;
end;
Given this declaration, you can call the NumString procedure like this:
NumString(17, MyString);
This procedure call assigns the value “17” to MyString (which must be a string
variable).
Within a procedure’s statement block, you can use variables and other identifiers
declared in the localDeclarations part of the procedure. You can also use the parameter
names from the parameter list (like N and S in the previous example); the parameter
list defines a set of local variables, so don’t try to redeclare the parameter names in
the localDeclarations section. Finally, you can use any identifiers within whose scope
the procedure declaration falls.
Function declarations
A function declaration is like a procedure declaration except that it specifies a return
type and a return value. Function declarations have the form
function functionName(parameterList): returnType; directives;
localDeclarations;
begin
statements
end;
where functionName is any valid identifier, returnType is a type identifier, statements is
a sequence of statements that execute when the function is called, and
(parameterList), directives;, and localDeclarations; are optional.
• For information about the parameterList, see “Parameters” on page 6-11.
• For information about directives, see “Calling conventions” on page 6-5, “Forward
and interface declarations” on page 6-6, “External declarations” on page 6-6,
“Overloading procedures and functions” on page 6-8, and “Writing dynamically
loadable libraries” on page 9-4. If you include more than one directive, separate
them with semicolons.
• For information about localDeclarations, which declares local identifiers, see “Local
declarations” on page 6-11.
The function’s statement block is governed by the same rules that apply to
procedures. Within the statement block, you can use variables and other identifiers
declared in the localDeclarations part of the function, parameter names from the
parameter list, and any identifiers within whose scope the function declaration falls.
In addition, the function name itself acts as a special variable that holds the function’s
return value, as does the predefined variable Result.
As long as extended syntax is enabled ({$X+}), Result is implicitly declared in every
function. Do not try to redeclare it.
For example,
function WF: Integer;
begin
WF := 17;
end;
defines a constant function called WF that takes no parameters and always returns
the integer value 17. This declaration is equivalent to
function WF: Integer;
begin
Result := 17;
end;
Here is a more complicated function declaration:
function Max(A: array of Real; N: Integer): Real;
var
X: Real;
I: Integer;
begin
X := A[0];
for I := 1 to N - 1 do
if X < A[I] then X := A[I];
Max := X;
end;
You can assign a value to Result or to the function name repeatedly within a
statement block, as long as you assign only values that match the declared return
type. When execution of the function terminates, whatever value was last assigned to
Result or to the function name becomes the function’s return value. For example,
function Power(X: Real; Y: Integer): Real;
var
I: Integer;
begin
Result := 1.0;
I := Y;
while I > 0 do
begin
if Odd(I) then Result := Result * X;
I := I div 2;
X := Sqr(X);
end;
end;
Result and the function name always represent the same value. Hence
function MyFunction: Integer;
begin
MyFunction := 5;
Result := Result * 2;
MyFunction := Result + 1;
end;
returns the value 11. But Result is not completely interchangeable with the function
name. When the function name appears on the left side of an assignment statement,
the compiler assumes that it is being used (like Result) to track the return value; when
the function name appears anywhere else in the statement block, the compiler
interprets it as a recursive call to the function itself. Result, on the other hand, can be
used as a variable in operations, typecasts, set constructors, indexes, and calls to
other routines.
If the function exits without assigning a value to Result or the function name, then the
function’s return value is undefined.
Calling conventions
When you declare a procedure or function, you can specify a calling convention using
one of the directives register, pascal, cdecl, stdcall, and safecall. For example,
function MyFunction(X, Y: Real): Real; cdecl;
ƒ
Calling conventions determine the order in which parameters are passed to the
routine. They also affect the removal of parameters from the stack, the use of registers
for passing parameters, and error and exception handling. The default calling
convention is register.
• The register and pascal conventions pass parameters from left to right; that is, the
left most parameter is evaluated and passed first and the rightmost parameter is
evaluated and passed last. The cdecl, stdcall, and safecall conventions pass
parameters from right to left.
• For all conventions except cdecl, the procedure or function removes parameters
from the stack upon returning. With the cdecl convention, the caller removes
parameters from the stack when the call returns.
• The register convention uses up to three CPU registers to pass parameters, while
the other conventions pass all parameters on the stack.
• The safecall convention implements exception “firewalls.” On Windows, this
implements interprocess COM error notification.
The table below summarizes calling conventions.
The default register convention is the most efficient, since it usually avoids creation
of a stack frame. (Access methods for published properties must use register.) The
cdecl convention is useful when you call functions from shared libraries written in C
or C++, while stdcall and safecall are recommended, in general, for calls to external
code. On Windows, the operating system APIs are stdcall and safecall. Other
operating systems generally use cdecl. (Note that stdcall is more efficient than cdecl.)
The safecall convention must be used for declaring dual-interface methods (see
Chapter 10, “Object interfaces”). The pascal convention is maintained for backward
compatibility. For more information on calling conventions, see Chapter 12,
“Program control”.
The directives near, far, and export refer to calling conventions in 16-bit Windows
programming. They have no effect in 32-bit applications and are maintained for
backward compatibility only.
External declarations
The external directive, which replaces the block in a procedure or function
declaration, allows you to call routines that are compiled separately from your
program. External routines can come from object files or dynamically loadable
libraries.
When importing a C function that takes a variable number of parameters, use the
varargs directive. For example,
function printf(Format: PChar): Integer; cdecl; varargs;
The varargs directive works only with external routines and only with the cdecl
calling convention.
where the first stringConstant gives the name of the library file and the second
stringConstant is the routine’s original name.
On Windows:
The following declaration imports a function from user32.dll (part of the Windows
API).
function MessageBox(HWnd: Integer; Text, Caption: PChar; Flags: Integer): Integer;
stdcall; external 'user32.dll' name 'MessageBoxA';
The function’s original name is MessageBoxA, but it is imported as MessageBox.
Instead of a name, you can use a number to identify the routine you want to
import:
external stringConstant index integerConstant;
where integerConstant is the routine’s index in the export table.
On Linux:
The following declaration imports a standard system function from libc.so.6.
function OpenFile(const PathName: PChar; Flags: Integer): Integer; cdecl;
external 'libc.so.6' name 'open';
The function’s original name is open, but it is imported as OpenFile.
In your importing declaration, be sure to match the exact spelling and case of the
routine’s name. Later, when you call the imported routine, the name is case-
insensitive.
For more information about libraries, see Chapter 9, “Libraries and packages”.
You can pass to an overloaded routine parameters that are not identical in type with
those in any of the routine’s declarations, but that are assignment-compatible with
the parameters in more than one declaration. This happens most frequently when a
routine is overloaded with different integer types or different real types—for
example,
procedure Store(X: Longint); overload;
procedure Store(X: Shortint); overload;
In these cases, when it is possible to do so without ambiguity, the compiler invokes
the routine whose parameters are of the type with the smallest range that
accommodates the actual parameters in the call. (Remember that real-valued
constant expressions are always of type Extended.)
Overloaded routines must be distinguished by the number of parameters they take
or the types of their parameters. Hence the following pair of declarations causes a
compilation error.
function Cap(S: string): string; overload;
ƒ
procedure Cap(var Str: string); overload;
ƒ
But the declarations
function Func(X: Real; Y: Integer): Real; overload;
ƒ
function Func(X: Integer; Y: Real): Real; overload;
ƒ
are legal.
When an overloaded routine is declared in a forward or interface declaration, the
defining declaration must repeat the routine’s parameter list.
The compiler can distinguish between overloaded functions that contain AnsiString/
PChar and WideString/WideChar parameters in the same parameter position.
String constants or literals passed into such an overload situation are translated into
the native string or character type, which is AnsiString/PChar.
procedure test(const S: String); overload;
procedure test(const W: WideString); overload;
var
a: string;
b: widestring;
begin
a := 'a';
b := 'b';
test(a); // calls String version
test(b); // calls WideString version
test('abc'); // calls String version
test(WideString('abc')); // calls widestring version
end;
in Unit1; if no routine in Unit1 matches the name and parameter list in the call, an
error results.
For information about distributing overloaded methods in a class hierarchy, see
“Overloading methods” on page 7-12. For information about exporting overloaded
routines from a shared library, see “The exports clause” on page 9-6.
Local declarations
The body of a function or procedure often begins with declarations of local variables
used in the routine’s statement block. These declarations can also include constants,
types, and other routines. The scope of a local identifier is limited to the routine
where it is declared.
Nested routines
Functions and procedures sometimes contain other functions and procedures within
the local-declarations section of their blocks. For example, the following declaration
of a procedure called DoSomething contains a nested procedure.
procedure DoSomething(S: string);
var
X, Y: Integer;
procedure NestedProc(S: string);
begin
ƒ
end;
begin
ƒ
NestedProc(S);
ƒ
end;
The scope of a nested routine is limited to the procedure or function in which it is
declared. In our example, NestedProc can be called only within DoSomething.
For real examples of nested routines, look at the DateTimeToString procedure, the
ScanDate function, and other routines in the SysUtils unit.
Parameters
Most procedure and function headers include a parameter list. For example, in the
header
function Power(X: Real; Y: Integer): Real;
the parameter list is (X: Real; Y: Integer).
A parameter list is a sequence of parameter declarations separated by semicolons and
enclosed in parentheses. Each declaration is a comma-delimited series of parameter
names, followed in most cases by a colon and a type identifier, and in some cases by
the = symbol and a default value. Parameter names must be valid identifiers. Any
declaration can be preceded by var, const, or out. Examples:
(X, Y: Real)
(var S: string; X: Integer)
(HWnd: Integer; Text, Caption: PChar; Flags: Integer)
(const P; I: Integer)
The parameter list specifies the number, order, and type of parameters that must be
passed to the routine when it is called. If a routine does not take any parameters, omit
the identifier list and the parentheses in its declaration:
procedure UpdateRecords;
begin
ƒ
end;
Within the procedure or function body, the parameter names (X and Y in the first
example) can be used as local variables. Do not redeclare the parameter names in the
local declarations section of the procedure or function body.
Parameter semantics
Parameters are categorized in several ways:
• Every parameter is classified as value, variable, constant, or out. Value parameters
are the default; the reserved words var, const, and out indicate variable, constant,
and out parameters, respectively.
• Value parameters are always typed, while constant, variable, and out parameters
can be either typed or untyped.
• Special rules apply to array parameters. See “Array parameters” on page 6-16.
Files and instances of structured types that contain files can be passed only as
variable (var) parameters.
These functions return the same result, but only the second one—DoubleByRef—can
change the value of a variable passed to it. Suppose we call the functions like this:
var
I, J, V, W: Integer;
begin
I := 4;
V := 4;
J := DoubleByValue(I); // J = 8, I = 4
W := DoubleByRef(V); // W = 8, V = 8
end;
After this code executes, the variable I, which was passed to DoubleByValue, has the
same value we initially assigned to it. But the variable V, which was passed to
DoubleByRef, has a different value.
A value parameter acts like a local variable that gets initialized to the value passed in
the procedure or function call. If you pass a variable as a value parameter, the
procedure or function creates a copy of it; changes made to the copy have no effect on
the original variable and are lost when program execution returns to the caller.
A variable parameter, on the other hand, acts like a pointer rather than a copy.
Changes made to the parameter within the body of a function or procedure persist
after program execution returns to the caller and the parameter name itself has gone
out of scope.
Even if the same variable is passed in two or more var parameters, no copies are
made. This is illustrated in the following example.
procedure AddOne(var X, Y: Integer);
begin
X := X + 1;
Y := Y + 1;
end;
var I: Integer;
begin
I := 1;
AddOne(I, I);
end;
After this code executes, the value of I is 3.
If a routine’s declaration specifies a var parameter, you must pass an assignable
expression—that is, a variable, typed constant (in the {$J+} state), dereferenced
pointer, field, or indexed variable—to the routine when you call it. To use our
previous examples, DoubleByRef(7) produces an error, although DoubleByValue(7) is
legal.
Indexes and pointer dereferences passed in var parameters—for example,
DoubleByRef(MyArray[I])—are evaluated once, before execution of the routine.
Constant parameters
A constant (const) parameter is like a local constant or read-only variable. Constant
parameters are similar to value parameters, except that you can’t assign a value to a
constant parameter within the body of a procedure or function, nor can you pass one
as a var parameter to another routine. (But when you pass an object reference as a
constant parameter, you can still modify the object’s properties.)
Using const allows the compiler to optimize code for structured- and string-type
parameters. It also provides a safeguard against unintentionally passing a parameter
by reference to another routine.
Here, for example, is the header for the CompareStr function in the SysUtils unit:
function CompareStr(const S1, S2: string): Integer;
Because S1 and S2 are not modified in the body of CompareStr, they can be declared
as constant parameters.
Out parameters
An out parameter, like a variable parameter, is passed by reference. With an out
parameter, however, the initial value of the referenced variable is discarded by the
routine it is passed to. The out parameter is for output only; that is, it tells the
function or procedure where to store output, but doesn’t provide any input.
For example, consider the procedure heading
procedure GetInfo(out Info: SomeRecordType);
When you call GetInfo, you must pass it a variable of type SomeRecordType:
var MyRecord: SomeRecordType;
ƒ
GetInfo(MyRecord);
But you’re not using MyRecord to pass any data to the GetInfo procedure; MyRecord is
just a container where you want GetInfo to store the information it generates. The call
to GetInfo immediately frees the memory used by MyRecord, before program control
passes to the procedure.
Out parameters are frequently used with distributed-object models like COM and
CORBA. In addition, you should use out parameters when you pass an uninitialized
variable to a function or procedure.
Untyped parameters
You can omit type specifications when declaring var, const, and out parameters.
(Value parameters must be typed.) For example,
procedure TakeAnything(const C);
declares a procedure called TakeAnything that accepts a parameter of any type. When
you call such a routine, you cannot pass it a numeral or untyped numeric constant.
Within a procedure or function body, untyped parameters are incompatible with
every type. To operate on an untyped parameter, you must cast it. In general, the
compiler cannot verify that operations on untyped parameters are valid.
The following example uses untyped parameters in a function called Equal that
compares a specified number of bytes of any two variables.
function Equal(var Source, Dest; Size: Integer): Boolean;
type
TBytes = array[0..MaxInt - 1] of Byte;
var
N: Integer;
begin
N := 0;
while (N < Size) and (TBytes(Dest)[N] = TBytes(Source)[N]) do
Inc(N);
Equal := N = Size;
end;
Given the declarations
type
TVector = array[1..10] of Integer;
TPoint = record
X, Y: Integer;
end;
var
Vec1, Vec2: TVector;
N: Integer;
P: TPoint;
you could make the following calls to Equal:
Equal(Vec1, Vec2, SizeOf(TVector)) // compare Vec1 to Vec2
Equal(Vec1, Vec2, SizeOf(Integer) * N) // compare first N elements of Vec1 and Vec2
Equal(Vec1[1], Vec1[6], SizeOf(Integer) * 5) // compare first 5 to last 5 elements of Vec1
Equal(Vec1[1], P, 4) // compare Vec1[1] to P.X and Vec1[2] to P.Y
String parameters
When you declare routines that take short-string parameters, you cannot include
length specifiers in the parameter declarations. That is, the declaration
procedure Check(S: string[20]); // syntax error
causes a compilation error. But
type TString20 = string[20];
procedure Check(S: TString20);
is valid. The special identifier OpenString can be used to declare routines that take
short-string parameters of varying length:
procedure Check(S: OpenString);
When the {$H–} and {$P+} compiler directives are both in effect, the reserved word
string is equivalent to OpenString in parameter declarations.
Short strings, OpenString, $H, and $P are supported for backward compatibility only.
In new code, you can avoid these considerations by using long strings.
Array parameters
When you declare routines that take array parameters, you cannot include index type
specifiers in the parameter declarations. That is, the declaration
procedure Sort(A: array[1..10] of Integer); // syntax error
causes a compilation error. But
type TDigits = array[1..10] of Integer;
procedure Sort(A: TDigits);
is valid. For most purposes, however, open array parameters are a better solution.
Since the Delphi language does not implement value semantics for dynamic arrays,
"value" parameters in routines do not represent a full copy of the dynamic array. In
this example
type
TDynamicArray = array of Integer;
procedure Run;
var
a: TDynamicArray;
begin
SetLength(a, 1);
a[0] := 0;
p(a);
Note The syntax of open array parameters resembles that of dynamic array types, but they
do not mean the same thing. The previous example creates a function that takes any
array of Char elements, including (but not limited to) dynamic arrays. To declare
parameters that must be dynamic arrays, you need to specify a type identifier:
type TDynamicCharArray = array of Char;
function Find(A: TDynamicCharArray): Integer;
For information about dynamic arrays, see “Dynamic arrays” on page 5-20.
Within the body of a routine, open array parameters are governed by the following
rules.
• They are always zero-based. The first element is 0, the second element is 1, and so
forth. The standard Low and High functions return 0 and Length–1, respectively.
The SizeOf function returns the size of the actual array passed to the routine.
• They can be accessed by element only. Assignments to an entire open array
parameter are not allowed.
• They can be passed to other procedures and functions only as open array
parameters or untyped var parameters. They cannot be passed to SetLength.
• Instead of an array, you can pass a variable of the open array parameter’s base
type. It will be treated as an array of length 1.
When you pass an array as an open array value parameter, the compiler creates a
local copy of the array within the routine’s stack frame. Be careful not to overflow the
stack by passing large arrays.
The following examples use open array parameters to define a Clear procedure that
assigns zero to each element in an array of reals and a Sum function that computes
the sum of the elements in an array of reals.
procedure Clear(var A: array of Real);
var
I: Integer;
begin
for I := 0 to High(A) do A[I] := 0;
end;
function Sum(const A: array of Real): Real;
var
I: Integer;
S: Real;
begin
S := 0;
for I := 0 to High(A) do S := S + A[I];
Sum := S;
end;
When you call routines that use open array parameters, you can pass open array
constructors to them. See “Open array constructors” on page 6-21.
Default parameters
You can specify default parameter values in a procedure or function heading. Default
values are allowed only for typed const and value parameters. To provide a default
value, end the parameter declaration with the = symbol followed by a constant
expression that is assignment-compatible with the parameter’s type.
For example, given the declaration
procedure FillArray(A: array of Integer; Value: Integer = 0);
the following procedure calls are equivalent.
FillArray(MyArray);
FillArray(MyArray, 0);
A multiple-parameter declaration cannot specify a default value. Thus, while
function MyFunction(X: Real = 3.5; Y: Real = 3.5): Real;
is legal,
function MyFunction(X, Y: Real = 3.5): Real; // syntax error
is not.
Parameters with default values must occur at the end of the parameter list. That is, all
parameters following the first declared default value must also have default values.
So the following declaration is illegal.
procedure MyProcedure(I: Integer = 1; S: string); // syntax error
Default values specified in a procedural type override those specified in an actual
routine. Thus, given the declarations
type TResizer = function(X: Real; Y: Real = 1.0): Real;
function Resizer(X: Real; Y: Real = 2.0): Real;
var
F: TResizer;
N: Real;
the statements
F := Resizer;
F(N);
result in the values (N, 1.0) being passed to Resizer.
Default parameters are limited to values that can be specified by a constant
expression. (See “Constant expressions” on page 5-44.) Hence parameters of a
dynamic-array, procedural, class, class-reference, or interface type can have no value
other than nil as their default. Parameters of a record, variant, file, static-array, or
object type cannot have default values at all.
For information about calling routines with default parameter values, see “Calling
procedures and functions.”.
Class types
A class type must be declared and given a name before it can be instantiated. (You
cannot define a class type within a variable declaration.) Declare classes only in the
outermost scope of a program or unit, not in a procedure or function declaration.
A class type declaration has the form
type className = class (ancestorClass)
memberList
end;
where className is any valid identifier, (ancestorClass) is optional, and memberList
declares members—that is, fields, methods, and properties—of the class. If you omit
(ancestorClass), then the new class inherits directly from the predefined TObject class.
If you include (ancestorClass) and memberList is empty, you can omit end. A class type
declaration can also include a list of interfaces implemented by the class; see
“Implementing interfaces” on page 10-4.
Methods appear in a class declaration as function or procedure headings, with no
body. Defining declarations for each method occur elsewhere in the program.
For example, here is the declaration of the TMemoryStream class from the Classes unit.
type
TMemoryStream = class(TCustomMemoryStream)
private
FCapacity: Longint;
procedure SetCapacity(NewCapacity: Longint);
protected
function Realloc(var NewCapacity: Longint): Pointer; virtual;
property Capacity: Longint read FCapacity write SetCapacity;
public
destructor Destroy; override;
procedure Clear;
procedure LoadFromStream(Stream: TStream);
procedure LoadFromFile(const FileName: string);
procedure SetSize(NewSize: Longint); override;
function Write(const Buffer; Count: Longint): Longint; override;
end;
TMemoryStream descends from TStream (in the Classes unit), inheriting most of its
members. But it defines—or redefines—several methods and properties, including its
destructor method, Destroy. Its constructor, Create, is inherited without change from
TObject, and so is not redeclared. Each member is declared as private, protected, or
public (this class has no published members); for explanations of these terms, see
“Visibility of class members” on page 7-4.
Given this declaration, you can create an instance of TMemoryStream as follows:
var stream: TMemoryStream;
stream := TMemoryStream.Create;
Object types
As an alternative to class types, you can declare object types using the syntax
type objectTypeName = object (ancestorObjectType)
memberList
end;
where objectTypeName is any valid identifier, (ancestorObjectType) is optional, and
memberList declares fields, methods, and properties. If (ancestorObjectType) is
omitted, then the new type has no ancestor. Object types cannot have published
members.
Since object types do not descend from TObject, they provide no built-in constructors,
destructors, or other methods. You can create instances of an object type using the
New procedure and destroy them with the Dispose procedure, or you can simply
declare variables of an object type, just as you would with records.
Object types are supported for backward compatibility only. Their use is not
recommended.
You can increase the visibility of a member in a descendant class by redeclaring it,
but you cannot decrease its visibility. For example, a protected property can be made
public in a descendant, but not private. Moreover, published members cannot
become public in a descendant class. For more information, see “Property overrides
and redeclarations” on page 7-23.
Published members
Published members have the same visibility as public members. The difference is that
runtime type information (RTTI) is generated for published members. RTTI allows an
application to query the fields and properties of an object dynamically and to locate
its methods. RTTI is used to access the values of properties when saving and loading
form files, to display properties in the Object Inspector, and to associate specific
methods (called event handlers) with specific properties (called events).
Published properties are restricted to certain data types. Ordinal, string, class,
interface, variant, and method-pointer types can be published. So can set types,
provided the upper and lower bounds of the base type have ordinal values between 0
and 31. (In other words, the set must fit in a byte, word, or double word.) Any real
type except Real48 can be published. Properties of an array type (as distinct from
array properties, discussed below) cannot be published.
Some properties, although publishable, are not fully supported by the streaming
system. These include properties of record types, array properties of all publishable
types (see “Array properties” on page 7-20), and properties of enumerated types that
include anonymous values (see “Enumerated types with explicitly assigned
ordinality” on page 5-8). If you publish a property of this kind, the Object Inspector
won’t display it correctly, nor will the property’s value be preserved when objects are
streamed to disk.
All methods are publishable, but a class cannot publish two or more overloaded
methods with the same name. Fields can be published only if they are of a class or
interface type.
A class cannot have published members unless it is compiled in the {$M+} state or
descends from a class compiled in the {$M+} state. Most classes with published
members derive from TPersistent, which is compiled in the {$M+} state, so it is
seldom necessary to use the $M directive.
Automated members
Automated members have the same visibility as public members. The difference is
that Automation type information (required for Automation servers) is generated for
automated members. Automated members typically appear only in Windows classes
and are not recommended for Linux programming. The automated reserved word is
maintained for backward compatibility. The TAutoObject class in the ComObj unit
does not use automated.
The following restrictions apply to methods and properties declared as automated.
• The types of all properties, array property parameters, method parameters, and
function results must be automatable. The automatable types are Byte, Currency,
Real, Double, Longint, Integer, Single, Smallint, AnsiString, WideString, TDateTime,
Variant, OleVariant, WordBool, and all interface types.
• Method declarations must use the default register calling convention. They can be
virtual, but not dynamic.
• Property declarations can include access specifiers (read and write) but other
specifiers (index, stored, default, and nodefault) are not allowed. Access
specifiers must list a method identifier that uses the default register calling
convention; field identifiers are not allowed.
• Property declarations must specify a type. Property overrides are not allowed.
The declaration of an automated method or property can include a dispid directive.
Specifying an already used ID in a dispid directive causes an error.
On Windows, this directive must be followed by an integer constant that specifies an
Automation dispatch ID for the member. Otherwise, the compiler automatically
assigns the member a dispatch ID that is one larger than the largest dispatch ID used
by any method or property in the class and its ancestors. For more information about
Automation (on Windows only), see “Automation objects (Windows only)” on
page 10-11.
Fields
A field is like a variable that belongs to an object. Fields can be of any type, including
class types. (That is, fields can hold object references.) Fields are usually private.
To define a field member of a class, simply declare the field as you would a variable.
All field declarations must occur before any property or method declarations. For
example, the following declaration creates a class called TNumber whose only
member, other than the methods is inherits from TObject, is an integer field called Int.
type TNumber = class
Int: Integer;
end;
Fields are statically bound; that is, references to them are fixed at compile time. To
see what this means, consider the following code.
type
TAncestor = class
Value: Integer;
end;
TDescendant = class(TAncestor)
Value: string; // hides the inherited Value field
end;
var
MyObject: TAncestor;
begin
MyObject := TDescendant.Create;
MyObject.Value := 'Hello!'; // error
(MyObject as TDescendant).Value := 'Hello!'; // works!
end;
Methods
A method is a procedure or function associated with a class. A call to a method
specifies the object (or, if it is a class method, the class) that the method should
operate on. For example,
SomeObject.Free
calls the Free method in SomeObject.
Method declarations can include special directives that are not used with other
functions or procedures. Directives should appear in the class declaration only, not in
the defining declaration, and should always be listed in the following order:
reintroduce; overload; binding; calling convention; abstract; warning
where binding is virtual, dynamic, or override; calling convention is register, pascal,
cdecl, stdcall, or safecall; and warning is platform, deprecated, or library.
Inherited
The reserved word inherited plays a special role in implementing polymorphic
behavior. It can occur in method definitions, with or without an identifier after it.
If inherited is followed by the name of a member, it represents a normal method call
or reference to a property or field—except that the search for the referenced member
begins with the immediate ancestor of the enclosing method’s class. For example,
when
inherited Create(...);
occurs in the definition of a method, it calls the inherited Create.
When inherited has no identifier after it, it refers to the inherited method with the
same name as the enclosing method or, if the enclosing method is a message handler,
to the inherited message handler for the same message. In this case, inherited takes
no explicit parameters, but passes to the inherited method the same parameters with
which the enclosing method was called. For example,
inherited;
occurs frequently in the implementation of constructors. It calls the inherited
constructor with the same parameters that were passed to the descendant.
Self
Within the implementation of a method, the identifier Self references the object in
which the method is called. For example, here is the implementation of TCollection’s
Add method in the Classes unit.
function TCollection.Add: TCollectionItem;
begin
Result := FItemClass.Create(Self);
end;
The Add method calls the Create method in the class referenced by the FItemClass
field, which is always a TCollectionItem descendant. TCollectionItem.Create takes a
single parameter of type TCollection, so Add passes it the TCollection instance object
where Add is called. This is illustrated in the following code.
var MyCollection: TCollection;
ƒ
MyCollection.Add // MyCollection is passed to the TCollectionItem.Create method
Self is useful for a variety of reasons. For example, a member identifier declared in a
class type might be redeclared in the block of one of the class’s methods. In this case,
you can access the original member identifier as Self.Identifier.
For information about Self in class methods, see “Class methods” on page 7-26.
Method binding
Method bindings can be static (the default), virtual, or dynamic. Virtual and dynamic
methods can be overridden, and they can be abstract. These designations come into
play when a variable of one class type holds a value of a descendant class type. They
determine which implementation is activated when a method is called.
Static methods
Methods are by default static. When a static method is called, the declared (compile-
time) type of the class or object variable used in the method call determines which
implementation to activate. In the following example, the Draw methods are static.
type
TFigure = class
procedure Draw;
end;
TRectangle = class(TFigure)
procedure Draw;
end;
Given these declarations, the following code illustrates the effect of calling a static
method. In the second call to Figure.Draw, the Figure variable references an object of
class TRectangle, but the call invokes the implementation of Draw in TFigure, because
the declared type of the Figure variable is TFigure.
var
Figure: TFigure;
Rectangle: TRectangle;
begin
Figure := TFigure.Create;
Figure.Draw; // calls TFigure.Draw
Figure.Destroy;
Figure := TRectangle.Create;
Figure.Draw; // calls TFigure.Draw
TRectangle(Figure).Draw; // calls TRectangle.Draw
Figure.Destroy;
Rectangle := TRectangle.Create;
Rectangle.Draw; // calls TRectangle.Draw
Rectangle.Destroy;
end;
Reintroduce
The reintroduce directive suppresses compiler warnings about hiding previously
declared virtual methods. For example,
procedure DoSomething; reintroduce; // the ancestor class also has a DoSomething method
Use reintroduce when you want to hide an inherited virtual method with a new one.
Abstract methods
An abstract method is a virtual or dynamic method that has no implementation in the
class where it is declared. Its implementation is deferred to a descendant class.
Abstract methods must be declared with the directive abstract after virtual or
dynamic. For example,
procedure DoSomething; virtual; abstract;
You can call an abstract method only in a class or instance of a class in which the
method has been overridden.
Overloading methods
A method can be redeclared using the overload directive. In this case, if the
redeclared method has a different parameter signature from its ancestor, it overloads
the inherited method without hiding it. Calling the method in a descendant class
activates whichever implementation matches the parameters in the call.
If you overload a virtual method, use the reintroduce directive when you redeclare it
in descendant classes. For example,
type
T1 = class(TObject)
procedure Test(I: Integer); overload; virtual;
end;
T2 = class(T1)
procedure Test(S: string); reintroduce; overload;
end;
ƒ
SomeObject := T2.Create;
SomeObject.Test('Hello!'); // calls T2.Test
SomeObject.Test(7); // calls T1.Test
Within a class, you cannot publish multiple overloaded methods with the same
name. Maintenance of runtime type information requires a unique name for each
published member.
type
TSomeClass = class
published
function Func(P: Integer): Integer;
function Func(P: Boolean): Integer // error
ƒ
Methods that serve as property read or write specifiers cannot be overloaded.
The implementation of an overloaded method must repeat the parameter list from
the class declaration. For more information about overloading, see “Overloading
procedures and functions” on page 6-8.
Constructors
A constructor is a special method that creates and initializes instance objects. The
declaration of a constructor looks like a procedure declaration, but it begins with the
word constructor. Examples:
constructor Create;
constructor Create(AOwner: TComponent);
Constructors must use the default register calling convention. Although the
declaration specifies no return value, a constructor returns a reference to the object it
creates or is called in.
A class can have more than one constructor, but most have only one. It is
conventional to call the constructor Create.
To create an object, call the constructor method on a class type. For example,
MyObject := TMyClass.Create;
This allocates storage for the new object on the heap, sets the values of all ordinal
fields to zero, assigns nil to all pointer and class-type fields, and makes all string
fields empty. Other actions specified in the constructor implementation are
performed next; typically, objects are initialized based on values passed as
Destructors
A destructor is a special method that destroys the object where it is called and
deallocates its memory. The declaration of a destructor looks like a procedure
declaration, but it begins with the word destructor. Example:
destructor SpecialDestructor(SaveData: Boolean);
destructor Destroy; override;
Destructors must use the default register calling convention. Although a class can
have more than one destructor, it is recommended that each class override the
inherited Destroy method and declare no other destructors.
To call a destructor, you must reference an instance object. For example,
MyObject.Destroy;
When a destructor is called, actions specified in the destructor implementation are
performed first. Typically, these consist of destroying any embedded objects and
freeing resources that were allocated by the object. Then the storage that was
allocated for the object is disposed of.
Here is an example of a destructor implementation.
destructor TShape.Destroy;
begin
FBrush.Free;
FPen.Free;
inherited Destroy;
end;
The last action in a destructor’s implementation is typically to call the inherited
destructor to destroy the object’s inherited fields.
When an exception is raised during creation of an object, Destroy is automatically
called to dispose of the unfinished object. This means that Destroy must be prepared
to dispose of partially constructed objects. Because a constructor sets the fields of a
new object to zero or empty values before performing other actions, class-type and
pointer-type fields in a partially constructed object are always nil. A destructor
should therefore check for nil values before operating on class-type or pointer-type
fields. Calling the Free method (defined in TObject), rather than Destroy, offers a
convenient way of checking for nil values before destroying an object.
Message methods
Message methods implement responses to dynamically dispatched messages. The
message method syntax is supported on all platforms. WinCLX uses message
methods to respond to Windows messages. Cross-platform portions of CLX do not
use message methods to respond to system events.
A message method is created by including the message directive in a method
declaration, followed by an integer constant between 1 and 49151 which specifies the
message ID. For message methods in WinCLX controls, the integer constant can be
one of the Windows message IDs defined, along with corresponding record types, in
the Messages unit. A message method must be a procedure that takes a single var
parameter.
For example, on Windows:
type
TTextBox = class(TCustomControl)
private
procedure WMChar(var Message: TWMChar); message WM_CHAR;
ƒ
end;
For example, on Linux or for cross-platform programming, you would handle
messages as follows:
const
ID_REFRESH = $0001;
type
TTextBox = class(TCustomControl)
private
procedure Refresh(var Message: TMessageRecordType); message ID_REFRESH;
ƒ
end;
A message method does not have to include the override directive to override an
inherited message method. In fact, it doesn’t have to specify the same method name
or parameter type as the method it overrides. The message ID alone determines
which message the method responds to and whether it is an override.
for the given ID, inherited calls the DefaultHandler method originally defined in
TObject.
The implementation of DefaultHandler in TObject simply returns without performing
any actions. By overriding DefaultHandler, a class can implement its own default
handling of messages. On Windows, the DefaultHandler method for WinCLX controls
calls the Windows DefWindowProc function.
Message dispatching
Message handlers are seldom called directly. Instead, messages are dispatched to an
object using the Dispatch method inherited from TObject:
procedure Dispatch(var Message);
The Message parameter passed to Dispatch must be a record whose first entry is a field
of type Word containing a message ID.
Dispatch searches backward through the class hierarchy (starting from the class of the
object where it is called) and invokes the first message method for the ID passed to it.
If no message method is found for the given ID, Dispatch calls DefaultHandler.
Properties
A property, like a field, defines an attribute of an object. But while a field is merely a
storage location whose contents can be examined and changed, a property associates
specific actions with reading or modifying its data. Properties provide control over
access to an object’s attributes, and they allow attributes to be computed.
The declaration of a property specifies a name and a type, and includes at least one
access specifier. The syntax of a property declaration is
property propertyName[indexes]: type index integerConstant specifiers;
where
• propertyName is any valid identifier.
• [indexes] is optional and is a sequence of parameter declarations separated by
semicolons. Each parameter declaration has the form identifier1, ..., identifiern:
type. For more information, see “Array properties” on page 7-20.
• type must be a predefined or previously declared type identifier. That is, property
declarations like property Num: 0..9 ... are invalid.
• the index integerConstant clause is optional. For more information, see “Index
specifiers” on page 7-21.
• specifiers is a sequence of read, write, stored, default (or nodefault), and
implements specifiers. Every property declaration must have at least one read or
write specifier. (For information about implements, see “Implementing interfaces
by delegation” on page 10-7.)
Properties are defined by their access specifiers. Unlike fields, properties cannot be
passed as var parameters, nor can the @ operator be applied to a property. The reason
is that a property doesn’t necessarily exist in memory. It could, for instance, have a
read method that retrieves a value from a database or generates a random value.
Property access
Every property has a read specifier, a write specifier, or both. These are called access
specifiers and they have the form
read fieldOrMethod
write fieldOrMethod
where fieldOrMethod is the name of a field or method declared in the same class as the
property or in an ancestor class.
• If fieldOrMethod is declared in the same class, it must occur before the property
declaration. If it is declared in an ancestor class, it must be visible from the
descendant; that is, it cannot be a private field or method of an ancestor class
declared in a different unit.
• If fieldOrMethod is a field, it must be of the same type as the property.
• If fieldOrMethod is a method, it cannot be dynamic and, if virtual, cannot be
overloaded. Moreover, access methods for a published property must use the
default register calling convention.
• In a read specifier, if fieldOrMethod is a method, it must be a parameterless
function whose result type is the same as the property’s type. (An exception is the
access method for an indexed property or an array property. See “Index
specifiers” on page 7-21 and “Array properties” on page 7-20.)
• In a write specifier, if fieldOrMethod is a method, it must be a procedure that takes
a single value or const parameter of the same type as the property (or more, if it is
an array property or indexed property).
For example, given the declaration
property Color: TColor read GetColor write SetColor;
the GetColor method must be declared as
function GetColor: TColor;
and the SetColor method must be declared as one of these:
procedure SetColor(Value: TColor);
procedure SetColor(const Value: TColor);
(The name of SetColor‘s parameter, of course, doesn’t have to be Value.)
When a property is referenced in an expression, its value is read using the field or
method listed in the read specifier. When a property is referenced in an assignment
statement, its value is written using the field or method listed in the write specifier.
The example below declares a class called TCompass with a published property called
Heading. The value of Heading is read through the FHeading field and written through
the SetHeading procedure.
type
THeading = 0..359;
TCompass = class(TControl)
private
FHeading: THeading;
procedure SetHeading(Value: THeading);
published
property Heading: THeading read FHeading write SetHeading;
ƒ
end;
Given this declaration, the statements
if Compass.Heading = 180 then GoingSouth;
Compass.Heading := 135;
correspond to
if Compass.FHeading = 180 then GoingSouth;
Compass.SetHeading(135);
In the TCompass class, no action is associated with reading the Heading property; the
read operation consists of retrieving the value stored in the FHeading field. On the
other hand, assigning a value to the Heading property translates into a call to the
SetHeading method, which, presumably, stores the new value in the FHeading field as
well as performing other actions. For example, SetHeading might be implemented like
this:
procedure TCompass.SetHeading(Value: THeading);
begin
if FHeading <> Value then
begin
FHeading := Value;
Repaint; // update user interface to reflect new value
end;
end;
A property whose declaration includes only a read specifier is a read-only property,
and one whose declaration includes only a write specifier is a write-only property. It
is an error to assign a value to a read-only property or use a write-only property in an
expression.
Array properties
Array properties are indexed properties. They can represent things like items in a list,
child controls of a control, and pixels of a bitmap.
The declaration of an array property includes a parameter list that specifies the
names and types of the indexes. For example,
property Objects[Index: Integer]: TObject read GetObject write SetObject;
property Pixels[X, Y: Integer]: TColor read GetPixel write SetPixel;
property Values[const Name: string]: string read GetValue write SetValue;
The format of an index parameter list is the same as that of a procedure’s or
function’s parameter list, except that the parameter declarations are enclosed in
brackets instead of parentheses. Unlike arrays, which can use only ordinal-type
indexes, array properties allow indexes of any type.
For array properties, access specifiers must list methods rather than fields. The
method in a read specifier must be a function that takes the number and type of
parameters listed in the property’s index parameter list, in the same order, and
whose result type is identical to the property’s type. The method in a write specifier
must be a procedure that takes the number and type of parameters listed in the
property’s index parameter list, in the same order, plus an additional value or const
parameter of the same type as the property.
For example, the access methods for the array properties above might be declared as
function GetObject(Index: Integer): TObject;
function GetPixel(X, Y: Integer): TColor;
function GetValue(const Name: string): string;
procedure SetObject(Index: Integer; Value: TObject);
procedure SetPixel(X, Y: Integer; Value: TColor);
procedure SetValue(const Name, Value: string);
An array property is accessed by indexing the property identifier. For example, the
statements
if Collection.Objects[0] = nil then Exit;
Canvas.Pixels[10, 20] := clRed;
Params.Values['PATH'] := 'C:\BIN';
correspond to
if Collection.GetObject(0) = nil then Exit;
Canvas.SetPixel(10, 20, clRed);
Params.SetValue('PATH', 'C:\BIN');
On Linux, you would use a path such as ‘/bin’ in place of ‘C:\BIN’ in the above
example.
The definition of an array property can be followed by the default directive, in which
case the array property becomes the default property of the class. For example,
type
TStringArray = class
public
property Strings[Index: Integer]: string ...; default;
ƒ
end;
If a class has a default property, you can access that property with the abbreviation
object[index], which is equivalent to object.property[index]. For example, given the
declaration above, StringArray.Strings[7] can be abbreviated to StringArray[7]. A class
can have only one default property. Changing or hiding the default property in
descendant classes may lead to unexpected behavior, since the compiler always
binds to properties statically.
Index specifiers
Index specifiers allow several properties to share the same access method while
representing different values. An index specifier consists of the directive index
followed by an integer constant between –2147483647 and 2147483647. If a property
has an index specifier, its read and write specifiers must list methods rather than
fields. For example,
type
TRectangle = class
private
FCoordinates: array[0..3] of Longint;
function GetCoordinate(Index: Integer): Longint;
procedure SetCoordinate(Index: Integer; Value: Longint);
public
property Left: Longint index 0 read GetCoordinate write SetCoordinate;
property Top: Longint index 1 read GetCoordinate write SetCoordinate;
property Right: Longint index 2 read GetCoordinate write SetCoordinate;
property Bottom: Longint index 3 read GetCoordinate write SetCoordinate;
property Coordinates[Index: Integer]: Longint read GetCoordinate write SetCoordinate;
ƒ
end;
An access method for a property with an index specifier must take an extra value
parameter of type Integer. For a read function, it must be the last parameter; for a
write procedure, it must be the second-to-last parameter (preceding the parameter
that specifies the property value). When a program accesses the property, the
property’s integer constant is automatically passed to the access method.
Storage specifiers
The optional stored, default, and nodefault directives are called storage specifiers.
They have no effect on program behavior, but control whether or not to save the
values of published properties in form files.
The stored directive must be followed by True, False, the name of a Boolean field, or
the name of a parameterless method that returns a Boolean value. For example,
property Name: TComponentName read FName write SetName stored False;
If a property has no stored directive, it is treated as if stored True were specified.
The default directive must be followed by a constant of the same type as the
property. For example,
property Tag: Longint read FTag write FTag default 0;
To override an inherited default value without specifying a new one, use the
nodefault directive. The default and nodefault directives are supported only for
ordinal types and for set types, provided the upper and lower bounds of the set’s
base type have ordinal values between 0 and 31; if such a property is declared
without default or nodefault, it is treated as if nodefault were specified. For reals,
pointers, and strings, there is an implicit default value of 0, nil, and '' (the empty
string), respectively.
Note You can’t use the ordinal value 2147483648 has a default value. This value is used
internally to represent nodefault.
When saving a component’s state, the storage specifiers of the component’s
published properties are checked. If a property’s current value is different from its
default value (or if there is no default value) and the stored specifier is True, then the
property’s value is saved. Otherwise, the property’s value is not saved.
Note Property values are not automatically initialized to the default value. That is, the
default directive controls only when property values are saved to the form file, but
not the initial value of the property on a newly created instance.
Storage specifiers are not supported for array properties. The default directive has a
different meaning when used in an array property declaration. See “Array
properties” on page 7-20.
can cast MyObject to TDescendant to access the descendant class’s properties and their
access specifiers.
type
TAncestor = class
ƒ
property Value: Integer read Method1 write Method2;
end;
TDescendant = class(TAncestor)
ƒ
property Value: Integer read Method3 write Method4;
end;
var MyObject: TAncestor;
ƒ
MyObject := TDescendant.Create;
Class references
Sometimes operations are performed on a class itself, rather than on instances of a
class (that is, objects). This happens, for example, when you call a constructor method
using a class reference. You can always refer to a specific class using its name, but at
times it is necessary to declare variables or parameters that take classes as values, and
in these situations you need class-reference types.
Class-reference types
A class-reference type, sometimes called a metaclass, is denoted by a construction of
the form
class of type
where type is any class type. The identifier type itself denotes a value whose type is
class of type. If type1 is an ancestor of type2, then class of type2 is assignment-
compatible with class of type1. Thus
type TClass = class of TObject;
var AnyObj: TClass;
declares a variable called AnyObj that can hold a reference to any class. (The
definition of a class-reference type cannot occur directly in a variable declaration or
parameter list.) You can assign the value nil to a variable of any class-reference type.
To see how class-reference types are used, look at the declaration of the constructor
for TCollection (in the Classes unit):
type TCollectionItemClass = class of TCollectionItem;
ƒ
constructor Create(ItemClass: TCollectionItemClass);
This declaration says that to create a TCollection instance object, you must pass to the
constructor the name of a class descending from TCollectionItem.
Class-reference types are useful when you want to invoke a class method or virtual
constructor on a class or object whose actual type is unknown at compile time.
Class operators
Every class inherits from TObject methods called ClassType and ClassParent that
return, respectively, a reference to the class of an object and of an object’s immediate
ancestor. Both methods return a value of type TClass (where TClass = class of
TObject), which can be cast to a more specific type. Every class also inherits a method
called InheritsFrom that tests whether the object where it is called descends from a
specified class. These methods are used by the is and as operators, and it is seldom
necessary to call them directly.
The is operator
The is operator, which performs dynamic type checking, is used to verify the actual
runtime class of an object. The expression
object is class
returns True if object is an instance of the class denoted by class or one of its
descendants, and False otherwise. (If object is nil, the result is False.) If the declared
type of object is unrelated to class—that is, if the types are distinct and one is not an
ancestor of the other—a compilation error results. For example,
if ActiveControl is TEdit then TEdit(ActiveControl).SelectAll;
This statement casts a variable to TEdit after first verifying that the object it references
is an instance of TEdit or one of its descendants.
The as operator
The as operator performs checked typecasts. The expression
object as class
returns a reference to the same object as object, but with the type given by class. At
runtime, object must be an instance of the class denoted by class or one of its
descendants, or be nil; otherwise an exception is raised. If the declared type of object
is unrelated to class—that is, if the types are distinct and one is not an ancestor of the
other—a compilation error results. For example,
with Sender as TButton do
begin
Caption := '&Ok';
OnClick := OkClick;
end;
The rules of operator precedence often require as typecasts to be enclosed in
parentheses. For example,
(Sender as TButton).Caption := '&Ok';
Class methods
A class method is a method (other than a constructor) that operates on classes instead
of objects. The definition of a class method must begin with the reserved word class.
For example,
type
TFigure = class
public
class function Supports(Operation: string): Boolean; virtual;
class procedure GetInfo(var Info: TFigureInfo); virtual;
ƒ
end;
The defining declaration of a class method must also begin with class. For example,
class procedure TFigure.GetInfo(var Info: TFigureInfo);
begin
ƒ
end;
In the defining declaration of a class method, the identifier Self represents the class
where the method is called (which could be a descendant of the class in which it is
defined). If the method is called in the class C, then Self is of the type class of C. Thus
you cannot use Self to access fields, properties, and normal (object) methods, but you
can use it to call constructors and other class methods.
A class method can be called through a class reference or an object reference. When it
is called through an object reference, the class of the object becomes the value of Self.
Exceptions
An exception is raised when an error or other event interrupts normal execution of a
program. The exception transfers control to an exception handler, which allows you to
separate normal program logic from error-handling. Because exceptions are objects,
they can be grouped into hierarchies using inheritance, and new exceptions can be
introduced without affecting existing code. An exception can carry information, such
as an error message, from the point where it is raised to the point where it is handled.
When an application uses the SysUtils unit, most runtime errors are automatically
converted into exceptions. Many errors that would otherwise terminate an
application—such as insufficient memory, division by zero, and general protection
faults—can be caught and handled.
Conditional statements are often the best way to test for errors. For example, suppose
you want to make sure that a file exists before trying to open it. You could do it this
way:
try
AssignFile(F, FileName);
Reset(F); // raises an EInOutError exception if file is not found
except
on Exception do ...
end;
But you could also avoid the overhead of exception handling by using
if FileExists(FileName) then // returns False if file is not found; raises no exception
begin
AssignFile(F, FileName);
Reset(F);
end;
Assertions provide another way of testing a Boolean condition anywhere in your
source code. When an Assert statement fails, the program either halts with a runtime
error or (if it uses the SysUtils unit) raises an EAssertionFailed exception. Assertions
should be used only to test for conditions that you do not expect to occur. For more
information, see the online Help for the standard procedure Assert.
Try...except statements
Exceptions are handled within try...except statements. For example,
try
X := Y/Z;
except
on EZeroDivide do HandleZeroDivide;
end;
This statement attempts to divide Y by Z, but calls a routine named HandleZeroDivide
if an EZeroDivide exception is raised.
The syntax of a try...except statement is
try statements except exceptionBlock end
where statements is a sequence of statements (delimited by semicolons) and
exceptionBlock is either
• another sequence of statements or
• a sequence of exception handlers, optionally followed by
else statements
An exception handler has the form
on identifier: type do statement
where identifier: is optional (if included, identifier can be any valid identifier), type is a
type used to represent exceptions, and statement is any statement.
A try...except statement executes the statements in the initial statements list. If no
exceptions are raised, the exception block (exceptionBlock) is ignored and control
passes to the next part of the program.
If an exception is raised during execution of the initial statements list, either by a raise
statement in the statements list or by a procedure or function called from the
statements list, an attempt is made to “handle” the exception:
• If any of the handlers in the exception block matches the exception, control passes
to the first such handler. An exception handler “matches” an exception just in case
the type in the handler is the class of the exception or an ancestor of that class.
• If no such handler is found, control passes to the statement in the else clause, if
there is one.
• If the exception block is just a sequence of statements without any exception
handlers, control passes to the first statement in the list.
If none of the conditions above is satisfied, the search continues in the exception
block of the next-most-recently entered try...except statement that has not yet exited.
If no appropriate handler, else clause, or statement list is found there, the search
propagates to the next-most-recently entered try...except statement, and so forth. If
the outermost try...except statement is reached and the exception is still not handled,
the program terminates.
When an exception is handled, the stack is traced back to the procedure or function
containing the try...except statement where the handling occurs, and control is
transferred to the executed exception handler, else clause, or statement list. This
process discards all procedure and function calls that occurred after entering the
try...except statement where the exception is handled. The exception object is then
automatically destroyed through a call to its Destroy destructor and control is passed
to the statement following the try...except statement. (If a call to the Exit, Break, or
Continue standard procedure causes control to leave the exception handler, the
exception object is still automatically destroyed.)
In the example below, the first exception handler handles division-by-zero
exceptions, the second one handles overflow exceptions, and the final one handles all
other math exceptions. EMathError appears last in the exception block because it is
the ancestor of the other two exception classes; if it appeared first, the other two
handlers would never be invoked.
try
ƒ
except
on EZeroDivide do HandleZeroDivide;
on EOverflow do HandleOverflow;
on EMathError do HandleMathError;
end;
An exception handler can specify an identifier before the name of the exception class.
This declares the identifier to represent the exception object during execution of the
statement that follows on...do. The scope of the identifier is limited to that statement.
For example,
try
ƒ
except
on E: Exception do ErrorDialog(E.Message, E.HelpContext);
end;
If the exception block specifies an else clause, the else clause handles any exceptions
that aren’t handled by the block’s exception handlers. For example,
try
ƒ
except
on EZeroDivide do HandleZeroDivide;
on EOverflow do HandleOverflow;
on EMathError do HandleMathError;
else
HandleAllOthers;
end;
Here, the else clause handles any exception that isn’t an EMathError.
An exception block that contains no exception handlers, but instead consists only of a
list of statements, handles all exceptions. For example,
try
ƒ
except
HandleException;
end;
Here, the HandleException routine handles any exception that occurs as a result of
executing the statements between try and except.
Re-raising exceptions
When the reserved word raise occurs in an exception block without an object
reference following it, it raises whatever exception is handled by the block. This
allows an exception handler to respond to an error in a limited way and then re-raise
the exception. Re-raising is useful when a procedure or function has to clean up after
an exception occurs but cannot fully handle the exception.
For example, the GetFileList function allocates a TStringList object and fills it with file
names matching a specified search path:
function GetFileList(const Path: string): TStringList;
var
I: Integer;
SearchRec: TSearchRec;
begin
Result := TStringList.Create;
try
I := FindFirst(Path, 0, SearchRec);
while I = 0 do
begin
Result.Add(SearchRec.Name);
I := FindNext(SearchRec);
end;
except
Result.Free;
raise;
end;
end;
GetFileList creates a TStringList object, then uses the FindFirst and FindNext functions
(defined in SysUtils) to initialize it. If the initialization fails—for example because the
search path is invalid, or because there is not enough memory to fill in the string
list—GetFileList needs to dispose of the new string list, since the caller does not yet
know of its existence. For this reason, initialization of the string list is performed in a
try...except statement. If an exception occurs, the statement’s exception block
disposes of the string list, then re-raises the exception.
Try...finally statements
Sometimes you want to ensure that specific parts of an operation are completed,
whether or not the operation is interrupted by an exception. For example, when a
routine acquires control of a resource, it is often important that the resource be
released, regardless of whether the routine terminates normally. In these situations,
you can use a try...finally statement.
The following example shows how code that opens and processes a file can ensure
that the file is ultimately closed, even if an error occurs during execution.
Reset(F);
try
ƒ // process file F
finally
CloseFile(F);
end;
The syntax of a try...finally statement is
try statementList1 finally statementList2 end
A file variable is any variable whose type is a file type. There are three classes of file:
typed, text, and untyped. The syntax for declaring file types is given in “File types” on
page 5-26.
Before a file variable can be used, it must be associated with an external file through a
call to the AssignFile procedure. An external file is typically a named disk file, but it
can also be a device, such as the keyboard or the display. The external file stores the
information written to the file or supplies the information read from the file.
Once the association with an external file is established, the file variable must be
“opened” to prepare it for input or output. An existing file can be opened via the
Reset procedure, and a new file can be created and opened via the Rewrite procedure.
Text files opened with Reset are read-only and text files opened with Rewrite and
Append are write-only. Typed files and untyped files always allow both reading and
writing regardless of whether they were opened with Reset or Rewrite.
Every file is a linear sequence of components, each of which has the component type
(or record type) of the file. The components are numbered starting with zero.
Files are normally accessed sequentially. That is, when a component is read using the
standard procedure Read or written using the standard procedure Write, the current
file position moves to the next numerically ordered file component. Typed files and
untyped files can also be accessed randomly through the standard procedure Seek,
which moves the current file position to a specified component. The standard
functions FilePos and FileSize can be used to determine the current file position and
the current file size.
When a program completes processing a file, the file must be closed using the
standard procedure CloseFile. After a file is closed, its associated external file is
updated. The file variable can then be associated with another external file.
By default, all calls to standard I/O procedures and functions are automatically
checked for errors, and if an error occurs an exception is raised (or the program is
terminated if exception handling is not enabled). This automatic checking can be
turned on and off using the {$I+} and {$I–} compiler directives. When I/O checking is
off—that is, when a procedure or function call is compiled in the {$I–} state—an I/O
error doesn’t cause an exception to be raised; to check the result of an I/O operation,
you must call the standard function IOResult instead.
You must call the IOResult function to clear an error, even if you aren’t interested in
the error. If you don’t clear an error and {$I+} is the current state, the next I/O
function call will fail with the lingering IOResult error.
Text files
This section summarizes I/O using file variables of the standard type Text.
When a text file is opened, the external file is interpreted in a special way: It is
considered to represent a sequence of characters formatted into lines, where each line
is terminated by an end-of-line marker (a carriage-return character, possibly
followed by a linefeed character). The type Text is distinct from the type file of Char.
For text files, there are special forms of Read and Write that let you read and write
values that are not of type Char. Such values are automatically translated to and from
their character representation. For example, Read(F, I), where I is a type Integer
variable, reads a sequence of digits, interprets that sequence as a decimal integer, and
stores it in I.
There are two standard text file variables, Input and Output. The standard file
variable Input is a read-only file associated with the operating system’s standard
input (typically, the keyboard). The standard file variable Output is a write-only file
associated with the operating system’s standard output (typically, the display).
Before an application begins executing, Input and Output are automatically opened,
as if the following statements were executed:
AssignFile(Input, '');
Reset(Input);
AssignFile(Output, '');
Rewrite(Output);
Note For Win32 applications, text-oriented I/O is available only in console applications—
that is, applications compiled with the “Generate console application” option
checked on the Linker page of the Project Options dialog box or with the -cc
command-line compiler option. In a GUI (non-console) application, any attempt to
read or write using Input or Output will produce an I/O error.
Some of the standard I/O routines that work on text files don’t need to have a file
variable explicitly given as a parameter. If the file parameter is omitted, Input or
Output is assumed by default, depending on whether the procedure or function is
input- or output-oriented. For example, Read(X) corresponds to Read(Input, X) and
Write(X) corresponds to Write(Output, X).
If you do specify a file when calling one of the input or output routines that work on
text files, the file must be associated with an external file using AssignFile, and
opened using Reset, Rewrite, or Append. An error occurs if you pass a file that was
opened with Reset to an output-oriented procedure or function. An error also occurs
if you pass a file that was opened with Rewrite or Append to an input-oriented
procedure or function.
Untyped files
Untyped files are low-level I/O channels used primarily for direct access to disk files
regardless of type and structuring. An untyped file is declared with the word file
and nothing more. For example,
var DataFile: file;
For untyped files, the Reset and Rewrite procedures allow an extra parameter to
specify the record size used in data transfers. For historical reasons, the default
record size is 128 bytes. A record size of 1 is the only value that correctly reflects the
exact size of any file. (No partial records are possible when the record size is 1.)
Except for Read and Write, all typed-file standard procedures and functions are also
allowed on untyped files. Instead of Read and Write, two procedures called BlockRead
and BlockWrite are used for high-speed data transfers.
To associate the device-interface functions with a specific file, you must write a
customized Assign procedure. The Assign procedure must assign the addresses of the
four device-interface functions to the four function pointers in the text file variable. In
addition, it should store the fmClosed “magic” constant in the Mode field, store the
size of the text file buffer in BufSize, store a pointer to the text file buffer in BufPtr, and
clear the Name string.
Assuming, for example, that the four device-interface functions are called DevOpen,
DevInOut, DevFlush, and DevClose, the Assign procedure might look like this:
procedure AssignDev(var F: Text);
begin
with TTextRec(F) do
begin
Mode := fmClosed;
BufSize := SizeOf(Buffer);
BufPtr := @Buffer;
OpenFunc := @DevOpen;
InOutFunc := @DevInOut;
FlushFunc := @DevFlush;
CloseFunc := @DevClose;
Name[0] := #0;
end;
end;
The device-interface functions can use the UserData field in the file record to store
private information. This field isn’t modified by the product file system at any time.
Device functions
The functions that make up a text file device driver are described below.
Wide-character strings
The System unit provides three functions, WideCharToString, WideCharLenToString,
and StringToWideChar, that can be used to convert null-terminated wide character
strings to single- or double-byte long strings.
Assignment will also convert between strings. For instance, the following are both
valid:
MyAnsiString := MyWideString;
MyWideString := MyAnsiString;
For more information about wide-character strings, see “About extended character
sets” on page 5-13.
For information on format strings, see “Format strings” in the online Help.
II
Special topics
Part II
The chapters in Part II cover specialized language features and advanced topics.
These chapters include:
• Chapter 9, “Libraries and packages”
• Chapter 10, “Object interfaces”
• Chapter 11, “Memory management”
• Chapter 12, “Program control”
• Chapter 13, “Inline assembly code”
Special topics
Chapter
Static loading
The simplest way to import a procedure or function is to declare it using the external
directive. For example,
On Windows:
procedure DoSomething; external 'MYLIB.DLL';
On Linux:
procedure DoSomething; external 'mylib.so';
If you include this declaration in a program, MYLIB.DLL (Windows) or mylib.so
(Linux) is loaded once, when the program starts. Throughout execution of the
program, the identifier DoSomething always refers to the same entry point in the same
shared library.
Declarations of imported routines can be placed directly in the program or unit
where they are called. To simplify maintenance, however, you can collect external
declarations into a separate “import unit” that also contains any constants and types
required for interfacing with the library. Other modules that use the import unit can
call any routines declared in it.
For more information about external declarations, see “External declarations” on
page 6-6.
Dynamic loading
You can access routines in a library through direct calls to OS library functions,
including LoadLibrary, FreeLibrary, and GetProcAddress. In Windows, these functions
are declared in Windows.pas; on Linux, they are implemented for compatibility in
SysUtils.pas; the actual Linux OS routines are dlopen, dlclose, and dlsym (all declared
in libc; see the man pages for more information). In this case, use procedural-type
variables to reference the imported routines.
For example, on Windows or Linux:
uses Windows, ...; {On Linux, replace Windows with SysUtils }
type
TTimeRec = record
Second: Integer;
Minute: Integer;
Hour: Integer;
end;
TGetTime = procedure(var Time: TTimeRec);
THandle = Integer;
var
Time: TTimeRec;
Handle: THandle;
GetTime: TGetTime;
ƒ
begin
Handle := LoadLibrary('libraryname');
if Handle <> 0 then
begin
@GetTime := GetProcAddress(Handle, 'GetTime');
if @GetTime <> nil then
begin
GetTime(Time);
with Time do
WriteLn('The time is ', Hour, ':', Minute, ':', Second);
end;
FreeLibrary(Handle);
end;
end;
When you import routines this way, the library is not loaded until the code
containing the call to LoadLibrary executes. The library is later unloaded by the call to
FreeLibrary. This allows you to conserve memory and to run your program even
when some of the libraries it uses are not present.
This same example can also be written on Linux as follows:
uses Libc, ...;
type
TTimeRec = record
Second: Integer;
Minute: Integer;
Hour: Integer;
end;
TGetTime = procedure(var Time: TTimeRec);
THandle = Pointer;
var
Time: TTimeRec;
Handle: THandle;
GetTime: TGetTime;
ƒ
begin
Handle := dlopen('datetime.so', RTLD_LAZY);
if Handle <> 0 then
begin
@GetTime := dlsym(Handle, 'GetTime');
if @GetTime <> nil then
begin
GetTime(Time);
with Time do
WriteLn('The time is ', Hour, ':', Minute, ':', Second);
end;
dlclose(Handle);
end;
end;
In this case, when importing routines, the shared object is not loaded until the code
containing the call to dlopen executes. The shared object is later unloaded by the call
to dlclose. This also allows you to conserve memory and to run your program even
when some of the shared objects it uses are not present.
Libraries can be built from multiple units. In this case, the library source file is
frequently reduced to a uses clause, an exports clause, and the initialization code. For
example,
library Editors;
uses EdInit, EdInOut, EdFormat, EdPrint;
exports
InitEditors,
DoneEditors name Done,
InsertText name Insert,
DeleteSelection name Delete,
FormatSelection,
PrintSelection name Print,
ƒ
SetErrorHandler;
begin
InitLibrary;
end.
You can put exports clauses in the interface or implementation section of a unit. Any
library that includes such a unit in its uses clause automatically exports the routines
listed the unit’s exports clauses—without the need for an exports clause of its own.
The directive local, which marks routines as unavailable for export, is platform-
specific and has no effect in Windows programming.
On Linux, the local directive provides a slight performance optimization for routines
that are compiled into a library but are not exported. This directive can be specified
for stand-alone procedures and functions, but not for methods. A routine declared
with local—for example,
function Contraband(I: Integer): Integer; local;
does not refresh the EBX register and hence
• cannot be exported from a library.
• cannot be declared in the interface section of a unit.
• cannot have its address taken or be assigned to a procedural-type variable.
• if it is a pure assembler routine, cannot be called from another unit unless the caller
sets up EBX.
In the body of the procedure, you can specify actions to take depending on which
parameter is passed to the procedure.
Packages
A package is a specially compiled library used by applications, the IDE, or both.
Packages allow you to rearrange where code resides without affecting the source
code. This is sometimes referred to as application partitioning.
Runtime packages provide functionality when a user runs an application. Design-time
packages are used to install components in the IDE and to create special property
editors for custom components. A single package can function at both design time
and runtime, and design-time packages frequently work by referencing runtime
packages in their requires clauses.
To distinguish them from other libraries, packages are stored in files:
On Windows:
Package files end with the .bpl (Borland package library) extension.
On Linux:
Packages generally begin with the prefix bpl and have a .so extension.
Ordinarily, packages are loaded statically when an applications starts. But you can
use the LoadPackage and UnloadPackage routines (in the SysUtils unit) to load packages
dynamically.
Note When an application utilizes packages, the name of each packaged unit still must
appear in the uses clause of any source file that references it. For more information
about packages, see the online Help.
where packageName is any valid identifier. The requiresClause and containsClause are
both optional. For example, the following code declares the DATAX package.
package DATAX;
requires
rtl,
clx;
contains Db, DBLocal, DBXpress, ... ;
end.
The requires clause lists other, external packages used by the package being
declared. It consists of the directive requires, followed by a comma-delimited list of
package names, followed by a semicolon. If a package does not reference other
packages, it does not need a requires clause.
The contains clause identifies the unit files to be compiled and bound into the
package. It consists of the directive contains, followed by a comma-delimited list of
unit names, followed by a semicolon. Any unit name may be followed by the
reserved word in and the name of a source file, with or without a directory path, in
single quotation marks; directory paths can be absolute or relative. For example,
On Windows:
contains MyUnit in 'C:\MyProject\MyUnit.pas'; // Windows
On Linux:
contains MyUnit in '/home/developer/MyProject/MyUnit.pas'; // Linux
Naming packages
A compiled package involves several generated files. For example, the source file for
the package called DATAX is DATAX.dpk, from which the compiler generates an
executable and a binary image called
On Windows:
DATAX.bpl and DATAX.dcp
On Linux:
bplDATAX.so and DATAX.dcp.
DATAX is used to refer to the package in the requires clauses of other packages, or
when using the package in an application. Package names must be unique within a
project.
Generated files
The following table lists the files produced by the successful compilation of a
package.
Using the –$G– switch may prevent a package from being used in the same
application with other packages.
Other command-line options may be used, if appropriate, when compiling packages.
10
Object interfaces
Chapter10
Interface types
Interfaces, like classes, can be declared only in the outermost scope of a program or
unit, not in a procedure or function declaration. An interface type declaration has the
form
type interfaceName = interface (ancestorInterface)
['{GUID}']
memberList
end;
where (ancestorInterface) and ['{GUID}'] are optional. In most respects, interface
declarations resemble class declarations, but the following restrictions apply.
• The memberList can include only methods and properties. Fields are not allowed in
interfaces.
• Since an interface has no fields, property read and write specifiers must be
methods.
• All members of an interface are public. Visibility specifiers and storage specifiers
are not allowed. (But an array property can be declared as default.)
• Interfaces have no constructors or destructors. They cannot be instantiated, except
through classes that implement their methods.
• Methods cannot be declared as virtual, dynamic, abstract, or override. Since
interfaces do not implement their own methods, these designations have no
meaning.
Here is an example of an interface declaration:
type
IMalloc = interface(IInterface)
['{00000002-0000-0000-C000-000000000046}']
function Alloc(Size: Integer): Pointer; stdcall;
function Realloc(P: Pointer; Size: Integer): Pointer; stdcall;
procedure Free(P: Pointer); stdcall;
function GetSize(P: Pointer): Integer; stdcall;
function DidAlloc(P: Pointer): Integer; stdcall;
procedure HeapMinimize; stdcall;
end;
In some interface declarations, the interface reserved word is replaced by
dispinterface. This construction (along with the dispid, read only, and write only
directives) is platform-specific and is not used in Linux programming.
Interface identification
An interface declaration can specify a globally unique identifier (GUID), represented
by a string literal enclosed in brackets immediately preceding the member list. The
GUID part of the declaration must have the form
['{xxxxxxxx–xxxx–xxxx–xxxx–xxxxxxxxxxxx}']
where each x is a hexadecimal digit (0 through 9 or A through F). On Windows, the
Type Library editor automatically generates GUIDs for new interfaces. You can also
generate GUIDs by pressing Ctrl+Shift+G in the Code editor.
A GUID is a 16-byte binary value that uniquely identifies an interface. If an interface
has a GUID, you can use interface querying to get references to its implementations.
(See “Interface querying” on page 10-10.)
The TGUID and PGUID types, declared in the System unit, are used to manipulate
GUIDs.
type
PGUID = ^TGUID;
TGUID = packed record
D1: Longword;
D2: Word;
D3: Word;
D4: array[0..7] of Byte;
end;
When you declare a typed constant of type TGUID, you can use a string literal to
specify its value. For example,
const IID_IMalloc: TGUID = '{00000002-0000-0000-C000-000000000046}';
In procedure and function calls, either a GUID or an interface identifier can serve as a
value or constant parameter of type TGUID. For example, given the declaration
function Supports(Unknown: IInterface; const IID: TGUID): Boolean;
Supports can be called in either of two ways
if Supports(Allocator, IMalloc) then ...
or
if Supports(Allocator, IID_IMalloc) then ...
Interface properties
Properties declared in an interface are accessible only through expressions of the
interface type; they cannot be accessed through class-type variables. Moreover,
interface properties are visible only within programs where the interface is compiled.
In an interface, property read and write specifiers must be methods, since fields are
not available.
Forward declarations
An interface declaration that ends with the reserved word interface and a semicolon,
without specifying an ancestor, GUID, or member list, is a forward declaration. A
forward declaration must be resolved by a defining declaration of the same interface
within the same type declaration section. In other words, between a forward
declaration and its defining declaration, nothing can occur except other type
declarations.
Forward declarations allow mutually dependent interfaces. For example,
type
IControl = interface;
IWindow = interface
['{00000115-0000-0000-C000-000000000044}']
function GetControl(Index: Integer): IControl;
ƒ
end;
IControl = interface
['{00000115-0000-0000-C000-000000000049}']
function GetWindow: IWindow;
ƒ
end;
Mutually derived interfaces are not allowed. For example, it is not legal to derive
IWindow from IControl and also derive IControl from IWindow.
Implementing interfaces
Once an interface has been declared, it must be implemented in a class before it can
be used. The interfaces implemented by a class are specified in the class’s declaration,
after the name of the class’s ancestor. Such declarations have the form
type className = class (ancestorClass, interface1, ..., interfacen)
memberList
end;
For example,
type
TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo)
ƒ
end;
declares a class called TMemoryManager that implements the IMalloc and IErrorInfo
interfaces. When a class implements an interface, it must implement (or inherit an
implementation of) each method declared in the interface.
Here is the declaration of TInterfacedObject in the System unit.
type
TInterfacedObject = class(TObject, IInterface)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
class function NewInstance: TObject; override;
property RefCount: Integer read FRefCount;
end;
TInterfacedObject implements the IInterface interface. Hence TInterfacedObject declares
and implements each of IInterface’s three methods.
Classes that implement interfaces can also be used as base classes. (The first example
above declares TMemoryManager as a direct descendent of TInterfacedObject.) Since
every interface inherits from IInterface, a class that implements interfaces must
implement the QueryInterface, _AddRef, and _Release methods. The System unit’s
TInterfacedObject implements these methods and is thus a convenient base from
which to derive other classes that implement interfaces.
When an interface is implemented, each of its methods is mapped onto a method in
the implementing class that has the same result type, the same calling convention, the
same number of parameters, and identically typed parameters in each position. By
default, each interface method is mapped to a method of the same name in the
implementing class.
Interface references
If you declare a variable of an interface type, the variable can reference instances of
any class that implements the interface. Such variables allow you to call interface
methods without knowing at compile time where the interface is implemented. But
they are subject to the following:
• An interface-type expression gives you access only to methods and properties
declared in the interface, not to other members of the implementing class.
• An interface-type expression cannot reference an object whose class implements a
descendant interface, unless the class (or one that it inherits from) explicitly
implements the ancestor interface as well.
For example,
type
IAncestor = interface
end;
IDescendant = interface(IAncestor)
procedure P1;
end;
TSomething = class(TInterfacedObject, IDescendant)
procedure P1;
procedure P2;
end;
ƒ
var
D: IDescendant;
A: IAncestor;
begin
D := TSomething.Create; // works!
A := TSomething.Create; // error
D.P1; // works!
D.P2; // error
end;
In this example,
• A is declared as a variable of type IAncestor. Because TSomething does not list
IAncestor among the interfaces it implements, a TSomething instance cannot be
assigned to A. But if we changed TSomething’s declaration to
TSomething = class(TInterfacedObject, IAncestor, IDescendant)
ƒ
the first error would become a valid assignment.
• D is declared as a variable of type IDescendant. While D references an instance of
TSomething, we cannot use it to access TSomething’s P2 method, since P2 is not a
method of IDescendant. But if we changed D’s declaration to
D: TSomething;
the second error would become a valid method call.
Interface assignment-compatibility
Variables of a given class type are assignment-compatible with any interface type
implemented by the class. Variables of an interface type are assignment-compatible
with any ancestor interface type. The value nil can be assigned to any interface-type
variable.
An interface-type expression can be assigned to a variant. If the interface is of type
IDispatch or a descendant, the variant receives the type code varDispatch. Otherwise,
the variant receives the type code varUnknown.
A variant whose type code is varEmpty, varUnknown, or varDispatch can be assigned
to an IInterface variable. A variant whose type code is varEmpty or varDispatch can be
assigned to an IDispatch variable.
Interface typecasts
An interface-type expression can be cast to Variant. If the interface is of type IDispatch
or a descendant, the resulting variant has the type code varDispatch. Otherwise, the
resulting variant has the type code varUnknown.
A variant whose type code is varEmpty, varUnknown, or varDispatch can be cast to
IInterface. A variant whose type code is varEmpty or varDispatch can be cast to
IDispatch.
Interface querying
You can use the as operator to perform checked interface typecasts. This is known as
interface querying, and it yields an interface-type expression from an object reference
or from another interface reference, based on the actual (runtime) type of the object.
An interface query has the form
object as interface
where object is an expression of an interface or variant type or denotes an instance of
a class that implements an interface, and interface is any interface declared with a
GUID.
An interface query returns nil if object is nil. Otherwise, it passes the GUID of interface
to the QueryInterface method in object, raising an exception unless QueryInterface
returns zero. If QueryInterface returns zero (indicating that object’s class implements
interface), the interface query returns an interface reference to object.
Some Automation servers allow you to omit parameters from a method call,
accepting their default values. For example,
Word.FileSaveAs('test.doc');
Word.FileSaveAs('test.doc', 6);
Word.FileSaveAs('test.doc',,,'secret');
Word.FileSaveAs('test.doc', Password := 'secret');
Word.FileSaveAs(Password := 'secret', Name := 'test.doc');
Automation method call parameters can be of integer, real, string, Boolean, and
variant types. A parameter is passed by reference if the parameter expression
consists only of a variable reference, and if the variable reference is of type Byte,
Smallint, Integer, Single, Double, Currency, TDateTime, AnsiString, WordBool, or Variant.
If the expression is not of one of these types, or if it is not just a variable, the
parameter is passed by value. Passing a parameter by reference to a method that
expects a value parameter causes COM to fetch the value from the reference
parameter. Passing a parameter by value to a method that expects a reference
parameter causes an error.
Memory management
Chapter11
11
This chapter explains how programs use memory and describes the internal formats
of Delphi data types.
Variables
Global variables are allocated on the application data segment and persist for the
duration of the program. Local variables (declared within procedures and functions)
reside in an application’s stack. Each time a procedure or function is called, it
allocates a set of local variables; on exit, the local variables are disposed of. Compiler
optimization may eliminate variables earlier.
Note On Linux, stack size is set by the environment only.
On Windows, an application’s stack is defined by two values: the minimum stack size
and the maximum stack size. The values are controlled through the $MINSTACKSIZE
and $MAXSTACKSIZE compiler directives, and default to 16,384 (16K) and
1,048,576 (1Mb) respectively. An application is guaranteed to have the minimum
stack size available, and an application’s stack is never allowed to grow larger than
the maximum stack size. If there is not enough memory available to satisfy an
application’s minimum stack requirement, Windows will report an error upon
attempting to start the application.
If a Windows application requires more stack space than specified by the minimum
stack size, additional memory is automatically allocated in 4K increments. If
allocation of additional stack space fails, either because more memory is not available
or because the total size of the stack would exceed the maximum stack size, an
EStackOverflow exception is raised. (Stack overflow checking is completely automatic.
The $S compiler directive, which originally controlled overflow checking, is
maintained for backward compatibility.)
On Windows or Linux, dynamic variables created with the GetMem or New
procedure are heap-allocated and persist until they are deallocated with FreeMem or
Dispose.
Long strings, wide strings, dynamic arrays, variants, and interfaces are heap-
allocated, but their memory is managed automatically.
Integer types
The format of an integer-type variable depends on its minimum and maximum
bounds.
• If both bounds are within the range –128..127 (Shortint), the variable is stored as a
signed byte.
• If both bounds are within the range 0..255 (Byte), the variable is stored as an
unsigned byte.
• If both bounds are within the range –32768..32767 (Smallint), the variable is stored
as a signed word.
• If both bounds are within the range 0..65535 (Word), the variable is stored as an
unsigned word.
• If both bounds are within the range –2147483648..2147483647 (Longint), the
variable is stored as a signed double word.
• If both bounds are within the range 0..4294967295 (Longword), the variable is
stored as an unsigned double word.
• Otherwise, the variable is stored as a signed quadruple word (Int64).
Character types
A Char, an AnsiChar, or a subrange of a Char type is stored as an unsigned byte. A
WideChar is stored as an unsigned word.
Boolean types
A Boolean type is stored as a Byte, a ByteBool is stored as a Byte, a WordBool type is
stored as a Word, and a LongBool is stored as a Longint.
A Boolean can assume the values 0 (False) and 1 (True). ByteBool, WordBool, and
LongBool types can assume the values 0 (False) or nonzero (True).
Enumerated types
An enumerated type is stored as an unsigned byte if the enumeration has no more
than 256 values and the type was declared in the {$Z1} state (the default). If an
enumerated type has more than 256 values, or if the type was declared in the {$Z2}
state, it is stored as an unsigned word. If an enumerated type is declared in the {$Z4}
state, it is stored as an unsigned double-word.
Real types
The real types store the binary representation of a sign (+ or –), an exponent, and a
significand. A real value has the form
+/– significand * 2exponent
where the significand has a single bit to the left of the binary decimal point. (That is, 0
<= significand < 2.)
In the figures that follow, the most significant bit is always on the left and the least
significant bit on the right. The numbers at the top indicate the width (in bits) of each
field, with the leftmost items stored at the highest addresses. For example, for a
Real48 value, e is stored in the first byte, f in the following five bytes, and s in the most
significant bit of the last byte.
Pointer types
A Pointer type is stored in 4 bytes as a 32-bit address. The pointer value nil is stored
as zero.
The NULL character at the end of a long string memory block is automatically
maintained by the compiler and the built-in string handling routines. This makes it
possible to typecast a long string directly to a null-terminated string.
For string constants and literals, the compiler generates a memory block with the
same layout as a dynamically allocated string, but with a reference count of –1. When
a long string variable is assigned a string constant, the string pointer is assigned the
address of the memory block generated for the string constant. The built-in string
handling routines know not to attempt to modify blocks that have a reference count
of –1.
The string length is the number of bytes, so it is twice the number of wide characters
contained in the string.
The NULL character at the end of a wide string memory block is automatically
maintained by the compiler and the built-in string handling routines. This makes it
possible to typecast a wide string directly to a null-terminated string.
Note On Linux, wide strings are implemented exactly as long strings.
Set types
A set is a bit array where each bit indicates whether an element is in the set or not.
The maximum number of elements in a set is 256, so a set never occupies more than
32 bytes. The number of bytes occupied by a particular set is equal to
(Max div 8) – (Min div 8) + 1
where Max and Min are the upper and lower bounds of the base type of the set. The
byte number of a specific element E is
(E div 8) – (Min div 8)
and the bit number within that byte is
E mod 8
where E denotes the ordinal value of the element. When possible, the compiler stores
sets in CPU registers, but a set always resides in memory if it is larger than the
generic Integer type or if the program contains code that takes the address of the set.
Record types
When a record type is declared in the {$A+} state (the default), and when the
declaration does not include a packed modifier, the type is an unpacked record type,
and the fields of the record are aligned for efficient access by the CPU. The alignment
is controlled by the type of each field and by whether fields are declared together.
Every data type has an inherent alignment, which is automatically computed by the
compiler. The alignment can be 1, 2, 4, or 8, and represents the byte boundary that a
value of the type must be stored on to provide the most efficient access. The table
below lists the alignments for all data types.
To ensure proper alignment of the fields in an unpacked record type, the compiler
inserts an unused byte before fields with an alignment of 2, and up to three unused
bytes before fields with an alignment of 4, if required. Finally, the compiler rounds
the total size of the record upward to the byte boundary specified by the largest
alignment of any of the fields.
If two fields share a common type specification, they are packed even if the
declaration does not include the packed modifier and the record type is not declared
in the {$A–} state. Thus, for example, given the following declaration
type
TMyRecord = record
A, B: Extended;
C: Extended;
end;
A and B are packed (aligned on byte boundaries) because they share the same type
specification. The compiler pads the structure with unused bytes to ensure that C
appears on a quadword boundary.
When a record type is declared in the {$A–} state, or when the declaration includes
the packed modifier, the fields of the record are not aligned, but are instead assigned
consecutive offsets. The total size of such a packed record is simply the size of all the
fields. Because data alignment can change, it's a good idea to pack any record
structure that you intend to write to disk or pass in memory to another module
compiled using a different version of the compiler.
File types
File types are represented as records. Typed files and untyped files occupy 332 bytes,
which are laid out as follows:
type
TFileRec = packed record
Handle: Integer;
Mode: word;
Flags: word;
case Byte of
0: (RecSize: Cardinal);
1: (BufSize: Cardinal;
BufPos: Cardinal;
BufEnd: Cardinal;
BufPtr: PChar;
OpenFunc: Pointer;
InOutFunc: Pointer;
FlushFunc: Pointer;
CloseFunc: Pointer;
UserData: array[1..32] of Byte;
Name: array[0..259] of Char; );
end;
Text files occupy 460 bytes, which are laid out as follows:
type
TTextBuf = array[0..127] of Char;
TTextRec = packed record
Handle: Integer;
Mode: word;
Flags: word;
BufSize: Cardinal;
BufPos: Cardinal;
BufEnd: Cardinal;
BufPtr: PChar;
OpenFunc: Pointer;
InOutFunc: Pointer;
FlushFunc: Pointer;
CloseFunc: Pointer;
UserData: array[1..32] of Byte;
Name: array[0..259] of Char;
Buffer: TTextBuf;
end;
Handle contains the file’s handle (when the file is open).
The Mode field can assume one of the values
const
fmClosed = $D7B0;
fmInput = $D7B1;
fmOutput = $D7B2;
fmInOut = $D7B3;
where fmClosed indicates that the file is closed, fmInput and fmOutput indicate a text
file that has been reset (fmInput) or rewritten (fmOutput), fmInOut indicates a typed or
untyped file that has been reset or rewritten. Any other value indicates that the file
variable is not assigned (and hence not initialized).
The UserData field is available for user-written routines to store data in.
Name contains the file name, which is a sequence of characters terminated by a null
character (#0).
For typed files and untyped files, RecSize contains the record length in bytes, and the
Private field is unused but reserved.
For text files, BufPtr is a pointer to a buffer of BufSize bytes, BufPos is the index of the
next character in the buffer to read or write, and BufEnd is a count of valid characters
in the buffer. OpenFunc, InOutFunc, FlushFunc, and CloseFunc are pointers to the I/O
routines that control the file; see “Device functions” on page 8-5. Flags determines
the line break style as follows:
All other Flags bits are reserved for future use. See also DefaultTextLineBreakStyle and
SetLineBreakStyle.
Procedural types
A procedure pointer is stored as a 32-bit pointer to the entry point of a procedure or
function. A method pointer is stored as a 32-bit pointer to the entry point of a
method, followed by a 32-bit pointer to an object.
Class types
A class-type value is stored as a 32-bit pointer to an instance of the class, which is
called an object. The internal data format of an object resembles that of a record. The
object’s fields are stored in order of declaration as a sequence of contiguous variables.
Fields are always aligned, corresponding to an unpacked record type. Any fields
inherited from an ancestor class are stored before the new fields defined in the
descendant class.
The first 4-byte field of every object is a pointer to the virtual method table (VMT) of the
class. There is exactly one VMT per class (not one per object); distinct class types, no
matter how similar, never share a VMT. VMT’s are built automatically by the
compiler, and are never directly manipulated by a program. Pointers to VMT’s,
which are automatically stored by constructor methods in the objects they create, are
also never directly manipulated by a program.
The layout of a VMT is shown in the following table. At positive offsets, a VMT
consists of a list of 32-bit method pointers—one per user-defined virtual method in
the class type—in order of declaration. Each slot contains the address of the
corresponding virtual method’s entry point. This layout is compatible with a C++ v-
table and with COM. At negative offsets, a VMT contains a number of fields that are
internal to Delphi’s implementation. Applications should use the methods defined in
TObject to query this information, since the layout is likely to change in future
implementations of the Delphi language.
Variant types
A variant is stored as a 16-byte record that contains a type code and a value (or a
reference to a value) of the type given by the code. The System and Variants units
define constants and types for variants.
The TVarData type represents the internal structure of a Variant variable (on
Windows, this is identical to the Variant type used by COM and the Win32 API). The
TVarData type can be used in typecasts of Variant variables to access the internal
structure of a variable.
The VType field of a TVarData record contains the type code of the variant in the
lower twelve bits (the bits defined by the varTypeMask constant). In addition, the
varArray bit may be set to indicate that the variant is an array, and the varByRef bit
may be set to indicate that the variant contains a reference as opposed to a value.
The Reserved1, Reserved2, and Reserved3 fields of a TVarData record are unused.
The contents of the remaining eight bytes of a TVarData record depend on the VType
field. If neither the varArray nor the varByRef bits are set, the variant contains a value
of the given type.
If the varArray bit is set, the variant contains a pointer to a TVarArray structure that
defines an array. The type of each array element is given by the varTypeMask bits in
the VType field.
If the varByRef bit is set, the variant contains a reference to a value of the type given
by the varTypeMask and varArray bits in the VType field.
The varString type code is private. Variants containing a varString value should never
be passed to a non-Delphi function. On Windows, Delphi's Automation support
automatically converts varString variants to varOleStr variants before passing them as
parameters to external functions.
On Linux, VT_decimal is not supported.
12
Program control
Chapter12
This chapter explains how parameters and function results are stored and
transferred. The final section discusses exit procedures.
Parameter passing
Parameters are transferred to procedures and functions via CPU registers or the
stack, depending on the routine’s calling convention. For information about calling
conventions, see “Calling conventions” on page 6-5.
Variable (var) parameters are always passed by reference, as 32-bit pointers that
point to the actual storage location.
Value and constant (const) parameters are passed by value or by reference,
depending on the type and size of the parameter:
• An ordinal parameter is passed as an 8-bit, 16-bit, 32-bit, or 64-bit value, using the
same format as a variable of the corresponding type.
• A real parameter is always passed on the stack. A Single parameter occupies 4
bytes, and a Double, Comp, or Currency parameter occupies 8 bytes. A Real48
occupies 8 bytes, with the Real48 value stored in the lower 6 bytes. An Extended
occupies 12 bytes, with the Extended value stored in the lower 10 bytes.
• A short-string parameter is passed as a 32-bit pointer to a short string.
Function results
The following conventions are used for returning function result values.
• Ordinal results are returned, when possible, in a CPU register. Bytes are returned
in AL, words are returned in AX, and double-words are returned in EAX.
• Real results are returned in the floating-point coprocessor’s top-of-stack register
(ST(0)). For function results of type Currency, the value in ST(0) is scaled by 10000.
For example, the Currency value 1.234 is returned in ST(0) as 12340.
• For a string, dynamic array, method pointer, of variant result, the effects are the
same as if the function result were declared as an additional var parameter
following the declared parameters. In other words, the caller passes an additional
32-bit pointer that points to a variable in which to return the function result.
• Pointer, class, class-reference, and procedure-pointer results are returned in EAX.
• For static-array, record, and set results, if the value occupies one byte it is returned
in AL; if the value occupies two bytes it is returned in AX; and if the value
occupies four bytes it is returned in EAX. Otherwise, the result is returned in an
additional var parameter that is passed to the function after the declared
parameters.
Method calls
Methods use the same calling conventions as ordinary procedures and functions,
except that every method has an additional implicit parameter Self, which is a
reference to the instance or class in which the method is called. The Self parameter is
passed as a 32-bit pointer.
• Under the register convention, Self behaves as if it were declared before all other
parameters. It is therefore always passed in the EAX register.
• Under the pascal convention, Self behaves as if it were declared after all other
parameters (including the additional var parameter sometimes passed for a
function result). It is therefore pushed last, ending up at a lower address than all
other parameters.
• Under the cdecl, stdcall, and safecall conventions, Self behaves as if it were
declared before all other parameters, but after the additional var parameter (if any)
passed for a function result. It is therefore the last to be pushed, except for the
additional var parameter.
Exit procedures
Exit procedures ensure that specific actions—such as updating and closing files—are
carried out before a program terminates. The ExitProc pointer variable allows you to
“install” an exit procedure, so that it is always called as part of the program’s
termination—whether the termination is normal, forced by a call to Halt, or the result
of a runtime error. An exit procedure takes no parameters.
Note It is recommended that you use finalization sections rather than exit procedures for
all exit behavior. (See “The finalization section” on page 3-5.) Exit procedures are
available only for executables. For shared objects (Linux) or .DLLs (Windows) you
can use a similar variable, DllProc, which is called when the library is loaded as well
as when it is unloaded. For packages, exit behavior must be implemented in a
finalization section. All exit procedures are called before execution of finalization
sections.
Units as well as programs can install exit procedures. A unit can install an exit
procedure as part of its initialization code, relying on the procedure to close files or
perform other clean-up tasks.
When implemented properly, an exit procedure is part of a chain of exit procedures.
The procedures are executed in reverse order of installation, ensuring that the exit
code of one unit isn’t executed before the exit code of any units that depend on it. To
keep the chain intact, you must save the current contents of ExitProc before pointing
it to the address of your own exit procedure. Also, the first statement in your exit
procedure must reinstall the saved value of ExitProc.
The following code shows a skeleton implementation of an exit procedure.
var
ExitSave: Pointer;
procedure MyExit;
begin
ExitProc := ExitSave; // always restore old vector first
ƒ
end;
begin
ExitSave := ExitProc;
ExitProc := @MyExit;
ƒ
end.
On entry, the code saves the contents of ExitProc in ExitSave, then installs the MyExit
procedure. When called as part of the termination process, the first thing MyExit does
is reinstall the previous exit procedure.
The termination routine in the runtime library keeps calling exit procedures until
ExitProc becomes nil. To avoid infinite loops, ExitProc is set to nil before every call, so
the next exit procedure is called only if the current exit procedure assigns an address
to ExitProc. If an error occurs in an exit procedure, it is not called again.
13
Inline assembly code
Chapter13
The built-in assembler allows you to write assembly code within Delphi programs. It
has the following features:
• Allows for inline assembly
• Supports all instructions found in the Intel Pentium III, Intel MMX extensions,
Streaming SIMD Extensions (SSE), and the AMD Athlon (including 3D Now!)
• Provides no macro support, but allows for pure assembly function procedures
• Permits the use of Delphi identifiers, such as constants, types, and variables in
assembly statements
As an alternative to the built-in assembler, you can link to object files that contain
external procedures and functions. See “Linking to object files” on page 6-7 for more
information.
Note If you have external assembly code that you want to use in your applications, you
should consider rewriting it in the Delphi language or minimally reimplement it
using the inline assembler.
Register use
In general, the rules of register use in an asm statement are the same as those of an
external procedure or function. An asm statement must preserve the EDI, ESI, ESP,
EBP, and EBX registers, but can freely modify the EAX, ECX, and EDX registers. On
entry to an asm statement, EBP points to the current stack frame and ESP points to
the top of the stack. Except for ESP and EBP, an asm statement can assume nothing
about register contents on entry to the statement.
Labels
Labels are used in built-in assembly statements as they are in the Delphi language—
by writing the label and a colon before a statement. There is no limit to a label’s
length. As in Delphi, labels must be declared in a label declaration part in the block
containing the asm statement. The one exception to this rule is local labels.
Local labels are labels that start with an at-sign (@). They consist of an at-sign
followed by one or more letters, digits, underscores, or at-signs. Use of local labels is
restricted to asm statements, and the scope of a local label extends from the asm
reserved word to the end of the asm statement that contains it. A local label doesn’t
have to be declared.
Instruction opcodes
The built-in assembler supports all of the Intel-documented opcodes for general
application use. Note that operating system privileged instructions may not be
supported. Specifically, the following families of instructions are supported:
• Pentium family
• Pentium Pro and Pentium II
• Pentium III
• Pentium 4
In addition, the built-in assembler supports the following instruction sets
• AMD 3DNow! (from the AMD K6 onwards)
• AMD Enhanced 3DNow! (from the AMD Athlon onwards)
For a complete description of each instruction, refer to your microprocessor
documentation.
Assembly directives
The built-in assembler supports three assembly define directives: DB (define byte),
DW (define word), and DD (define double word). Each generates data corresponding
to the comma-separated operands that follow the directive.
The DB directive generates a sequence of bytes. Each operand can be a constant
expression with a value between –128 and 255, or a character string of any length.
Constant expressions generate one byte of code, and strings generate a sequence of
bytes with values corresponding to the ASCII code of each character.
The DW directive generates a sequence of words. Each operand can be a constant
expression with a value between –32,768 and 65,535, or an address expression. For an
address expression, the built-in assembler generates a near pointer—that is, a word
that contains the offset part of the address.
The DD directive generates a sequence of double words. Each operand can be a
constant expression with a value between –2,147,483,648 and 4,294,967,295, or an
address expression. For an address expression, the built-in assembler generates a far
pointer—that is, a word that contains the offset part of the address, followed by a
word that contains the segment part of the address.
The DQ directive defines a quad word for Int64 values.
The data generated by the DB, DW, and DD directives is always stored in the code
segment, just like the code generated by other built-in assembly statements. To
generate uninitialized or initialized data in the data segment, you should use Delphi
var or const declarations.
Some examples of DB, DW, and DD directives follow.
asm
DB FFH { One byte }
DB 0,99 { Two bytes }
DB 'A' { Ord('A') }
DB 'Hello world...',0DH,0AH { String followed by CR/LF }
DB 12,"string" { Delphi style string }
DW 0FFFFH { One word }
DW 0,9999 { Two words }
DW 'A' { Same as DB 'A',0 }
DW 'BA' { Same as DB 'A','B' }
DW MyVar { Offset of MyVar }
DW MyProc { Offset of MyProc }
DD 0FFFFFFFFH { One double-word }
DD 0,999999999 { Two double-words }
DD 'A' { Same as DB 'A',0,0,0 }
DD 'DCBA' { Same as DB 'A','B','C','D' }
DD MyVar { Pointer to MyVar }
DD MyProc { Pointer to MyProc }
end;
DMTINDEX retrieves the dynamic method table index of the passed dynamic
method. This directive also needs a fully specified class name with a method name as
a parameter, for example, TExample.DynamicMethod. To invoke the dynamic
method, call System.@CallDynaInst with the (E)SI register containing the value
obtained from DMTINDEX.
Note Methods with the message directive are implemented as dynamic methods and can
also be called using the DMTINDEX technique. For example:
TMyClass = class
procedure x; message MYMESSAGE;
end;
The following example uses both DMTINDEX and VMTOFFSET to access dynamic
and virtual methods:
program Project2;
type
TExample = class
procedure DynamicMethod; dynamic;
procedure VirtualMethod; virtual;
end;
procedure TExample.DynamicMethod;
begin
end;
procedure TExample.VirtualMethod;
begin
end;
var
e: TExample;
begin
e := TExample.Create;
try
CallDynamicMethod(e);
CallVirtualMethod(e);
finally
e.Free;
end;
end.
Operands
Inline assembler operands are expressions that consist of constants, registers,
symbols, and operators.
Within operands, the following reserved words have predefined meanings:
Table 13.1 Built-in assembler reserved words
Reserved words always take precedence over user-defined identifiers. For example,
var
Ch: Char;
ƒ
asm
MOV CH, 1
end;
loads 1 into the CH register, not into the Ch variable. To access a user-defined symbol
with the same name as a reserved word, you must use the ampersand (&) override
operator:
MOV &Ch, 1
It is best to avoid user-defined identifiers with the same names as built-in assembler
reserved words.
Expressions
The built-in assembler evaluates all expressions as 32-bit integer values. It doesn’t
support floating-point and string values, except string constants.
Expressions are built from expression elements and operators, and each expression has
an associated expression class and expression type.
the built-in assembler cannot compute the value of X + Y at compile time. In this case,
to move the sum of X and Y into Z you would use
asm
MOV EAX,X
ADD EAX,Y
MOV Z,EAX
end;
In a Delphi expression, a variable reference denotes the contents of the variable. But in
an assembler expression, a variable reference denotes the address of the variable. In
Delphi the expression X + 4 (where X is a variable) means the contents of X plus 4,
while to the built-in assembler it means the contents of the word at the address four
bytes higher than the address of X. So, even though you’re allowed to write
asm
MOV EAX,X+4
end;
this code doesn’t load the value of X plus 4 into AX; instead, it loads the value of a
word stored four bytes beyond X. The correct way to add 4 to the contents of X is
asm
MOV EAX,X
ADD EAX,4
end;
Expression elements
The elements of an expression are constants, registers, and symbols.
Constants
The built-in assembler supports two types of constant: numeric constants and string
constants.
Numeric constants
Numeric constants must be integers, and their values must be between –2,147,483,648
and 4,294,967,295.
By default, numeric constants use decimal notation, but the built-in assembler also
supports binary, octal, and hexadecimal. Binary notation is selected by writing a B
after the number, octal notation by writing an O after the number, and hexadecimal
notation by writing an H after the number or a $ before the number.
Numeric constants must start with one of the digits 0 through 9 or the $ character.
When you write a hexadecimal constant using the H suffix, an extra zero is required
in front of the number if the first significant digit is one of the digits A through F. For
example, 0BAD4H and $BAD4 are hexadecimal constants, but BAD4H is an identifier
because it starts with a letter.
String constants
String constants must be enclosed in single or double quotation marks. Two
consecutive quotation marks of the same type as the enclosing quotation marks count
as only one character. Here are some examples of string constants:
'Z'
'Delphi'
‘Linux’
"That's all folks"
'"That''s all folks," he said.'
'100'
'"'
"'"
String constants of any length are allowed in DB directives, and cause allocation of a
sequence of bytes containing the ASCII values of the characters in the string. In all
other cases, a string constant can be no longer than four characters and denotes a
numeric value which can participate in an expression. The numeric value of a string
constant is calculated as
Ord(Ch1) + Ord(Ch2) shl 8 + Ord(Ch3) shl 16 + Ord(Ch4) shl 24
where Ch1 is the rightmost (last) character and Ch4 is the leftmost (first) character. If
the string is shorter than four characters, the leftmost characters are assumed to be
zero. The following table shows string constants and their numeric values.
Registers
The following reserved symbols denote CPU registers in the inline assembler:
When an operand consists solely of a register name, it is called a register operand. All
registers can be used as register operands, and some registers can be used in other
contexts.
The base registers (BX and BP) and the index registers (SI and DI) can be written
within square brackets to indicate indexing. Valid base/index register combinations
are [BX], [BP], [SI], [DI], [BX+SI], [BX+DI], [BP+SI], and [BP+DI]. You can also index
with all the 32-bit registers—for example, [EAX+ECX], [ESP], and [ESP+EAX+5].
The segment registers (ES, CS, SS, DS, FS, and GS) are supported, but segments are
normally not useful in 32-bit applications.
The symbol ST denotes the topmost register on the 8087 floating-point register stack.
Each of the eight floating-point registers can be referred to using ST(X), where X is a
constant between 0 and 7 indicating the distance from the top of the register stack.
Symbols
The built-in assembler allows you to access almost all Delphi identifiers in assembly
language expressions, including constants, types, variables, procedures, and
functions. In addition, the built-in assembler implements the special symbol @Result,
which corresponds to the Result variable within the body of a function. For example,
the function
function Sum(X, Y: Integer): Integer;
begin
Result := X + Y;
end;
could be written in assembly language as
function Sum(X, Y: Integer): Integer; stdcall;
begin
asm
MOV EAX,X
ADD EAX,Y
MOV @Result,EAX
end;
end;
Identifiers can be qualified within asm statements. For example, given the
declarations
type
TPoint = record
X, Y: Integer;
end;
TRect = record
A, B: TPoint;
end;
var
P: TPoint;
R: TRect;
the following constructions can be used in an asm statement to access fields.
MOV EAX,P.X
MOV EDX,P.Y
MOV ECX,R.A.X
MOV EBX,R.B.Y
A type identifier can be used to construct variables on the fly. Each of the following
instructions generates the same machine code, which loads the contents of [EDX] into
EAX.
MOV EAX,(TRect PTR [EDX]).B.X
MOV EAX,TRect([EDX]).B.X
MOV EAX,TRect[EDX].B.X
MOV EAX,[EDX].TRect.B.X
Expression classes
The built-in assembler divides expressions into three classes: registers, memory
references, and immediate values.
An expression that consists solely of a register name is a register expression.
Examples of register expressions are AX, CL, DI, and ES. Used as operands, register
expressions direct the assembler to generate instructions that operate on the CPU
registers.
Expressions that denote memory locations are memory references. Delphi’s labels,
variables, typed constants, procedures, and functions belong to this category.
Expressions that aren’t registers and aren’t associated with memory locations are
immediate values. This group includes Delphi’s untyped constants and type
identifiers.
Immediate values and memory references cause different code to be generated when
used as operands. For example,
const
Start = 10;
var
Count: Integer;
ƒ
asm
MOV EAX,Start { MOV EAX,xxxx }
MOV EBX,Count { MOV EBX,[xxxx] }
MOV ECX,[Start] { MOV ECX,[xxxx] }
MOV EDX,OFFSET Count { MOV EDX,xxxx }
end;
Because Start is an immediate value, the first MOV is assembled into a move
immediate instruction. The second MOV, however, is translated into a move memory
instruction, as Count is a memory reference. In the third MOV, the brackets convert
Start into a memory reference (in this case, the word at offset 10 in the data segment).
In the fourth MOV, the OFFSET operator converts Count into an immediate value
(the offset of Count in the data segment).
The brackets and OFFSET operator complement each other. The following asm
statement produces identical machine code to the first two lines of the previous asm
statement.
asm
MOV EAX,OFFSET [Start]
MOV EBX,[OFFSET Count]
end;
Memory references and immediate values are further classified as either relocatable or
absolute. Relocation is the process by which the linker assigns absolute addresses to
symbols. A relocatable expression denotes a value that requires relocation at link
time, while an absolute expression denotes a value that requires no such relocation.
Typically, expressions that refer to labels, variables, procedures, or functions are
relocatable, since the final address of these symbols is unknown at compile time.
Expressions that operate solely on constants are absolute.
The built-in assembler allows you to carry out any operation on an absolute value,
but it restricts operations on relocatable values to addition and subtraction of
constants.
Expression types
Every built-in assembler expression has a type—or, more correctly, a size, because
the assembler regards the type of an expression simply as the size of its memory
location. For example, the type of an Integer variable is four, because it occupies 4
bytes. The built-in assembler performs type checking whenever possible, so in the
instructions
var
QuitFlag: Boolean;
OutBufPtr: Word;
ƒ
asm
MOV AL,QuitFlag
MOV BX,OutBufPtr
end;
the assembler checks that the size of QuitFlag is one (a byte), and that the size of
OutBufPtr is two (a word). The instruction
MOV DL,OutBufPtr
produces an error because DL is a byte-sized register and OutBufPtr is a word. The
type of a memory reference can be changed through a typecast; these are correct
ways of writing the previous instruction:
MOV DL,BYTE PTR OutBufPtr
MOV DL,Byte(OutBufPtr)
MOV DL,OutBufPtr.Byte
These MOV instructions all refer to the first (least significant) byte of the OutBufPtr
variable.
In some cases, a memory reference is untyped. One example is an immediate value
(Buffer) enclosed in square brackets:
procedure Example(var Buffer);
asm
MOV AL, [Buffer]
MOV CX, [Buffer]
MOV EDX, [Buffer]
The built-in assembler permits these instructions, because the expression [Buffer] has
no type—it just means “the contents of the location indicated by Buffer,” and the type
can be determined from the first operand (byte for AL, word for CX, and double-
word for EDX).
In cases where the type can’t be determined from another operand, the built-in
assembler requires an explicit typecast. For example,
INC BYTE PTR [ECX]
IMUL WORD PTR [EDX]
Expression operators
The built-in assembler provides a variety of operators. Precedence rules are different
from that of the Delphi language; for example, in an asm statement, AND has lower
precedence than the addition and subtraction operators. The following table lists the
built-in assembler’s expression operators in decreasing order of precedence.
A
Delphi grammar
Appendix A
Index I-1
B case-sensitivity 4-1, 4-2, 6-8
unit names and files 4-2
$B directive 4-9 -cc compiler switch 8-3
base types 5-8, 5-18, 5-19, 5-20 cdecl (calling convention) 6-5, 12-2
begin (reserved word) 3-2, 4-21, 6-2, 6-3 constructors and destructors 12-4
binary operators 4-7 Self 12-4
binding varargs 6-7
fields 7-7 Char type 5-5, 5-14, 5-29, 11-3
methods 7-10 character operators 4-9
bitwise operators,not 4-9 character sets
blanks 4-1 ANSI 5-5, 5-13
BlockRead procedure 8-4 extended 5-13
blocks 4-29 to 4-30 multibyte (MBCS) 5-13
function 3-4, 6-2, 6-3 Pascal 4-1, 4-2, 4-5
library 9-7 single-byte (SBCS) 5-13
outer and inner 4-31 character strings 4-1, 4-5, 5-47
procedure 3-4, 6-2, 6-3 characters
program 3-1, 3-2 pointers 5-29
scope 4-29 to 4-32 string literals as 4-5, 5-5
try...except 7-29, 7-32 types 5-5, 11-3
try...finally 7-33 wide 5-13, 11-3
BlockWrite procedure 8-4 checked typecasts
body (routine) 6-2 interfaces 10-10
boldface 1-2 objects 7-26
Boolean operators 4-8 Chr function 5-5
complete vs. partial evaluation 4-8 circular references
Boolean types 5-6, 11-3 packages 9-12
BORLANDMM.DLL 9-9 units 3-8
Break procedure 4-27 classes 7-1 to 7-34
exception handlers 7-31 class methods 7-1, 7-26
in try...finally block 7-34 class references 7-24
BSTR type (COM) 5-13 class types 7-1, 7-2
built-in assembler 13-1 to 13-19 comparison 4-12
built-in types 5-1 compatibility 7-3, 10-10
by reference (parameters) 6-12, 6-14, 10-13, 12-1 declaring class types 7-2, 7-4, 7-6, 7-7, 7-8, 7-17,
by value (parameters) 6-12, 10-13, 12-1 10-5
Byte type 5-4, 11-3 files and 5-26
assembler 13-16 memory 11-10
ByteBool type 5-6, 11-3 metaclasses 7-24
operators 4-12, 7-25
C scope 4-31
variants and 5-33
C 6-7 Classes unit 7-9, 7-24
C++ 10-1, 11-11 ClassParent method 7-25
calling conventions 5-31, 6-5, 12-1 class-reference types 7-24
access specifiers 6-5, 7-18 comparison 4-12
interfaces 10-3, 10-7 constructors and 7-25
methods 12-4 memory 11-11
shared libraries 9-4 variants and 5-33
varargs directive 6-7 ClassType method 7-25
calling routines 9-1 clients 3-4
Cardinal type 5-4 Close function 8-4, 8-6
carriage-return 4-1, 4-5 CloseFile function 8-6
case (reserved word) 4-25, 5-24 CloseFile procedure 8-6
case statements 4-25 CLX 1-2
Index I-3
default properties 7-21 DLLs 9-1 to 9-9
interfaces 10-2 calling routines in 6-7
default property (COM object) 5-35 dynamic arrays in 9-9
default specifier 7-6, 7-17, 7-22 dynamic variables in 9-9
DefaultHandler method 7-17 exceptions 9-9
defining declarations 6-6, 7-6, 7-8, 10-4 global variables 9-8
DefWindowProc function 7-17 loading dynamically 9-2
delegated interface 10-7 loading statically 9-2
delegation (interface implementation) 10-7 long strings in 9-9
Delphi 2-1 multithreading 9-8
$DENYPACKAGEUNIT directive 9-13 variables 9-1
dependency, units 3-7 to 3-8 writing 9-4
deprecated (directive) 4-18 dlopen 9-2
dereference operator 4-10, 5-20 dlsym 9-2
pointer overview 5-28 DMTINDEX 13-6
variants and 5-35 do (reserved word) 4-22, 4-27, 4-28, 7-30
descendants 7-3, 7-5 .dof files 2-2
$DESIGNONLY directive 9-13 Double type 5-10, 11-5
design-time packages 9-10 downto (reserved word) 4-28
.desk files 2-3 .dpk files 2-2, 9-13
desktop settings files 2-3 .dpr files 2-2, 3-1, 3-6
Destroy method 7-14, 7-15, 7-31 .dpu files 2-3, 3-7, 9-12, 9-13
destructors 7-1, 7-14, 7-15 .drc files 2-3
calling conventions 12-4 .dsk files 2-3
device drivers, text-file 8-4 dual interfaces 10-3, 10-13
device functions 8-4, 8-5 methods 6-5
.dfm files 2-2, 2-7, 7-5 DWORD type (assembler) 13-16
difference (sets) 4-11 dynamic arrays 5-20, 11-7
directives 4-1, 4-4 assigning to 5-20
assembler 13-4 comparison 5-21
compiler 3-2, 4-6 files and 5-26
list 4-4 freeing 5-42
order 7-9 in dynamically loadable libraries 9-9
directory paths memory management 11-2
in uses clause 3-6 multidimensional 5-22
disjunction 4-8 open array parameters and 6-17
bitwise 4-9 records and 5-25
dispatch interface types 10-11 truncating 5-21
Dispatch method 7-17 variants and 5-33
dispatching messages 7-17 dynamic methods 7-10, 7-11
dispatching method calls 7-11 dynamic variables 5-42
dispid (directive) 7-6, 10-2, 10-11, 10-12 in dynamically loadable libraries 9-9
dispinterface 10-11 dynamically loadable libraries 6-7, 9-1 to 9-14
dispinterface (reserved word) 10-2 dynamic arrays 9-9
Dispose procedure 5-20, 5-42, 7-4, 9-9, 11-1, 11-2 dynamic variables 9-9
div 4-7 exceptions 9-9
division 4-7 global variables 9-8
dlclose 9-2 loading statically 9-2
.DLL files 6-7, 9-1 long strings 9-9
DLL_PROCESS_DETACH 9-8 variables 9-1
DLL_THREAD_ATTACH 9-8 writing 9-4
DLL_THREAD_DETACH 9-8
DLLProc variable 9-8
Index I-5
G implementation section 3-3, 3-4, 3-7
and forward declarations 6-6
-$G- compiler switch 9-14 methods 7-8
$G directive 9-13 scope 4-31
generic types 5-1 uses clause 3-8
GetHeapStatus function 11-2 implements (directive) 7-23, 10-7
GetMem procedure 5-28, 5-42, 9-9, 11-1, 11-2 $IMPLICITBUILD directive 9-13
GetMemoryManager procedure 11-2 $IMPORTEDDATA directive 9-13
GetProcAddress function 9-2 importing routines from libraries 9-1
global identifiers 4-30 in (reserved word) 3-6, 4-11, 5-18, 5-35, 9-11
global variables 5-41 Inc procedure 5-3
dynamically loadable libraries 9-8 incrementing ordinals 5-3, 5-5
interfaces 10-10 index (directive) 6-8
memory management 11-2 index specifier 7-6, 7-17, 7-21
GlobalAlloc 11-1 index specifier (Windows only) 9-6
goto statements 4-20 indexes 4-15
grammar (formal) A-1 to A-7 array 5-19, 5-20, 5-22
GUIDs 10-1, 10-3, 10-10 array properties 7-20
generating 10-3 in var parameters 5-36, 6-13
string 5-11
H string variants 5-33
variant arrays 5-36
$H directive 5-11, 6-15 indirect unit references 3-7
Halt procedure 12-5, 12-6 inequality operator 4-11
heading inheritance 7-2, 7-3, 7-5
program 2-1, 3-1, 3-2 interfaces 10-2
routine 6-2 inherited (reserved word) 7-9, 7-14
unit 3-3 calling conventions 12-4
heap memory 5-42, 11-2 message handlers 7-16
Hello world! 2-3 InheritsFrom method 7-25
HelpContext property 7-34 initialization
hexadecimal numerals 4-4 dynamically loadable libraries 9-7
hiding class members 7-8, 7-12, 7-23 files 5-41
reintroduce 7-12 objects 7-13
hiding interface implementations 10-6 units 3-4
High function 5-3, 5-12, 5-19, 5-21, 6-17 variables 5-41
HInstance variable 9-8 variants 5-33, 5-41
hint directives 4-18 initialization section 3-3, 3-4
$HINTS directive 4-18 exceptions 7-29
Initialize procedure 5-42
I inline (reserved word) 13-1
$I directive 8-3 inline assembler code 13-1 to 13-19
identifiers 4-1, 4-2, 4-4 inner block 4-31
global and local 4-30 InOut function 8-4, 8-6
in exception handlers 7-31 input (program parameter) 3-2
qualified 3-7 Input variable 8-3
scope 4-29 to 4-32 Int64 type 4-7, 5-3, 5-4, 5-10, 11-3
IDispatch 10-10, 10-11 variants and 5-33
dual interfaces 10-13 integer operators 4-7
if...then statements 4-23 Integer type 4-7, 5-4
nested 4-24 integer types 5-4
IInterface 10-2 comparison 4-12
immediate values (assembler) 13-13 constants 5-43
conversion 4-16
data formats 11-3
Index I-7
dual-interface 6-5
dynamic 7-10, 7-11
O
implementation 7-8 object files
overloading 7-12 calling routines in 6-7
overriding 7-11, 7-12, 10-6 Object Inspector 7-5
pointers 4-13, 5-31 object types 7-4
publishing 7-5 objects 4-22, 7-1
static 7-10 ’of object’ 5-31
virtual 7-6, 7-10, 7-11 comparison 4-12
$MINSTACKSIZE directive 11-2 files and 5-26
mod 4-7 memory 11-10
multibyte character sets 5-13 of (reserved word) 4-25, 5-18, 5-20, 5-26, 5-31, 6-16,
string-handling routines 8-7 6-18, 7-24
multidimensional arrays 5-19, 5-22, 5-45 of object (method pointers) 5-31
multiple unit references 3-7 OleVariant 5-37
multiplication 4-7 OleVariant type 5-29, 5-36
multithreaded applications 5-42 on (reserved word) 7-30
dynamically loadable libraries 9-8 opcodes (assembler) 13-2, 13-3
mutually dependent classes 7-7 open array constructors 6-18, 6-21
mutually dependent units 3-8 open array parameters 6-16, 6-21
and dynamic arrays 6-17
N Open function 8-4, 8-5
OpenString 6-15
name (directive) 6-7, 6-8, 9-6 operands 4-6
named parameters 10-12 operators 4-6 to 4-14
names assembler 13-16
exported routines 9-6 class 7-25
functions 6-3, 6-4 precedence 4-13, 7-26
identifiers 4-17 or 4-8, 4-9
packages 9-11 Ord function 5-3
programs 3-1, 3-2 order of method directives 7-9
units 3-3, 3-7 ordinal types 5-3 to 5-9
naming conflicts 3-6, 4-31 ordinality 5-3
near (directive) 6-6 enumerated types 5-7, 5-8
negation 4-8 out (output) parameters 6-12, 6-14, 6-20
bitwse 4-9 out (reserved word) 6-12, 6-14
nested conditionals 4-24 outer block 4-31
nested exceptions 7-33 OutlineError 7-34
nested routines 5-31, 6-11 output (program parameter) 3-2
New procedure 5-20, 5-28, 5-42, 7-4, 9-9, 11-1, 11-2 Output variable 8-3
nil 5-28, 5-33, 5-42, 11-5 overloaded methods 7-12
nodefault specifier 7-6, 7-17, 7-22 access specifiers 7-13, 7-18
not 4-7, 4-8 publishing 7-5
Null (variants) 5-33, 5-35 overloaded procedures and functions 6-6, 6-8
null character 5-14, 11-6, 11-7, 11-10 default parameters 6-10, 6-20
null string 4-5 dynamically loadable libraries 9-6
null-terminated strings 5-14 to 5-17, 5-29, 11-6, forward declarations 6-9
11-7 overriding interface implementations 10-6
mixing with Pascal strings 5-16 overriding methods 7-11, 10-6
standard routines 8-6, 8-7 hiding and 7-12
numerals 4-1, 4-4 overriding properties 7-23
as labels 4-5, 4-20 access specifiers and 7-23
assembler 13-9 Automation 7-6
type 5-43, 6-9 hiding and 7-23
Index I-9
program, control 6-20
program (reserved word) 3-2
R
program control ?? to 12-6 raise (reserved word) 4-21, 7-29, 7-30, 7-32
programs 2-1 to 2-5, 3-1 to 3-8 range-checking 5-5, 5-9
examples 2-3 to 2-5 Read procedure 8-2, 8-3, 8-4, 8-6
syntax 3-1 to 3-3 read specifier 7-6, 7-17, 7-18
project files 2-2, 3-1, 3-2, 3-6 array properties 7-20
Project Manager 2-1 index specifier and 7-21
project options files 2-2 object interfaces 10-1, 10-4, 10-7
projects 2-6, 3-6 overloading 7-13, 7-18
properties 7-1, 7-17 to 7-24 Readln procedure 8-6
access specifiers 7-18 readonly (directive) 10-2, 10-12
array 7-5, 7-20 read-only properties 7-19
as parameters 7-18 real (floating-point) operators 4-7
declaring 7-17, 7-20 Real type 5-10
default 7-21, 10-2 real types 5-10, 11-4
interfaces 10-4 comparison 4-12
overriding 7-6, 7-23 conversion 4-16
read-only 7-19 publishing 7-5
record 7-5 Real48 type 5-10, 7-5, 11-4
write-only 7-19 $REALCOMPATIBILITY directive 5-10
protected class members 7-4, 7-5 ReallocMem procedure 5-42, 11-1
prototypes 6-2 records 4-22, 5-23 to 5-26
PShortString type 5-30 constants 5-46
PSingle type 5-29 in properties 7-5
PString type 5-29, 5-30 memory 11-8
PTextBuf type 5-30 record types 5-23
Ptr function 5-28 scope 4-31, 5-24
public class members 7-4, 7-5 variant parts 5-24 to 5-26
public identifiers (interface section) 3-4 variants and 5-33
published class members 7-4, 7-5 recursive procedure and function calls 6-1, 6-4
$M directive 7-6 reference-counting 5-13, 10-10, 11-6, 11-7
restrictions 7-5 register (calling convention) 6-5, 7-6, 7-13, 7-15,
PVariant type 5-30 12-2
PVarRec type 5-30 constructors and destructors 12-4
PWideChar type 5-14, 5-29 dynamically loadable libraries 9-4
PWideString type 5-30 interfaces 10-3, 10-7
PWordArray type 5-30 Self 12-4
registers 6-5, 12-2, 12-3
Q assembler 13-2, 13-11, 13-13, 13-19
storing sets 11-7
qualified identifiers 3-7, 4-3, 4-32, 5-23 reintroduce (directive) 7-12, 7-13
in typecasts 4-15, 4-16 relational operators 4-11
pointers 5-28 _Release method 10-2, 10-5, 10-10
with Self 7-10 relocatable expressions (assembler) 13-14
querying (interfaces) 10-10 Rename procedure 8-6
QueryInterface method 10-2, 10-5, 10-11 repeat statements 4-21, 4-27
quoted strings 4-5, 5-47 requires clause 9-10, 9-11, 9-12
assembler 13-10 .RES files 2-2, 3-2
QWORD type (assembler) 13-16 reserved words 4-1, 4-2, 4-3
assembler 13-7
list 4-3
Reset procedure 8-2, 8-4, 8-5, 8-6
resident (directive) 9-6
resource files 2-2, 2-3, 3-2
Index I-11
StringToWideChar function 8-7 to (reserved word) 4-28
strong typing 5-1 TObject 7-3, 7-17, 7-25
structured statements 4-21 tokens 4-1
structured types 5-17 TPersistent 7-6
files and 5-26 True 5-6, 11-3
records and 5-25 true constants 5-43
variants and 5-33 try...except statements 4-21, 7-29, 7-30
structures 5-23 try...finally statements 4-21, 7-33
StrUpper function 5-16 TTextBuf type 5-30
subrange types 4-7, 4-25, 5-8 TTextRec type 5-30
subset operator 4-11 TVarData 5-34, 11-12
subtraction 4-7 TVarRec 5-30
pointers 4-10 TVarRec type 6-18
Succ function 5-3 TWordArray 5-30
successor 5-3 type identifiers 5-2
superset operator 4-11 Type Library editor 10-3
symbol pairs 4-2 typecasts 4-15 to 4-17, 7-8
symbols 4-1, 4-2 checked 7-26, 10-10
assembler 13-11 enumerated types 5-8
syntax in constant declarations 5-43
descriptions 1-2 untyped parameters 6-14
formal A-1 to A-7 variants 5-34
System unit 3-1, 3-5, 5-29, 5-34, 5-35, 6-18, 7-3, 7-29, type-checking (objects) 7-26
8-1, 8-7, 10-2, 10-3, 10-5, 10-11, 11-12 types 5-1 to 5-40
dynamically loadable libraries 9-7, 9-8 array 5-19 to 5-23, 11-7
memory management 11-2 assembler 13-15
modifying 8-1 assignment-compatibility 5-38
scope 4-32 automatable 7-6, 10-11
uses clause and 8-1 Boolean 5-6, 11-3
SysUtils unit 3-5, 5-29, 6-11, 6-18, 7-27, 7-28, 7-29, built-in 5-1
7-34 character 5-5, 11-3
dynamically loadable libraries 9-9 class 7-1, 7-2, 7-4, 7-6, 7-7, 7-8, 7-17, 11-10
uses clause and 8-1 classification 5-1
class-reference 7-24, 11-11
T compatibility 5-17, 5-31, 5-38
constants 5-43
$T directive 4-12 declared 5-1
tag (records) 5-24 declaring 5-39
TAggregatedObject 10-7 dispatch interface 10-11
TBYTE type (assembler) 13-16 enumerated 5-6 to 5-8, 11-3
TByteArray type 5-29 exception 7-28
TClass 7-3, 7-24, 7-25 file 5-26, 11-9
TCollection 7-24 fundamental 5-1
Add method 7-9 generic 5-1
TCollectionItem 7-25 integer 5-4, 11-3
TDateTime 5-35 interface 10-1 to 10-4
text files 8-2, 8-3 internal data formats 11-3 to 11-12
Text type 5-26, 8-3 object 7-4
text-file device drivers 8-4 ordinal 5-3 to 5-9
TextFile type 5-26 pointer 5-29 to 5-30
TGUID 10-3 predefined 5-1
then (reserved word) 4-23 procedural 5-30 to 5-33, 11-10
thread variables 5-42 real 5-10, 11-4
in packages 9-11 record 5-23 to 5-26, 11-8
threadvar 5-42 scope 5-39
TInterfacedObject 10-2, 10-5
Index I-13
W wrap-around (ordinal types) 5-5
Write procedure 8-2, 8-3, 8-4, 8-6
$WARNINGS directive 4-18 write procedures 5-3
$WEAKPACKAGEUNIT directive 9-13 write specifier 7-6, 7-17, 7-18
while statements 4-21, 4-27 array properties 7-20
wide characters and strings 5-13 index specifier and 7-21
memory management 11-2 object interfaces 10-1, 10-4
standard routines 8-7 overloading 7-13, 7-18
WideChar type 4-10, 5-5, 5-11, 5-14, 5-29, 11-3 Writeln procedure 2-4, 8-6
WideCharLenToString function 8-7 writeonly (directive) 10-2, 10-12
WideCharToString function 8-7 write-only properties 7-19
WideString type 5-11, 5-13, 5-14, 5-30
memory management 11-6 X
Windows 7-17
memory management 11-1, 11-2 $X directive 4-5, 4-19, 5-14, 6-1, 6-3
messages 7-15 .xfm files 2-2, 2-7, 7-5
variants and 11-12 xor 4-8, 4-9
Windows unit 9-2
with statements 4-21, 4-22, 5-24 Z
Word type 5-4, 11-3
-$Z- compiler switch 9-14
assembler 13-16
$Z directive 11-3
WordBool type 5-6, 11-3