Delphi 5 Object Pascal Language Guide PDF
Delphi 5 Object Pascal Language Guide PDF
Language Guide
Borland
Delphi 5
for Windows 98, Windows 95, & Windows NT
Inprise Corporation
100 Enterprise Way, Scotts Valley, CA 95066-3249
Refer to the file DEPLOY.TXT located in the root directory of your Delphi 5 product for a complete list of files that you
can distribute in accordance with the Delphi 5 License Statement and Limited Warranty.
Inprise may have patents and/or pending patent applications covering subject matter in this document. The
furnishing of this document does not give you any license to these patents.
COPYRIGHT 1983, 1999 Inprise Corporation. All rights reserved. All Inprise and Borland brand and product names
are trademarks or registered trademarks of Inprise Corporation. Other brand and product names are trademarks or
registered trademarks of their respective holders.
Printed in the U.S.A.
HDE1350WW21002 3E2R899
9900010203-9 8 7 6 5 4 3 2 1
PDF
Contents
Chapter 1
Introduction
Reserved words . . . . . . . . . .
Directives. . . . . . . . . . . . . .
Numerals . . . . . . . . . . . . . .
Labels . . . . . . . . . . . . . . . .
Character strings . . . . . . . . .
Comments and compiler directives .
Expressions . . . . . . . . . . . . . .
Operators. . . . . . . . . . . . . .
Arithmetic operators . . . . .
Boolean operators . . . . . . .
Logical (bitwise) operators . .
String operators . . . . . . . .
Pointer operators. . . . . . . .
Set operators . . . . . . . . . .
Relational operators . . . . . .
Class operators . . . . . . . . .
The @ operator . . . . . . . . .
Operator precedence rules . .
Function calls . . . . . . . . . . .
Set constructors . . . . . . . . . .
Indexes . . . . . . . . . . . . . . .
Typecasts . . . . . . . . . . . . . .
Value typecasts . . . . . . . . .
Variable typecasts . . . . . . .
Declarations and statements . . . . .
Declarations . . . . . . . . . . . .
Statements . . . . . . . . . . . . .
Simple statements . . . . . . . . .
Assignment statements . . . .
Procedure and function calls .
Goto statements . . . . . . . .
Structured statements. . . . . . .
Compound statements . . . .
With statements . . . . . . . .
If statements . . . . . . . . . .
Case statements . . . . . . . .
Control loops . . . . . . . . . .
Repeat statements . . . . . . .
While statements. . . . . . . .
For statements . . . . . . . . .
Blocks and scope . . . . . . . . . . .
Blocks . . . . . . . . . . . . . . . .
Scope . . . . . . . . . . . . . . . .
Naming conflicts . . . . . . . .
1-1
.
.
.
.
.
.
.
.
.
.
1-1
1-1
1-2
1-2
1-2
Part I
Overview
2-1
Program organization . . . . . . . . . . .
Pascal source files . . . . . . . . . . .
Other files used to build applications
Compiler-generated files . . . . . . .
Example programs. . . . . . . . . . . . .
A simple console application . . . . .
A more complicated example . . . . .
A Windows application . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2-1
2-1
2-2
2-3
2-3
2-3
2-4
2-5
Chapter 3
3-1
3-1
3-2
3-2
3-2
3-3
3-3
3-4
3-4
3-4
3-5
3-5
3-5
3-6
3-7
Chapter 4
Syntactic elements
Fundamental syntactic elements .
Special symbols . . . . . . . . .
Identifiers . . . . . . . . . . . .
Qualified identifiers . . . .
4-1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4-1
4-2
4-2
4-2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 4-3
. 4-3
. 4-4
. 4-4
. 4-4
. 4-5
. 4-5
. 4-6
. 4-6
. 4-7
. 4-8
. 4-9
. 4-9
4-10
4-10
4-11
4-11
4-12
4-13
4-13
4-13
4-14
4-14
4-14
4-16
4-16
4-16
4-16
4-17
4-17
4-18
4-19
4-19
4-20
4-21
4-23
4-24
4-24
4-25
4-25
4-27
4-27
4-27
4-28
Chapter 5
Declaring types . . . . . . . .
Variables . . . . . . . . . . . .
Declaring variables . . . .
Absolute addresses . .
Dynamic variables . . .
Thread-local variables .
Declared constants . . . . . .
True constants . . . . . . .
Constant expressions .
Resource strings . . . .
Typed constants . . . . . .
Array constants . . . .
Record constants . . . .
Procedural constants .
Pointer constants. . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 5-1
. 5-2
. 5-2
. 5-3
. 5-4
. 5-5
. 5-6
. 5-7
. 5-8
. 5-9
. 5-11
. 5-11
. 5-12
. 5-12
. 5-12
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 5-14
. 5-15
. 5-16
. 5-16
. 5-17
. 5-17
. 5-20
. 5-20
. 5-21
. 5-23
. 5-24
. 5-24
. 5-26
. 5-26
. 5-26
. 5-27
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 5-28
. 5-29
. 5-30
. 5-32
. 5-32
. 5-33
. 5-33
. 5-33
. 5-34
. 5-34
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5-35
5-36
5-36
5-37
5-37
5-38
5-38
5-38
5-39
5-40
5-40
5-41
5-41
5-42
5-42
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 6-1
. 6-2
. 6-3
. 6-4
. 6-5
. 6-6
. 6-6
. 6-7
. 6-7
. 6-8
. 6-9
. 6-9
6-10
6-10
6-11
6-11
6-12
6-13
6-13
6-13
6-15
6-16
Chapter 6
. . . . 5-13
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
ii
6-1
. . 6-17
. . 6-17
. . 6-17
. . 6-18
Chapter 7
Re-raising exceptions . . . . . . . . .
Nested exceptions . . . . . . . . . . .
Try...finally statements . . . . . . . .
Standard exception classes and routines
7-1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7-1
7-2
7-3
7-3
7-3
7-4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7-28
7-29
7-30
7-30
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 8-1
. 8-3
. 8-4
. 8-4
. 8-5
. 8-5
. 8-5
. 8-6
. 8-6
. 8-6
. 8-7
. 8-7
Chapter 8
. . . 7-5
. . . 7-5
. . . 7-5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 7-6
. 7-7
. 7-8
. 7-8
. 7-8
. 7-9
. 7-9
. 7-9
. 7-10
. 7-11
. 7-12
. 7-12
. 7-14
. 7-15
. 7-15
. 7-15
. 7-16
. 7-16
. 7-18
. 7-19
. 7-20
. 7-20
. 7-22
. 7-22
. 7-22
. 7-23
. 7-23
. 7-24
. 7-24
. 7-25
. 7-25
. 7-25
. 7-26
8-1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Part II
Special topics
Chapter 9
iii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 9-1
. 9-1
. 9-2
. 9-3
. 9-4
. 9-4
. 9-5
. 9-5
. 9-6
. 9-6
. 9-7
. 9-7
. 9-8
. 9-8
. 9-8
. 9-9
. 9-9
. 9-9
. . 9-10
Chapter 10
Object interfaces
Set types . . . . . . .
Static array types . .
Dynamic array types
Record types . . . . .
File types . . . . . . .
Procedural types. . .
Class types . . . . . .
Class reference types
Variant types . . . . .
10-1
Interface types . . . . . . . . . . . . . . . .
IUnknown and inheritance . . . . . . .
Interface identification . . . . . . . . . .
Calling conventions . . . . . . . . . . .
Interface properties. . . . . . . . . . . .
Forward declarations . . . . . . . . . .
Implementing interfaces . . . . . . . . . .
Method resolution clauses. . . . . . . .
Changing inherited implementations .
Implementing interfaces by delegation
Delegating to an interface-type
property . . . . . . . . . . . . . . .
Delegating to a class-type property .
Interface references . . . . . . . . . . . . .
Interface assignment-compatibility. . .
Interface typecasts . . . . . . . . . . . .
Interface querying . . . . . . . . . .
Automation objects . . . . . . . . . . . . .
Dispatch interface types . . . . . . . . .
Dispatch interface methods . . . . .
Dispatch interface properties . . . .
Accessing Automation objects . . . . .
Automation object method-call
syntax. . . . . . . . . . . . . . . . .
Dual interfaces . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 10-1
. 10-2
. 10-2
. 10-3
. 10-3
. 10-3
. 10-4
. 10-5
. 10-5
. 10-6
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 10-6
. 10-7
. 10-8
. 10-9
. 10-9
. 10-9
10-10
10-10
10-11
10-11
10-11
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 11-6
. 11-7
. 11-7
. 11-7
. 11-8
. 11-9
. 11-9
.11-10
.11-10
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 13-7
. 13-8
. 13-8
13-10
13-10
13-12
13-13
13-14
13-16
12-1
12-1
12-1
12-3
12-3
12-3
12-4
12-4
Chapter 13
11-1
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Program control
Chapter 11
Delphis memory manager
Variables. . . . . . . . .
Internal data formats . . .
Integer types . . . . . .
Character types . . . . .
Boolean types . . . . . .
Enumerated types . . .
Real types . . . . . . . .
The Real48 type . . .
The Single type . . .
The Double type . .
The Extended type .
The Comp type . . .
The Currency type .
Pointer types . . . . . .
Short string types. . . .
Long string types . . . .
Wide string types . . . .
.
.
.
.
.
.
.
.
.
Chapter 12
. . 10-12
. . 10-12
Memory management
.
.
.
.
.
.
.
.
.
. 11-1
. 11-2
. 11-2
. 11-2
. 11-3
. 11-3
. 11-3
. 11-3
. 11-4
. 11-4
. 11-4
. 11-5
. 11-5
. 11-5
. 11-5
. 11-5
. 11-5
. 11-6
13-1
13-1
13-2
13-2
13-2
13-3
13-4
13-4
13-5
13-6
13-7
Appendix A
iv
A-1
I-1
Tables
4.1
4.2
4.3
4.4
4.5
4.6
4.7
4.8
4.9
4.10
4.11
5.1
5.2
5.3
5.4
5.5
5.6
5.7
5.8
6.1
Reserved words . . . . . . . . . . .
Directives . . . . . . . . . . . . . . .
Binary arithmetic operators . . . .
Unary arithmetic operators . . . . .
Boolean operators . . . . . . . . . .
Logical (bitwise) operators . . . . .
String operators . . . . . . . . . . .
Character-pointer operators . . . .
Set operators . . . . . . . . . . . . .
Relational operators . . . . . . . . .
Precedence of operators. . . . . . .
Generic integer types for 32-bit
implementations of Object Pascal .
Fundamental integer types . . . . .
Fundamental real types . . . . . . .
Generic real types . . . . . . . . . .
String types. . . . . . . . . . . . . .
Selected pointer types declared in
System and SysUtils . . . . . . . . .
Variant type conversion rules . . .
Types for integer constants . . . . .
Calling conventions . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 4-3
. 4-3
. 4-6
. 4-6
. 4-7
. 4-8
. 4-9
. 4-9
. 4-10
. 4-10
. 4-12
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 5-26
. 5-31
. 5-39
. 6-5
8.1
8.2
8.3
9.1
9.2
9.3
11.1
11.2
11.3
11.4
11.5
13.1
13.2
13.3
13.4
5-3
5-3
5-8
5-9
5-9
13.5
13.6
13.7
.
.
.
.
.
.
.
.
.
.
. 8-1
. 8-6
. 8-7
. 9-9
. 9-9
.
.
.
.
.
.
.
.
.
. 9-10
. 11-5
. 11-6
. 11-7
. 11-7
.11-10
. 13-7
. 13-9
13-10
. .13-11
. 13-14
. 13-14
. 13-15
vi
Chapter
Introduction
Chapter1
This manual is about the Object Pascal programming language as it is used in Delphi.
Introduction
1-1
Typographical conventions
Identifiersthat is, names of constants, variables, types, fields, properties,
procedures, functions, programs, units, libraries, and packagesappear in italics in
the text. Object Pascal 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 Delphis Code editor displays reserved words and directives, if you have
the Syntax Highlight option turned on.
Some program listings, like the example above, 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 function
declaration above could be represented as
function functionName(argumentList): returnType;
Syntax descriptions can also contain ellipsis marks (...) and subscripts:
function functionName(arg1, ..., argn): ReturnType;
1-2
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
Chapter
Overview
Chapter2
Program organization
Programs are usually divided into source-code modules called units. Each program
begins 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, Object Pascal
programs do not require makefiles, header files, or preprocessor include directives.
(Delphis 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.
2-1
Program organization
Unit source files contain most of the code in an application. Each Delphi application
has a single project file and several unit files; the project filewhich corresponds to
the main program file in traditional Pascalorganizes the unit files into an
application. Delphi automatically maintains a project file for each application.
If you are compiling a program from the command line, you can put all your source
code into unit (.PAS) files. But if you use the Delphi IDE to build your application,
you must have a project (.DPR) file.
Package source files are similar to project files, but they are used to construct special
dynamic-link libraries called packages. For more information about packages, see
Chapter 9, Dynamic-link libraries and packages.
2-2
Example programs
Compiler-generated files
The first time you build an application or a standard dynamic-link library, the
compiler produces a .DCU (Delphi compiled unit) file for each new unit used in your
project; all the .DCU files in your project are then linked to create a single .EXE
(executable) or .DLL file. The first time you build a package, the compiler produces a
.DCU file for each new unit contained in the package, and then creates both a .DCP
and a .BPL file. (For more information about dynamic-link libraries and packages, see
Chapter 9, Dynamic-link libraries and packages.) 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 rebuild a project, individual units are not recompiled unless their source
(.PAS) files have changed since the last compilation, or their .DCU files cannot be
found, or you explicitly tell the compiler to reprocess them. In fact, it is not necessary
for a units source file to be present at all, as long as the compiler can find the .DCU
file.
Example programs
The examples that follow illustrate basic features of Object Pascal and Delphi
programming. The first two examples are not Delphi applications, but you can
compile them from the command line.
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. (Object Pascal
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.)
Overview
2-3
Example programs
If you have Delphi installed and your Path includes the Delphi\Bin directory (where
DCC32.EXE and DCC32.CFG reside), you can type this program into a file called
GREETING.PAS or GREETING.DPR and compile it by entering
DCC32 GREETING
on the command line. The resulting executable (GREETING.EXE) prints the message
Hello world!
Aside from its simplicity, this example differs in several important ways from
programs that you are likely to write with Delphi. First, it is a console application.
Delphi is typically used to write Windows applications with graphical interfaces;
hence, in a Delphi application you would not ordinarily call Writeln. Moreover, the
entire example program (save for Writeln) is in a single file. In a Delphi application,
the program headingthe first line of the examplewould be placed in a separate
project file that would not contain any of the actual application logic, other than a
few calls to methods defined in unit files.
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? Its
defined in Unit1. Heres the source code for Unit1, which you can save in a file called
UNIT1.PAS:
unit Unit1;
interface
procedure PrintMessage(msg: string);
implementation
procedure PrintMessage(msg: string);
begin
Writeln(msg);
end;
end.
2-4
Example programs
A Windows application
Our next example is a Windows application built with Delphis Visual Component
Library (VCL). This program uses Delphi-generated form and resource files, so you
wont be able to compile it from the source code alone. But it illustrates important
features of Object Pascal. 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
Forms,
Unit1 { the unit for Form1 },
Unit2 { the unit for Form2 };
{$R *.RES} { this directive links the project's resource file }
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: Forms, which is part of
the VCL; Unit1, which is associated with the applications 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 Delphi 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
Overview
2-5
Example programs
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
uses { these units are part of Delphi's Visual Component Library (VCL) }
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;
var
Form1: TForm1;
implementation
uses Unit2; { this is where Form2 is defined }
{$R *.DFM} { this directive links Unit1's form file }
procedure TForm1.Button1Click(Sender: TObject);
begin
Form1.Hide;
Form2.Show;
end;
end.
Unit1 creates a class named TForm1 (derived from the VCLs TForm) and an instance
of this class, Form1. TForm1 includes a buttonButton1, an instance of TButtonand
a procedure named TForm1.Button1Click that is called at runtime whenever the user
presses Button1. TForm1.Button1Click does two things: it hides Form1 (the call to
Form1.Hide) and it displays Form2 (the call to Form2.Show). Form2 is defined in Unit2:
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm2 = class(TForm)
Label1: TLabel;
CancelButton: TButton;
2-6
Example programs
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 cant see this from the source code, but Label1 displays a
caption that reads Hello world! The caption is defined in Form2s form file,
UNIT2.DFM.
Unit2 defines two procedures. TForm2.CancelButtonClick is called at runtime
whenever the user presses CancelButton; it closes Form2. TForm2.FormClose is called at
runtime whenever Form2 closes; it reopens Form1. These procedures (along with
Unit1s TForm1.Button1Click) are known as event handlers because they respond to
events that occur while the program is running. Event handlers are assigned to
specific events by the form (.DFM) files for Form1 and Form2.
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 projects main form.) When the user presses the button on Form1, Form1
disappears and is replaced by Form2, which displays the Hello world! greeting.
When the user closes Form2 (by pressing CancelButton or the Close button on the title
bar), Form1 reappears.
Overview
2-7
2-8
Chapter
Chapter3
A program is constructed from source-code modules called units. Each unit is stored
in its own file and compiled separately; compiled units (.DCU files) are linked to
create an application. Units allow you to
divide large programs into modules that can be edited separately.
create libraries that you can share among programs.
distribute libraries to other developers without making the source code available.
In traditional Pascal programming, all source code, including the main program, is
stored in .PAS files. Delphi uses a project (.DPR) file to store the main program,
while most other source code resides in unit (.PAS) files. Each applicationor
projectconsists of a single project file and one or more unit files. (Strictly speaking,
you neednt explicitly use any units in a project, but all programs automatically use
the System unit.) To build a project, the compiler needs either a source file or a
previously compiled DCU for each unit.
3-1
The example below shows the project file for a program called Editor.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
program Editor;
uses
Forms,
REAbout in 'REABOUT.PAS' {AboutBox},
REMain in 'REMain.pas' {MainForm};
{$R *.RES}
begin
Application.Title := 'Text Editor';
Application.CreateForm(TMainForm, MainForm);
Application.Run;
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 projects 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
programs logic resides in its unit files. Project files are generated and maintained by
Delphi, 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 Delphi programs, the block consists of a compound
statementbracketed between the reserved words begin and endwhose
3-2
component statements are simply method calls to the projects Application object.
(Every Delphi project has 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.
The unit must conclude with the word end followed by a period.
would occur in a source file called MAINFORM.PAS, and the file containing the
compiled unit would be MAINFORM.DCU.
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.
3-3
3-4
3-5
In the uses clause of a program or library, 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. Examples:
uses Windows, Messages, SysUtils, Strings in 'C:\Classes\Strings.pas', Classes;
uses
Forms,
Main,
Extra in '..\EXTRA\EXTRA.PAS';
Include in ... after a unit name when you need to specify the units source file. Since
the Delphi IDE expects unit names to match the names of the source files in which
they reside, there is usually no reason to do this. Using in is necessary only when the
location of the source file is unclear, for example when
You have used a source file that is in a different directory from the project file, and
that directory is not in the compilers search path or Delphis Library search path.
Different directories in the compilers search path have identically named units.
You are compiling a console application from the command line, and you have
named a unit with an identifier that doesnt match the name of its source file.
Delphi also relies on the in ... construction to determine which units are part of a
project. Only units that appear in a project (.DPR) files uses clause followed by in
and a file name are considered to be part of the project; other units in the uses clause
are used by the project without belonging to it. This distinction has no effect on
compilation, but it affects IDE tools like the Project Manager and Project Browser.
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 compilers search path, Delphis Library search
path, or the same directory as the unit that uses it. Moreover, unit names must match
the names of their source files.
3-6
unit Unit2;
interface
uses Unit1;
const b = c;
unit Unit1;
interface
const c = 1;
In this example, Prog depends directly on Unit2, which depends directly on Unit1.
Hence Prog is indirectly dependent on Unit1. Because Unit1 does not appear in Progs
uses clause, identifiers declared in Unit1 are not available to Prog.
To compile a client module, the compiler needs to locate all units that the client
depends on, directly or indirectly. Unless the source code for these units has
changed, however, the compiler needs only their .DCU files, not their source (.PAS)
files.
When changes are made in the interface section of a unit, other units that depend on
it must be recompiled. But when changes are made only in the implementation or
other sections of a unit, dependent units dont have to be recompiled. The compiler
tracks these dependencies automatically and recompiles units only when necessary.
unit Unit2;
interface
uses Unit1;
3-7
However, the two units can legally reference each other if one of the references is
moved to the implementation section:
unit Unit1;
interface
uses Unit2;
unit Unit2;
interface
implementation
uses Unit1;
To reduce the chance of circular references, its a good idea to list units in the
implementation uses clause whenever possible. Only when identifiers from another
unit are used in the interface section is it necessary to list that unit in the interface
uses clause.
3-8
Chapter
Syntactic elements
Chapter4
Object Pascal 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 31including 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.
is perfectly legal. Convention and readability, however, dictate that we write this as
Size := 20;
Price := 10;
Tokens are categorized as special symbols, identifiers, reserved words, directives, numerals,
labels, and character strings. A separator can be part of a token only if the token is a
character string. Adjacent identifiers, reserved words, numerals, and labels must
have one or more separators between them.
Syntactic elements
4-1
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-parenthesisplusasterisk and asterisk-plusright-parenthesis(* *)are equivalent to the left and right brace{ }.
Notice that !, " (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 Object Pascal is case-insensitive, an identifier like CalculateValue could be
written in any of these ways:
CalculateValue
calculateValue
calculatevalue
CALCULATEVALUE
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
4-2
Reserved words
The following reserved words cannot be redefined or used as identifiers.
Table 4.1
Reserved words
and
downto
in
or
string
array
else
inherited
out
then
as
end
initialization
packed
threadvar
asm
except
inline
procedure
to
begin
exports
interface
program
try
case
file
is
property
type
class
finalization
label
raise
unit
const
finally
library
record
until
constructor
for
mod
repeat
uses
destructor
function
nil
resourcestring
var
dispinterface
goto
not
set
while
div
if
object
shl
with
do
implementation
of
shr
xor
In addition to the words in Table 4.1, 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.
Directives
Directives have special meanings in Object Pascal, but, unlike reserved words,
appear only in contexts where user-defined identifiers cannot occur. Hence
although it is inadvisable to do soyou can define an identifier that looks exactly
like a directive.
Table 4.2
Directives
absolute
dynamic
name
protected
abstract
export
near
public
resident
safecall
assembler
external
nodefault
published
stdcall
automated
far
overload
read
stored
cdecl
forward
override
readonly
virtual
contains
implements
package
register
write
default
index
pascal
reintroduce
writeonly
dispid
message
private
requires
Syntactic elements
4-3
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, 7E2 means 7 102, and 12.25e+6 and 12.25e6 both mean 12.25 106.
The dollar-sign prefix indicates a hexadecimal numeralfor example, $8F.
Hexadecimals must be within the range $00000000 to $FFFFFFFF. The sign of a
hexadecimal is determined by the leftmost (most significant) bit of its binary
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-38.
Labels
A label is a sequence of no more than four digitsthat is, a numeral between 0 and
9999. Leading zeros are not significant. Identifiers can also function as labels.
Labels are used in goto statements. For more information about goto statements and
labels, see Goto statements on page 4-18.
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'
'You''ll see'
''''
''
' '
{
{
{
{
{
BORLAND }
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
4-4
You can combine quoted strings with control strings to form larger character strings.
For example, you could use
'Line 1'#13#10'Line 2'
A comment that contains a dollar sign ($) immediately after the opening { or (* is a
compiler directive. For example,
{$WARNINGS OFF}
Expressions
An expression is a construction that returns a value. For example,
X
@X
15
InterestRate
Calc(X,Y)
X * Y
Z / (1 - Z)
X = 1.5
C in Range1
not Done
['a','b','c']
Char(48)
{
{
{
{
{
{
{
{
{
{
{
{
variable }
address of a variable }
integer constant }
variable }
function call }
product of X and Y }
quotient of Z and (1 - Z) }
Boolean }
Boolean }
negation of a Boolean }
set }
value typecast }
Syntactic elements
4-5
Expressions
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 Object Pascal
language. For example, the expression (X + Y) is built from the variables X and Y
called operandswith 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 unary or
binary. 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-29.
The sections that follow assume some familiarity with Object Pascal 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-12.
Arithmetic operators
Arithmetic operators, which take real or integer operands, include +, , *, /, div, and
mod.
Table 4.3
Operator
Operation
Operand types
Result type
Example
addition
integer, real
integer, real
X + Y
subtraction
integer, real
integer, real
Result - 1
multiplication
integer, real
integer, real
P * InterestRate
real division
integer, real
real
X / 2
div
integer division
integer
integer
mod
remainder
integer
integer
Y mod 6
Example
Table 4.4
4-6
Operator
Operation
Operand type
Result type
sign identity
integer, real
integer, real
+7
sign negation
integer, real
integer, real
-X
Expressions
Boolean operators
The Boolean operators not, and, or, and xor take operands of any Boolean type and
return a value of type Boolean.
Table 4.5
Boolean operators
Operator
Operation
Operand types
Result type
Example
not
negation
Boolean
Boolean
not (C in MySet)
and
conjunction
Boolean
Boolean
or
disjunction
Boolean
Boolean
A or B
xor
exclusive disjunction
Boolean
Boolean
A xor B
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.
Syntactic elements
4-7
Expressions
Short-circuit evaluation also allows the use of constructions that might otherwise
result in illegal runtime operations. For example, the following code iterates through
the string S, up to the first comma.
while (I <= Length(S)) and (S[I] <> ',') do
begin
Inc(I);
end;
In a case where S has no commas, the last iteration increments I to a value which is
greater than the length of S. When the while condition is next tested, complete
evaluation results in an attempt to read S[I], which could cause a runtime error.
Under short-circuit evaluation, in contrast, the second part of the while condition
(S[I] <> ',')is not evaluated after the first part fails.
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.
Operand types
Result type
Examples
not
bitwise negation
integer
integer
not X
and
bitwise and
integer
integer
X and Y
or
bitwise or
integer
integer
X or Y
xor
bitwise xor
integer
integer
X xor Y
shl
integer
integer
X shl 2
shr
integer
integer
Y shl I
4-8
Expressions
String operators
The relational operators =, <>, <, >, <=, and >= all take string operands (see
Relational operators on page 4-10). The + operator concatenates two strings.
Table 4.7
String operators
Operator
Operation
Operand types
Result type
Example
concatenation
string
S + '. '
Pointer operators
The relational operators <, >, <=, and >= can take operands of type PChar (see
Relational operators on page 4-10). The following operators also take pointers as
operands. For more information about pointers, see Pointers and pointer types on
page 5-24.
Table 4.8
Character-pointer operators
Operator Operation
Operand types
Result type
Example
pointer addition
character pointer
P + I
pointer subtraction
P - Q
pointer dereference
pointer
P^
equality
pointer
Boolean
P = Q
<>
inequality
pointer
Boolean
P <> Q
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.
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.
Syntactic elements
4-9
Expressions
Set operators
The following operators take sets as operands.
Table 4.9
Operator
Set operators
Operation
Operand types
Result type
Example
union
set
set
Set1 + Set2
difference
set
set
S - T
intersection
set
set
S * T
<=
subset
set
Boolean
Q <= MySet
>=
superset
set
Boolean
S1 >= S2
equality
set
Boolean
S2 = MySet
<>
inequality
set
Boolean
MySet <> S1
in
membership
ordinal, set
Boolean
A in Set1
Relational operators
Relational operators are used to compare two operands. The operators =, <>, <=, and
>= also apply to sets (see Set operators on page 4-10); = and <> also apply to
pointers (see Pointer operators on page 4-9).
Table 4.10
4-10
Relational operators
Result
type
Example
Boolean
I = Max
inequality
Boolean
X <> Y
<
less-than
Boolean
X < Y
>
greater-than
Boolean
Len > 0
<=
less-than-orequal-to
Boolean
Cnt <= I
>=
greater-thanor-equal-to
Boolean
I >= 1
Operator
Operation
Operand types
equality
<>
Expressions
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 ordering of the extended ASCII character
set. 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.
The operators <, >, <=, and >= apply to PChar 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. 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-10.
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-24. 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-28.) 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.
If F is a routine (a function or procedure), @F returns Fs entry point. The type of
@F is always Pointer.
When @ is applied to a method defined in a class, the method identifier must be
qualified with the class name. For example,
@TMyClass.DoSomething
Syntactic elements
4-11
Expressions
Precedence of operators
Operators
Precedence
@, not
first (highest)
second
+, , or, xor
third
fourth (lowest)
multiplies Y times Z, then adds X to the result; * is performed first, because is has a
higher precedence than +. But
X - Y + Z
first subtracts Y from X, then adds Z to the result; and + have the same precedence,
so the operation on the left is performed first.
You can use parentheses to override these precedence rules. An expression within
parentheses is evaluated first, then treated as a single operand. For example,
(X + Y) * Z
Without parentheses, however, the compiler follows operator precedence rules and
reads it as
(X = (Y or X)) = Z
Here the parentheses are unnecessary (to the compiler), but they spare both
programmer and reader from having to think about operator precedence.
4-12
Expressions
Function calls
Because functions return a value, function calls are expressions. For example, if youve
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 ]
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-9. For information about arrays and array properties, see
Arrays on page 5-16 and Array properties on page 7-18.
Syntactic elements
4-13
Expressions
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 expressions
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
types or both be pointer types. Examples of value typecasts include
Integer('A')
Char(48)
Boolean(0)
Color(2)
Longint(@Buffer)
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)
Shortint(MyChar) := 122;
4-14
Expressions
You can cast variables to a procedural type. For example, given the declarations
type
var
F:
P:
N:
{
{
{
{
{
{
in P
in F
P to
F to
to F }
to P }
F }
P }
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-24. For information about casting class and interface types, see The as
operator on page 7-24 and Interface typecasts on page 9.
Syntactic elements
4-15
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.
Statements
Statements define algorithmic actions within a program. Simple statementslike
assignments and procedure callscan 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 doesnt contain any other statements. Simple statements include
assignments, calls to procedures and functions, and goto jumps.
4-16
Assignment statements
An assignment statement has the form
variable := expression
where variable is any variable referenceincluding a variable, variable typecast,
dereferenced pointer, or component of a structured variableand 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;
Function calls, like calls to procedures, can be treated as statements in their own right:
MyFunction(X);
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.
Syntactic elements
4-17
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-27.) 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;
4-18
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 statementthat is, an if or case statementexecutes at most one of
its constituents, depending on specified criteria.
Loop statementsincluding repeat, while, and for loopsexecute a sequence of
constituent statements repeatedly.
A special group of statementsincluding raise, try...except, and try...finally
constructionscreate and handle exceptions. For information about exception
generation and handling, see Exceptions on page 7-25.
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 Object Pascal 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;
Syntactic elements
4-19
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 a variable reference denoting an object or record, and statement is any
simple or structured statement. Within statement, you can refer to fields, properties,
and methods of obj using their identifiers alonewithout qualifiers.
For example, given the declarations
type TDate = record
Day: Integer;
Month: Integer;
Year: Integer;
end;
var OrderDate: TDate;
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;
4-20
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
objn1; 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;
Syntactic elements
4-21
The then and else clauses contain one statement each, but it can be a structured
statement. For example,
if J <> 0 then
begin
Result := I/J;
Count := Count + 1;
end
else if Count = Last then
Done := True
else
Exit;
Notice that there is never a semicolon between the then clause and the word else.
You can place a semicolon after an entire if statement to separate it from the next
statement in its block, but the then and else clauses require nothing more than a
space or carriage return between them. Placing a semicolon immediately before else
(in an if statement) is a common programming error.
A special difficulty arises in connection with nested if statements. The problem arises
because some if statements have else clauses while others do not, but the syntax for
the two kinds of statement is otherwise the same. In a series of nested conditionals
where there are fewer else clauses than if statements, it may not seem clear which
else clauses are bound to which ifs. Consider a statement of the form
if expression1 then if expression2 then statement1 else statement2;
There would appear to be two ways to parse this:
if expression1 then [ if expression2 then statement1 else statement2 ];
if expression1 then [ if expression2 then statement1 ] else statement2;
The compiler always parses in the first way. That is, in real code, the statement
if ... { expression1 } then
if ... { expression2 } then
... { statement1 }
else
... { statement2 } ;
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
4-22
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 provides a readable alternative to complex 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-39.)
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
statement;
end
When a case statement is executed, at most one of its constituent statements 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 statement in the else clause (if there is one) is executed.
Syntactic elements
4-23
Control loops
Loops allow you to execute a sequence of statements repeatedly, using a control
condition or variable to determine when the execution stops. Object Pascal 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
4-24
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.
Examples of while statements include
while Data[I] <> X do I := I + 1;
while I > 0 do
begin
if Odd(I) then Z := Z * X;
I := I div 2;
X := Sqr(X);
end;
while not Eof(InputFile) do
begin
Readln(InputFile, Line);
Process(Line);
end;
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
Syntactic elements
4-25
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, 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 re-evaluates 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);
4-26
Blocks
A block consists of a series of declarations followed by a compound statement. All
declarations must occur together at the beginning of the block. So the form of a block is
declarations
begin
statements
end
The declarations section can include, in any order, declarations for variables, constants
(including resource strings), types, procedures, functions, and labels. In a program
block, the declarations section can also include one or more exports clauses (see
Chapter 9, Dynamic-link libraries and packages).
For example, in a function declaration like
function UpperCase(const S: string): string;
var
Ch: Char;
L: Integer;
Source, Dest: PChar;
begin
end;
the first line of the declaration is the function heading and all of the succeeding lines
make up the block. Ch, L, Source, and Dest are local variables; their declarations apply
only to the UpperCase function block and overridein this block onlyany
declarations of the same identifiers that may occur in the program block or in the
interface or implementation section of a unit.
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 scopeespecially
identifiers declared in functions and proceduresare sometimes called local, while
identifiers with wider scope are called global.
Syntactic elements
4-27
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 overrides 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 scopethat is, in the unit where the reference itself occurs, or, if that unit
doesnt declare the identifier, in the last unit in the uses clause that does declare the
identifier.
The System unit is used automatically by every program or unit. The declarations in
System, along with the predefined types, routines, and constants that the compiler
understands automatically, always have the outermost scope.
You can override these rules of scope and by-pass an inner declaration by using a
qualified identifier (see Qualified identifiers on page 4-2) or a with statement (see
With statements on page 4-20).
4-28
Chapter
Chapter5
A type is essentially a name for a kind of data. When you declare a variable you must
specify its type, which determines the set of values the variable can hold and the
operations that can be performed on it. Every expression returns data of a particular
type, as does every function. Most functions and procedures require parameters of
specific types.
Object Pascal is a strongly typed language, which means that it distinguishes a
variety of data types and does not always allow you to substitute one type for
another. This is usually beneficial because it lets the compiler treat data intelligently
and validate your code more thoroughly, preventing hard-to-diagnose runtime
errors. When you need greater flexibility, however, there are mechanisms to
circumvent strong typing. These include typecasting (see Typecasts on page 4-14),
pointers (see Pointers and pointer types on page 5-24), variants (see Variant types
on page 5-29), variant parts in records (see Variant parts in records on page 5-21),
and absolute addressing of variables (see Absolute addresses on page 5-37).
About types
There are several ways to categorize Object Pascal 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 Delphis 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 Object Pascal, 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. Its a good idea to use generic types when possible, since
they provide optimal performance and portability. However, changes in storage
Data types, variables, and constants
5-1
Simple types
format from one implementation of a generic type to the next could cause
compatibility problemsfor example, if you are streaming data to a file.
Types can be classified as simple, string, structured, pointer, procedural, or variant. In
addition, type identifiers themselves can be regarded as belonging to a special
type because they can be passed as parameters to certain functions (such as
High, Low, and SizeOf).
The outline below shows the taxonomy of Object Pascal data types.
simple
ordinal
integer
character
Boolean
enumerated
subrange
real
string
structured
set
array
record
file
class
class reference
interface
pointer
procedural
variant
type identifier
The standard function SizeOf operates on all variables and type identifiers. It returns
an integer representing the amount of memory (in bytes) used to store data of the
specified type. For example, SizeOf(Longint) returns 4, since a Longint variable uses
four bytes of memory.
Type declarations are illustrated in the sections that follow. For general information
about type declarations, see Declaring types on page 5-35.
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,
5-2
Simple types
each value has an ordinality, which determines the ordering of the type. For integer
types, the ordinality of a value is the value itself; for all other ordinal types except
subranges, the first value has ordinality 0, the next value has ordinality 1, and so
forth. If a value has ordinality n, its predecessor has ordinality n1 and its successor
has ordinality n+1.
Several predefined functions operate on ordinal values and type identifiers. The most
important of them are summarized below.
Function
Parameter
Return value
Remarks
Ord
ordinal expression
ordinality of expressions
value
Pred
ordinal expression
predecessor of expressions
value
Succ
ordinal expression
successor of expressions
value
High
Low
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 Object Pascal compiler.
Table 5.1
Type
Range
Format
Integer
2147483648..2147483647
signed 32-bit
Cardinal
0..4294967295
unsigned 32-bit
Fundamental integer types include Shortint, Smallint, Longint, Int64, Byte, Word, and
Longword.
Table 5.2
Type
Range
Format
Shortint
128..127
signed 8-bit
Smallint
32768..32767
signed 16-bit
Longint
2147483648..2147483647
signed 32-bit
5-3
Simple types
Table 5.2
Type
Range
63
63
Format
Int64
2 ..2 1
Byte
0..255
unsigned 8-bit
Word
0..65535
unsigned 16-bit
Longword
0..4294967295
unsigned 32-bit
signed 64-bit
I :=
J :=
Integer;
Int64;
High(Integer);
I + 1;
J := Int64(I) + 1;
Most standard routines that take integer arguments truncate Int64 values to 32 bits.
However, the High, Low, Succ, Pred, Inc, Dec, IntToStr, and IntToHex routines fully
support Int64 arguments. Also, the Round, Trunc, StrToInt64, and StrToInt64Def
functions return Int64 values. A few routinesincluding Ordcannot take Int64
values at all.
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;
Character types
The fundamental character types are AnsiChar and WideChar. AnsiChar values are
byte-sized (8-bit) characters ordered according to the extended ANSI character set.
WideChar values are word-sized (16-bit) characters ordered according to the Unicode
character set. The first 256 Unicode characters correspond to the ANSI characters.
The generic character type is Char, which is equivalent to AnsiChar. Because the
implementation of Char is subject to change, its a good idea to use the standard
5-4
Simple types
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;
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 different
languages and the Windows environment.
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.
Boolean
Ord(False) = 0
Ord(False) = 0
Ord(True) = 1
Ord(True) <> 0
Succ(False) = True
Succ(False) = True
Pred(True) = False
Pred(False) = True
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.
5-5
Simple types
The remarks above refer to the ordinality of Boolean valuesnot to the values
themselves. In Object Pascal, Boolean expressions cannot be equated with integers or
reals. Hence, if X is an integer variable, the statement
if X 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, and their ordinality
follows the sequence in which the identifiers are listed.
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.
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 Delphis VCL that descend from it. So if youre writing a Delphi application
and you create an event handler like
procedure TForm1.DBGrid1Enter(Sender: TObject);
var Thing: TSound;
begin
Thing := Click;
end;
youll get a compilation error; the compiler interprets Click within the scope of the
procedure as a reference to TForms Click method. You can work around this by
qualifying the identifier; thus, if TSound is declared in MyUnit, you would use
Thing := MyUnit.Click;
5-6
Simple types
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 cant declare another variable within the
same scope using these constant identifiers. Thus
var Card1: (Club, Diamond, Heart, Spade);
var Card2: (Club, Diamond, Heart, Spade);
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);
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 Low..High 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.
5-7
Simple types
The ordinality of each value in a subrange is preserved from the base type. (In the
first example above, 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);
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.
Table 5.3
5-8
Type
Range
Real48
Significant digits
45
38
1112
Size in bytes
6
Single
1.5 x 10
78
Double
1516
Extended
1920
10
Comp
263+1 .. 263 1
1920
Currency
922337203685477.5808.. 922337203685477.5807
1920
.. 3.4 x 10
String types
Note
Type
Range
Real
Significant digits
1516
Size in bytes
8
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, 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 CPU family, 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 CPU 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. Object Pascal supports the following
predefined string types.
Table 5.5
String types
Type
Maximum length
Memory required
Used for
ShortString
255 characters
2 to 256 bytes
backward compatibility
AnsiString
~231 characters
4 bytes to 2GB
WideString
~230 characters
4 bytes to 2GB
Unicode characters;
COM servers and interfaces
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
5-9
String types
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-14).
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 characteror, strictly speaking,
the ith bytein S. For a ShortString or AnsiString, S[i] is of type AnsiChar; for a
WideString, S[i] is of type WideChar. The statement MyString[2] := 'A'; assigns the
value A to the second character of MyString. The following code uses the standard
UpCase function to convert MyString to uppercase.
var I: Integer;
begin
I := Length(MyString);
while I > 0 do
begin
MyString[I] := UpCase(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 constantor any other expression that returns a
stringto a variable. The length of the string changes dynamically when the
assignment is made. Examples:
MyString
MyString
MyString
MyString
MyString
:=
:=
:=
:=
:=
'Hello world!';
'Hello ' + 'world';
MyString + '!';
' ';
{ space }
'';
{ empty string }
For more information, see Character strings on page 4-4 and String operators on
page 4-9.
5-10
String types
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 uses 8-bit
ANSI characters and is maintained for backward compatibility only.
Object Pascal supports short-string typesin effect, subtypes of ShortStringwhose
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. It uses 8-bit ANSI characters.
A long-string variable is a pointer occupying four bytes of memory. When the
variable is emptythat is, when it contains a zero-length stringthe pointer is nil
and the string uses no additional storage. When the variable is nonempty, it points to
a dynamically allocated block of memory that contains the string value, 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 variables 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 ifbut only ifits reference
count is greater than one. This is called copy-on-write semantics.
5-11
String types
WideString
The WideString type represents a dynamically allocated string of 16-bit Unicode
characters. In most respects it is similar to AnsiString, but it is less efficient because it
does not implement reference-counting and copy-on-write semantics.
WideString is compatible with the COM BSTR type. Delphi has COM support
features that convert AnsiString values to WideString, but if you make calls to the
COM API, you may need to explicitly cast or convert your strings to WideString.
5-12
String types
SysUtils unit (see Chapter 8, Standard routines and I/O) to handle null-terminated
strings when you need to share data with systems that use them.
For example, the following type declarations could be used to store null-terminated
strings.
type
TIdentifier = array[0..15] of Char;
TFileName = array[0..259] of Char;
TMemoText = array[0..1023] of WideChar;
You can assign a string constant to a statically allocated zero-based character array.
(Dynamic arrays wont work for this purpose.) If you initialize an array constant with
a string that is shorter than the declared length of the array, the remaining characters
are set to #0. For more information about arrays, see Arrays on page 5-16.
P := 'Hello world!';
P := @TempString;
You can also pass string constants to any function that takes value or const
parameters of type PChar or PWideCharfor example StrUpper('Hello world!'). As
with assignments to a PChar, the compiler generates a null-terminated copy of the
string and gives the function a pointer to that copy. Finally, you can initialize PChar
or PWideChar constants with string literals, alone or in a structured type. Examples:
const
Message: PChar = 'Program terminated';
Prompt: PChar = 'Enter values: ';
Digits: array[0..9] of PChar = (
'Zero', 'One', 'Two', 'Three', 'Four',
'Five', 'Six', 'Seven', 'Eight', 'Nine');
Zero-based character arrays are compatible with PChar and PWideChar. When you
use a character array in place of a pointer value, the compiler converts the array to a
pointer constant whose value corresponds to the address of the first element of the
array. For example,
5-13
String types
var
MyArray: array[0..32] of Char;
MyPointer: PChar;
begin
MyArray := 'Hello';
MyPointer := MyArray;
SomeProcedure(MyArray);
SomeProcedure(MyPointer);
end;
5-14
Structured types
You can also cast a long string as a null-terminated string. The following rules apply.
If S is a long-string expression, PChar(S) casts S as a null-terminated string; it
returns a pointer to the first character in S. For example, if Str1 and Str2 are long
strings, you could call the Win32 API MessageBox function like this:
MessageBox(0, PChar(Str1), PChar(Str2), MB_OK);
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.
5-15
Structured types
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;
You can also use the set of ... construction directly in variable declarations:
var MySet: set of 'a'..'z';
MySet := ['a','b','c'];
Every set type can hold the empty set, denoted by []. For more information about
sets, see Set constructors on page 4-13 and Set operators on page 4-10.
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.
5-16
Structured types
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 dont 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;
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 arrays first index type. The standard
function Length returns the number of elements in the arrays first dimension.
A one-dimensional, packed, static array of Char values is called a packed string. Packedstring 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-33.
An array type of the form array[0..x] of Char is called a zero-based character array. Zerobased character arrays are used to store null-terminated strings and are compatible with
PChar values. See Working with null-terminated strings on page 5-12.
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
5-17
Structured types
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 declaration above,
SetLength(MyFlexibleArray, 20);
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.
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;
5-18
Structured types
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 arrays highest index (that is, Length1), and Low returns 0. In the case of a
zero-length array, High returns 1 (with the anomalous consequence that High < Low).
Note
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-13.
allocates ten rows for Ints but no columns. Later, you can allocate the columns one at
a time (giving them different lengths); for example
SetLength(Ints[2], 5);
makes the third column of Ints five integers long. At this point (even if the other
columns havent been allocated) you can assign values to the third columnfor
example, Ints[2,4] := 6.
The following example uses dynamic arrays (and the IntToStr function declared in
the SysUtils unit) to create a triangular matrix of strings.
var
A : array of array of string;
I, J : Integer;
begin
SetLength(A, 10);
for I := Low(A) to High(A) do
begin
SetLength(A[I], I);
for J := Low(A[I]) to High(A[I]) do
A[I,J] := IntToStr(I) + ',' + IntToStr(J) + ' ';
end;
end;
5-19
Structured types
Int1 := Int2;
or
type IntArray = array[1..10] of Integer;
var
Int1: IntArray;
Int2: IntArray;
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;
5-20
Structured types
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
records name:
Record1.Year := 1904;
Record1.Month := Jun;
Record1.Day := 16;
Because the scope of a field designator is limited to the record in which it occurs, you
dont have to worry about naming conflicts between field designators and other
variables.
Instead of defining record types, you can use the record ... construction directly in
variable declarations:
var S: record
Name: string;
Age: Integer;
end;
However, a declaration like this largely defeats the purpose of records, which is to
avoid repetitive coding of similar groups of variables. Moreover, separately declared
records of this kind will not be assignment-compatible, even if their structures are
identical.
fieldListn: typen;
case tag: ordinalType of
constantList1: (variant1);
constantListn: (variantn);
end;
5-21
Structured types
The first part of the declarationup to the reserved word caseis the same as that of
a standard record type. The remainder of the declarationfrom case to the optional
final semicolonis called the variant part. In the variant part,
tag is optional and can be any valid identifier. If you omit tag, omit the colon (:)
after it as well.
ordinalType denotes an ordinal type.
Each constantList is a constant denoting a value of type ordinalType, or a
comma-delimited list of such constants. No value can be represented more than
once in the combined constantLists.
Each variant is a comma-delimited list of declarations resembling the fieldList: type
constructions in the main part of the record type. That is, a variant has the form
fieldList1: type1;
fieldListn: typen;
where each fieldList is a valid identifier or comma-delimited list of identifiers, each
type denotes a type, and the final semicolon is optional. The types must not be long
strings, dynamic arrays, variants (that is, Variant types), or interfaces, nor can they
be structured types that contain long strings, dynamic arrays, variants, or
interfaces; but they can be pointers to these types.
Records with variant parts are complicated syntactically but deceptively simple
semantically. The variant part of a record contains several variants which share the
same space in memory. You can read or write to any field of any variant at any time;
but if you write to a field in one variant and then to a field in another variant, you may
be overwriting your own data. The tag, if there is one, functions as an extra field (of
type ordinalType) in the non-variant part of the record.
Variant parts have two purposes. First, suppose you want to create a record type that
has fields for different kinds of data, but you know that you will never need to use all
of the fields in a single record instance. For example,
type
TEmployee = record
FirstName, LastName: string[40];
BirthDate: TDate;
case Salaried: Boolean of
True: (AnnualSalary: Currency);
False: (HourlyWage: Currency);
end;
The idea here is that every employee has either a salary or an hourly wage, but not
both. So when you create an instance of TEmployee, there is no reason to allocate
enough memory for both fields. In this case, the only difference between the variants
is in the field names, but the fields could just as easily have been of different types.
Consider some more complicated examples:
5-22
Structured types
type
TPerson = record
FirstName, LastName: string[40];
BirthDate: TDate;
case Citizen: Boolean of
True: (Birthplace: string[40]);
False: (Country: string[20];
EntryPort: string[20];
EntryDate, ExitDate: TDate);
end;
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 above) 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 is an ordered set 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
typeswhether implicit or explicitare 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.
5-23
For example,
type
PhoneEntry = record
FirstName, LastName: string[20];
PhoneNumber: string[15];
Listed: Boolean;
end;
PhoneList = file of PhoneEntry;
Overview of pointers
To see how pointers work, look at the following example.
1
2
3
4
5
6
7
8
var
X, Y: Integer;
P: ^Integer;
begin
X := 17;
P := @X;
Y := P^;
end;
5-24
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-11 and Procedural types in statements and expressions on page 5-28.
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 anothersomething 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 Object Pascal, since pointers often operate behind the
scenes in code where they dont 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 variables. Moreover, some advanced
programming techniques require the use of pointers.
Finally, pointers are sometimes the only way to circumvent Object Pascals 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^.
5-25
The reserved word nil is a special constant that can be assigned to any pointer. When
nil is assigned to a pointer, the pointer doesnt 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, its 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-12.)
5-26
Pointer type
PAnsiString, PString
AnsiString
PByteArray
PCurrency
Currency
PExtended
Extended
POleVariant
OleVariant
PShortString
ShortString. Useful when porting legacy code that uses PString type.
PTextBuf
PVarRec
PVariant
Variant
PWideString
WideString
PWordArray
Procedural types
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;
If you take any procedure or function heading and remove the identifier after the
word procedure or function, whats left is the name of a procedural type. You can
use such type names directly in variable declarations (as in the example above) 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 variables above are all procedure pointersthat 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
5-27
Procedural types
In assignment statements, the type of the variable on the left determines the
interpretation of procedure or method pointers on the right. For example,
var
F, G: function: Integer;
I: Integer;
function SomeFunction: Integer;
The first statement assigns a procedural value to F. The second statement copies that
value to another variable. The third statement makes a call to the referenced function
5-28
Variant types
and assigns the result to I. Because I is an integer variable, not a procedural one, the
last assignment actually calls the function (which returns an integer).
In some situations it is less clear how a procedural variable should be interpreted.
Consider the statement
if F = MyFunction then ...;
In this case, the occurrence of F results in a function call; the compiler calls the
function pointed to by F, then calls the function MyFunction, then compares the
results. The rule is that whenever a procedural variable occurs within an expression,
it represents a call to the referenced procedure or function. In a case where F
references a procedure (which doesnt return a value), or where F references a
function that requires parameters, the statement above causes a compilation error. To
compare the procedural value of F with MyFunction, use
if @F = @MyFunction then ...;
calls the Windows 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, as they are called, 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.
Variants can hold values of any type except records, sets, static arrays, files, classes,
class references, pointers, and Int64. In other words, with the exception of Int64,
variants can hold anything but structured types and pointers. They can hold COM
and CORBA objects, whose methods and properties can be accessed through them.
(See Chapter 10, Object interfaces.) They can hold dynamic arrays, and they can
5-29
Variant types
hold a special kind of static array called a variant array. (See Variant arrays on
page 5-32.) 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] is illegitimate.
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 variants type code. The varTypeMask
constant is a bit mask used to extract the code from VarTypes 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. See the online Help on VarType for a list if codes, and
note that new type codes may be added in future implementations of Object Pascal.
5-30
Variant types
Target
integer
Source
real
string
character Boolean
integer
converts
integer
formats
converts to
real
converts to
string
representation
same as
string
(left)
returns False if 0,
True otherwise
real
rounds to
nearest integer
converts real
formats
converts to
string
representation
using
Windows
regional
settings
same as
string
(left)
returns False if 0,
True otherwise
string
converts to
integer,
truncating if
necessary;
raises
exception if
string is not
numeric
converts to
real using
Windows
regional
settings; raises
exception if
string is not
numeric
converts
string/
character
formats
same as
string
(left)
returns False if
string is false
(noncasesensitive) or a
numeric string
that evaluates to
0, True if string is
true or a
nonzero numeric
string; raises
exception
otherwise
character
same as string
(above)
same as string
(above)
same as string
(above)
same as
string-tostring
same as string
(above)
Boolean
False = 0,
True = 1
(255 if Byte)
False = 0,
True = 1
False = 0,
True = 1
same as
string
(left)
False = False,
True = True
Unassigned
returns 0
returns 0
returns empty
string
same as
string
(left)
returns False
Null
raises
exception
raises
exception
raises
exception
same as
string
(left)
raises exception
Out-of-range assignments often result in the target variable getting the highest value
in its range. Invalid assignments or casts raise the EVariantError exception.
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 Windows 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.
5-31
Variant types
If a variant references a COM object, any attempt to convert it reads the objects
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. Operations on variants
return Variant values; they return Null if one or both operands is Null, and raise an
exception if one or both operands is Unassigned. In a binary operation, if only one
operand is a variant, the other is converted to a variant.
The return type of an operation is determined by its operands. In general, the same
rules that apply to operands of statically bound types apply to variants. For example,
if V1 and V2 are variants that hold an integer and a real value, then V1 + V2 returns a
real-valued variant. (See Operators on page 4-6.) With variants, however, you can
perform binary operations on combinations of values that would not be allowed
using statically typed expressions. When possible, the compiler converts mismatched
variants using the rules summarized in Table 5.7. For example, if V3 and V4 are
variants that hold a numeric string and an integer, the expression V3 + V4 returns an
integer-valued variant; the numeric string is converted to an integer before the
operation is performed.
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 arrays
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.
5-32
OleVariant
The OleVariant type represents variants that contain only COM-compatible types.
When a Variant is assigned to an OleVariant, incompatible types are converted to
their compatible counterparts. For example, if a variant containing an AnsiString is
assigned to an OleVariant, the AnsiString becomes a WideString.
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
T2
T3
T4
=
=
=
=
Integer;
T1;
Integer;
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;
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];
5-33
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 components.
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 Char.
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.
5-34
Declaring types
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;
A type identifiers scope doesnt 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;
5-35
Variables
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 informationfor example, to associate a Delphi property
editor with properties of a particular typethe 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;
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;
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-39.) Thus the declaration
5-36
Variables
var I: Integer = 7;
I := 7;
Absolute addresses
To declare a variable that resides at a specified memory address, put the word
absolute after the type name, followed by an address. Example:
var CrtMode: Byte absolute $0040;
This technique is useful only in low-level programming, for example when writing
device drivers.
To create a new variable that resides at the same address as an existing variable, use
the name of the existing variable (instead of an address) after the word absolute. 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 strings length, the value of StrLen is the length of
Str.
You cannot initialize a variable in an absolute declaration.
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 variables 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, Initialize, StrAlloc, and StrDispose.
Long strings and dynamic arrays are also heap-allocated dynamic variables, but their
memory is managed automatically.
5-37
Declared constants
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.
Reference-counted variables (such as long strings, dynamic arrays, or interfaces) are
not thread-safe, even if they are declared with threadvar. Do not use dynamic thread
variables, since there is in general no way to free the heap-allocated memory created
by each thread of execution. Finally, do not create pointer- or procedural-type thread
variables.
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-39 for more information.)
5-38
Declared constants
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
(hexadecimal)
Range of constant
(decimal)
Type
$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
63
4294967296..2 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;
5-39
Declared constants
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:
Abs
Chr
Hi
High
Length
Lo
Low
Odd
Ord
Pred
Round
SizeOf
Succ
Swap
Trunc
Resource strings
Resource strings are stored as resources and linked into the executable or library so
that they can be modified without recompiling the program. For more information,
see the online Help topics on localizing applications.
Resource strings are declared like other true constants, except that the word const is
replaced by resourcestring. The expression to the right of the = symbol must be a
constant expression and must return a string value. For example,
resourcestring
CreateError = 'Cannot create file %s';
OpenError = 'Cannot open file %s';
LineTooLong = 'Line too long';
ProductName = 'Borland Delphi\000\000';
SomeResourceString = SomeTrueConstant;
Typed constants
Typed constants, unlike true constants, can hold values of array, record, procedural,
and pointer types. Typed constants cannot occur in constant expressions.
In the default {$J+} compiler state, typed constants can have new values assigned to
them; they behave essentially like initialized variables. But if the {$J} compiler
5-40
Declared constants
directive is in effect, typed constants cannot change value at runtime; they are, in
effect, read-only variables.
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 arrays 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');
Record constants
To declare a record constant, specify the value of each fieldas fieldName: value,
with the field assignments separated by semicolonsin 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.
5-41
Declared constants
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);
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;
Given these declarations, you can use the procedural constant MyFunction in a
function call:
I := MyFunction(5, 7)
Pointer constants
When you declare a pointer constant, you must initialize it to a value that can be
resolvedat least as a relative addressat compile time. There are three ways to do
this: with the @ operator, with nil, and (if the constant is of type PChar) 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!';
5-42
Chapter
Chapter6
calls SomeFunction and assigns the result to I. Function calls cannot appear on the left
side of an assignment statement.
Both function and procedure calls can be used as complete statements. For example,
DoSomething;
6-1
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-9.
For information about directives, see Calling conventions on page 6-4, Forward
and interface declarations on page 6-5, External declarations on page 6-6, and
Overloading procedures and functions on page 6-7. 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-8.
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 procedures 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 example above); the parameter list
defines a set of local variables, so dont try to redeclare the parameter names in the
localDeclarations section. Finally, you can use any identifiers within whose scope the
procedure declaration falls.
6-2
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 any type, 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-9.
For information about directives, see Calling conventions on page 6-4, Forward
and interface declarations on page 6-5, External declarations on page 6-6, and
Overloading procedures and functions on page 6-7. 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-8.
The functions 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 functions
return value, as does the predefined variable Result.
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;
6-3
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 functions 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.
Result is implicitly declared in every function, so do not try to redeclare it.
If execution terminates without an assignment being made to Result or the function
name, then the functions 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.
6-4
The register and pascal conventions pass parameters from left to right; that is, the
leftmost 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 COM error and exception handling.
The table below summarizes calling conventions.
Table 6.1
Calling conventions
Directive
Parameter order
Clean-up
register
Left-to-right
Routine
Yes
pascal
Left-to-right
Routine
No
cdecl
Right-to-left
Caller
No
stdcall
Right-to-left
Routine
No
safecall
Right-to-left
Routine
No
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 DLLs written in C or C++,
while stdcall and safecall are used for Windows API calls. 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.
declares a function called Calculate. Somewhere after the forward declaration, the
routine must be redeclared in a defining declaration that includes a block. The defining
declaration for Calculate might look like this:
function Calculate;
{ declarations }
begin
{ statement block }
end;
6-5
Ordinarily, a defining declaration does not have to repeat the routines parameter list
or return type, but if it does repeat them, they must match those in the forward
declaration exactly (except that default parameters can be omitted). If the forward
declaration specifies an overloaded procedure or function (see Overloading
procedures and functions on page 6-7), then the defining declaration must repeat
the parameter list.
Between a forward declaration and its defining declaration, you can place nothing
except other declarations. The defining declaration can be an external or assembler
declaration, but it cannot be another forward declaration.
The purpose of a forward declaration is to extend the scope of a procedure or
function identifier to an earlier point in the source code. This allows other procedures
and functions to call the forward-declared routine before it is actually defined.
Besides letting you organize your code more flexibly, forward declarations are
sometimes necessary for mutual recursions.
The forward directive is not allowed in the interface section of a unit. Procedure and
function headers in the interface section, however, behave like forward declarations
and must have defining declarations in the implementation section. A routine
declared in the interface section is available from anywhere else in the unit and from
any other unit or program that uses the unit where it is declared.
External declarations
The external directive, which replaces the block in a procedure or function
declaration, allows you to call procedures and functions that are compiled separately
from your program.
links BLOCK.OBJ into the program or unit in which it occurs. Next, declare the
functions and procedures that you want to call:
procedure MoveWord(var Source, Dest; Count: Integer); external;
procedure FillWord(var Dest; Data: Integer; Count: Integer); external;
Now you can call the MoveWord and FillWord routines from BLOCK.OBJ.
Declarations like the ones above are frequently used to access external routines
written in assembly language. You can also place assembly-language routines
directly in your Object Pascal source code; for more information, see Chapter 13,
Inline assembler code.
6-6
These declarations create two functions, both called Divide, that take parameters of
different types. When you call Divide, the compiler determines which function to
6-7
invoke by looking at the actual parameters passed in the call. For example,
Divide(6.0, 3.0) calls the first Divide function, because its arguments are real-valued.
You can pass to an overloaded routine parameters that are not identical in type with
those in any of the routines 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 typesfor example,
procedure Store(X: Longint); overload;
procedure Store(X: Shortint); overload;
are legal.
When an overloaded routine is declared in a forward or interface declaration, the
defining declaration must repeat the routines parameter list.
If you use default parameters in overloaded routines, be careful of ambiguous
parameter signatures. For more information, see Default parameters and
overloaded routines on page 6-17.
You can limit the potential effects of overloading by qualifying a routines name when
you call it. For example, Unit1.MyProcedure(X, Y) can call only routines declared 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 DLL, see The exports clause on page 9-4.
Local declarations
The body of a function or procedure often begins with declarations of local variables
used in the routines 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.
6-8
Parameters
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;
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 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;
6-9
Parameters
Within the procedure or function body, the parameter names (X and Y in the first
example above) 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-13.
Files and instances of structured types that contain files can be passed only as
variable (var) parameters.
// X is a value parameter
// X is a variable parameter
These functions return the same result, but only the second oneDoubleByRefcan
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);
W := DoubleByRef(V);
end;
// J = 8, I = 4
// W = 8, V = 8
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
6-10
Parameters
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;
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 cant 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 objects 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
6-11
Parameters
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 doesnt 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 youre 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;
6-12
Parameters
var
Vec1, Vec2: TVector;
N: Integer;
P: TPoint;
//
//
//
//
compare
compare
compare
compare
Vec1 to
first N
first 5
Vec1[1]
Vec2
elements of Vec1 and Vec2
to last 5 elements of Vec1
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
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
is valid. For most purposes, however, open array parameters are a better solution.
6-13
Parameters
For example,
function Find(A: array of Char): Integer;
declares a function called Find that takes a character array of any size and returns an
integer.
Note
The syntax of open array parameters resembles that of dynamic array types, but they
do not mean the same thing. The example above 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-17.
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 Length1, 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 parameters 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 routines 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-18.
6-14
Parameters
We can call this function using an open array constructor (see Open array
constructors on page 6-18). For example,
MakeStr(['test', 100, ' ', True, 3.14159, TForm])
6-15
Parameters
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 parameters type.
For example, given the declaration
procedure FillArray(A: array of Integer; Value: Integer = 0);
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
the statements
F := Resizer;
F(N);
6-16
Confused(X);
// Which procedure is called?
6-17
This is equivalent to
var Temp: array[0..3] of Integer;
Temp[0] := 5;
Temp[1] := 7;
Temp[2] := I;
Temp[3] := I + J;
Add(Temp);
Open array constructors can be passed only as value or const parameters. The
expressions in a constructor must be assignment-compatible with the base type of the
array parameter. In the case of a variant open array parameter, the expressions can be
of different types.
6-18
Chapter
Chapter7
A class, or class type, defines a structure consisting of fields, methods, and properties.
Instances of a class type are called objects. The fields, methods, and properties of a
class are called its components or members.
A field is essentially a variable that is part of an object. Like the fields of a record, a
classs fields represent data items that exist in each instance of the class.
A method is a procedure or function associated with a class. Most methods
operate on objectsthat is, instances of a class. Some methods (called class
methods) operate on class types themselves.
A property is an interface to data associated with an object (often stored in a field).
Properties have access specifiers, which determine how their data are read and
modified. From other parts of a programoutside of the object itselfa property
appears in most respects like a field.
Objects are dynamically allocated blocks of memory whose structure is determined
by their class type. Each object has a unique copy of every field defined in the class,
but all instances of a class share the same methods. Objects are created and destroyed
by special methods called constructors and destructors.
A variable of a class type is actually a pointer that references an object. Hence more
than one variable can refer to the same object. Like other pointers, class-type
variables can hold the value nil. But you dont have to explicitly dereference a classtype variable to access the object it points to. For example, SomeObject.Size := 100
assigns the value 100 to the Size property of the object referenced by SomeObject; you
would not write this as SomeObject^.Size := 100.
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.
Classes and objects
7-1
Class types
TListColumns descends from TCollection (in the Classes unit), inheriting most of its
members. But it definesor redefinesseveral methods and properties, including its
constructor method, Create. Its destructor, Destroy, is inherited without change from
TCollection, 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, we can create a TListColumns with
var ListColumns: TListColumns;
ListColumns := TListColumns.Create(SomeListView);
declares a class called TSomeControl that descends from TWinControl. A class type
automatically inherits all of the members from its immediate ancestor. Each class can
7-2
Class types
declare new members and can redefine inherited ones, but a class cannot remove
members defined in an ancestor. Hence TSomeControl contains all of the members
defined in TWinControl and in each of TWinControls ancestors.
The scope of a members identifier starts at the point where the member is declared,
continues to the end of the class declaration, and extends over all descendants of the
class and the blocks of all methods defined in the class and its descendants.
For more information about TObject, see the online VCL reference. For more
information about class-reference types, see Class references on page 7-22.
If the declaration of a class type doesnt specify an ancestor, the class inherits directly
from TObject. Thus
type TMyClass = class
end;
is equivalent to
type TMyClass = class(TObject)
end;
the variable Fig can be assigned values of type TFigure, TRectangle, and TSquare.
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
7-3
Class types
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.
declares a published property called Color. Visibility determines where and how a
member can be accessed, with private representing the least accessibility, protected
representing an intermediate level of accessibility, and public, published, and
automated representing the greatest accessibility.
If a members declaration appears without its own visibility specifier, the member
has the same visibility as the one that precedes it. Members at the beginning of a class
declaration that dont have a specified visibility are by default published, provided
the class is compiled in the {$M+} state or is derived from a class compiled in the
{$M+} state; otherwise, such members are public.
For readability, it is best to organize a class declaration by visibility, placing all the
private members together, followed by all the protected members, and so forth. This
way each visibility reserved word appears at most once and marks the beginning of a
new section of the declaration. So a typical class declaration should like this:
type
TMyClass = class(TControl)
private
{ private declarations here}
protected
{ protected declarations here }
public
{ public declarations here }
published
{ published declarations here }
end;
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-20.
7-4
Class types
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. Delphi uses RTTI to access the values of properties when saving and
loading form (.DFM) 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, 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. Array properties cannot be published.
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 classes derived
from the TAutoObject class in the OleAuto unit. This unit, and the automated reserved
word itself, are maintained for backward compatibility. The TAutoObject class in the
ComObj unit does not use automated.
7-5
Class types
end;
TFigure = class // defining declaration
Drawing: TDrawing;
end;
7-6
Fields
Do not confuse forward declarations with complete declarations of types that derive
from TObject without declaring any class members.
type
TFirstClass = class;
TSecondClass = class
end;
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
TDescendant(MyObject).Value := 'Hello!'; // works!
end;
7-7
Methods
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
Method implementations
Within a class declaration, methods appear as procedure and function headings,
which work like forward declarations. Somewhere after the class declaration, but
within the same module, each method must be implemented by a defining
declaration. For example, suppose the declaration of TMyClass includes a method
called DoSomething:
type
TMyClass = class(TObject)
procedure DoSomething;
end;
end;
While a class can be declared in either the interface or the implementation section of a
unit, defining declarations for a classs methods must be in the implementation section.
In the heading of a defining declaration, the method name is always qualified with
the name of the class to which it belongs. The heading can repeat the parameter list
from the class declaration; if it does so, the order, type, and names of the parameters
must match exactly, and, if the method is a function, so must the return value.
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 a method identifier, it represents a normal method call,
except that the search for the method begins with the immediate ancestor of the
enclosing methods class. For example, when
inherited Create(...);
7-8
Methods
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 TCollections
Add method in the Classes unit of the VCL.
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;
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 classs 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-24.
Method binding
Methods 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;
7-9
Methods
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;
Given these declarations, the following code illustrates the effect of calling a virtual
method through a variable whose actual type varies at runtime.
var
Figure: TFigure;
begin
Figure := TRectangle.Create;
Figure.Draw; // calls TRectangle.Draw
Figure.Destroy;
7-10
Methods
Figure := TEllipse.Create;
Figure.Draw; // calls TEllipse.Draw
Figure.Destroy;
end;
Only virtual and dynamic methods can be overridden. All methods, however, can be
overloaded; see Overloading methods on page 7-12.
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.
7-11
Methods
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
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);
7-12
Methods
Constructors must use the default register calling convention. Although the
declaration specifies no return value, when a constructor is called using a class
reference, it returns a reference to the object it creates.
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 in 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
parameters to the constructor. Finally, the constructor returns a reference to the
newly allocated and initialized object. The type of the returned value is the same as
the class type specified in the constructor call.
If an exception is raised during execution of a constructor that was invoked on a class
reference, the Destroy destructor is automatically called to destroy the unfinished object.
When a constructor is called using an object reference (rather than a class reference),
it does not create an object or return a value. Instead, the constructor operates on the
specified object, executing only the statements in the constructors implementation. A
constructor is typically invoked on an object reference in conjunction with the
reserved word inherited to execute an inherited constructor.
Here is an example of a class type and its constructor.
type
TShape = class(TGraphicControl)
private
FPen: TPen;
FBrush: TBrush;
procedure PenChanged(Sender: TObject);
procedure BrushChanged(Sender: TObject);
public
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
end;
constructor TShape.Create(Owner: TComponent);
begin
inherited Create(Owner); // Initialize inherited parts
Width := 65; // Change inherited properties
Height := 65;
FPen := TPen.Create; // Initialize new fields
FPen.OnChange := PenChanged;
FBrush := TBrush.Create;
FBrush.OnChange := BrushChanged;
end;
7-13
Methods
descendant class. Because a constructor always clears the storage it allocates for a new
object, all fields start with a value of zero (ordinal types), nil (pointer and class types),
empty (string types), or Unassigned (variants). Hence there is no need to initialize fields
in a constructors implementation except to nonzero or nonempty values.
When invoked through a class-type identifier, a constructor declared as virtual is
equivalent to a static constructor. When combined with class-reference types,
however, virtual constructors allow polymorphic construction of objectsthat is,
construction of objects whose types arent known at compile time. (See Class
references on page 7-22.)
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. Examples:
destructor Destroy;
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;
7-14
Methods
Message handlers
Message handlers are methods that implement responses to dynamically dispatched
messages. Delphis VCL uses message handlers to respond to Windows messages.
A message handler 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 handlers in VCL controls, the integer constant must be one
of the Windows message IDs defined, along with corresponding record types, in the
Messages unit. For example,
type
TTextBox = class(TCustomControl)
private
procedure WMChar(var Message: TWMChar); message WM_CHAR;
end;
The inherited statement searches backward through the class hierarchy and invokes
the first message handler with the same ID as the current method, automatically
passing the message record to it. If no ancestor class implements a message handler
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. The DefaultHandler method for VCL 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);
7-15
Properties
The Message parameter passed to Dispatch must be a record whose first entry is a field
of type Cardinal containing a message ID. See the Messages unit for examples.
Dispatch searches backward through the class hierarchy (starting from the class of the
object where it is called) and invokes the first message handler for the ID passed to it.
If no message handler 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 objects 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-18.
the index integerConstant clause is optional. For more information, see Index
specifiers on page 7-19.
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-6.)
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 doesnt 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
7-16
Properties
end;
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
7-17
Properties
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;
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;
7-18
Properties
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:\DELPHI\BIN';
correspond to
if Collection.GetObject(0) = nil then Exit;
Canvas.SetPixel(10, 20, clRed);
Params.SetValue('PATH', 'C:\DELPHI\BIN');
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
determines an objects default property 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
7-19
Properties
corresponds to
Rectangle.SetCoordinate(2, Rectangle.GetCoordinate(0) + 100);
Storage specifiers
The optional stored, default, and nodefault directives are called storage specifiers.
They have no effect on program behavior, but control the way Delphi maintains
runtime type information (RTTI). Specifically, storage specifiers determine whether
Delphi saves the values of published properties in form (.DFM) 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;
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 sets
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.
When Delphi saves a components state, it checks the storage specifiers of the
components published properties. If a propertys current value is different from its
default value (or if there is no default value) and the stored specifier is True, then the
propertys value is saved. Otherwise, the propertys value is not saved.
Note
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-18.
7-20
Properties
protected
property Size: Integer read FSize;
property Text: string read GetText write SetText;
property Color: TColor read FColor write SetColor stored False;
end;
type
TDerived = class(TAncestor)
protected
property Size write SetSize;
published
property Text;
property Color stored True default clBlue;
end;
The override of Size adds a write specifier to allow the property to be modified. The
overrides of Text and Color change the visibility of the properties from protected to
published. The property override of Color also specifies that the property should be
filed if its value isnt clBlue.
A redeclaration of a property that includes a type identifier hides the inherited
property rather than overriding it. This means that a new property is created with the
same name as the inherited one. Any property declaration that specifies a type must
be a complete declaration, and must therefore include at least one access specifier.
Whether a property is hidden or overridden in a derived class, property look-up is
always static. That is, the declared (compile-time) type of the variable used to identify
an object determines the interpretation of its property identifiers. Hence, after the
following code executes, reading or assigning a value to MyObject.Value invokes
Method1 or Method2, even though MyObject holds an instance of TDescendant. But you
can cast MyObject to TDescendant to access the descendant classs properties and their
access specifiers.
type
TAncestor = class
7-21
Class references
TDescendant = class(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 assignmentcompatible 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 VCLs Classes unit):
type TCollectionItemClass = class of TCollectionItem;
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.
7-22
Class references
For example,
type TControlClass = class of TControl;
function CreateControl(ControlClass: TControlClass;
const ControlName: string; X, Y, W, H: Integer): TControl;
begin
Result := ControlClass.Create(MainForm);
with Result do
begin
Parent := MainForm;
Name := ControlName;
SetBounds(X, Y, W, H);
Visible := True;
end;
end;
Constructors called using class references are usually virtual. The constructor
implementation activated by the call depends on the runtime type of the class
reference.
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 objects 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 classthat is, if the types are distinct and one is not an
ancestor of the othera 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.
7-23
Class references
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 classthat is, if the types are distinct and one is not an ancestor of the
othera compilation error results. For example,
with Sender as TButton do
begin
Caption := '&Ok';
OnClick := OkClick;
end;
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.
7-24
Exceptions
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, all runtime errors are automatically
converted into exceptions. Errors that would otherwise terminate an application
such as insufficient memory, division by zero, and general protection faultscan be
caught and handled.
Given these declarations, you can define a single EMathError exception handler that
also handles EInvalidOp, EZeroDivide, EOverflow, and EUnderflow.
Exception classes sometimes define fields, methods, or properties that convey
additional information about the error. For example,
type EInOutError = class(Exception)
ErrorCode: Integer;
end;
7-25
Exceptions
Notice the CreateFmt method called in the raise statement. Exception and its
descendants have special constructors that provide alternative ways to create
exception messages and context IDs. See the online Help for details.
A raised exception is destroyed automatically after it is handled. Never attempt to
destroy a raised exception manually.
Note
Raising an exception in the initialization section of a unit may not produce the
intended result. Normal exception support comes from the SysUtils unit, which must
be initialized before such support is available. If an exception occurs during
initialization, all initialized unitsincluding SysUtilsare finalized and the
exception is re-raised. Then the System unit catches the exception and handles it,
usually by interrupting the program.
Try...except statements
Exceptions are handled within try...except statements. For example,
try
X := Y/Z;
except
on EZeroDivide do HandleZeroDivide;
end;
7-26
Exceptions
7-27
Exceptions
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 arent handled by the blocks 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 isnt 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.
7-28
Exceptions
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 failsfor example because the
search path is invalid, or because there is not enough memory to fill in the string
listGetFileList 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 statements exception block
disposes of the string list, then re-raises the exception.
Nested exceptions
Code executed in an exception handler can itself raise and handle exceptions. As long
as these exceptions are also handled within the exception handler, they do not affect
the original exception. However, once an exception raised in an exception handler
propagates beyond that handler, the original exception is lost. This is illustrated by
the Tan function below.
type
ETrigError = class(EMathError);
function Tan(X: Extended): Extended;
begin
try
Result := Sin(X) / Cos(X);
except
on EMathError do
raise ETrigError.Create('Invalid argument to Tan');
end;
end;
7-29
Exceptions
exception to be destroyed. To the caller, it appears as if the Tan function has raised an
ETrigError 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;
7-30
Chapter
Chapter8
This chapter discusses text and file I/O and summarizes standard library routines.
Many of the procedures and functions listed here are defined in the System unit,
which is implicitly compiled with every application. Others are built into the
compiler but are treated as if they were in the System unit.
Some standard routines are in units such as SysUtils, which must be listed in a uses
clause to make them available in programs. You cannot, however, list System in a
uses clause, nor should you modify the System unit or try to rebuild it explicitly.
For more information about the routines listed here, see the online Help.
Procedure or
function
Description
Append
AssignFile
BlockRead
BlockWrite
ChDir
CloseFile
Eof
Eoln
Erase
FilePos
FileSize
Returns the current size of a file; not used for text files.
8-1
Table 8.1
Procedure or
function
Description
Flush
GetDir
IOResult
Returns an integer value that is the status of the last I/O function performed.
MkDir
Creates a subdirectory.
Read
Reads one or more values from a file into one or more variables.
Readln
Does what Read does and then skips to beginning of next line in the text file.
Rename
Reset
Rewrite
RmDir
Seek
SeekEof
SeekEoln
SetTextBuf
Truncate
Write
Writeln
Does the same as Write, and then writes an end-of-line marker to the text file.
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-23.
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.
8-2
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
offthat is, when a procedure or function call is compiled in the {$I} statean I/O
error doesnt 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 arent interested in
the error. If you dont 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 systems standard
input (typically the keyboard). The standard file variable Output is a write-only file
associated with the operating systems 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
8-3
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.
8-4
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 TextRec(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 isnt modified by the Delphi file system at any time.
Device functions
The functions that make up a text-file device driver are described below.
8-5
When Mode is fmOutput, the InOut function writes BufPos characters from BufPtr^,
and returns zero in BufPos.
8-6
Function
Description
StrAlloc
StrBufSize
StrCat
StrComp
StrCopy
Copies a string.
StrDispose
StrECopy
StrEnd
StrFmt
StrIComp
StrLCat
Concatenates two strings with a given maximum length of the resulting string.
StrLComp
Table 8.2
Function
Description
StrLCopy
StrLen
StrLFmt
Formats one or more values into a string with a given maximum length.
StrLIComp
Compares two strings for a given maximum length without case sensitivity.
StrLower
StrMove
StrNew
StrPCopy
StrPLCopy
StrPos
StrRScan
StrScan
StrUpper
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.
For more information about wide-character strings, see About extended character
sets on page 5-12.
Procedure or
function
Description
Abort
Addr
AllocMem
8-7
Table 8.3
8-8
Procedure or
function
Description
ArcTan
Assert
Assigned
Beep
Break
ByteToCharIndex
Chr
Close
CompareMem
CompareStr
CompareText
Continue
Copy
Cos
CurrToStr
Date
DateTimeToStr
DateToStr
Dec
Dispose
ExceptAddr
Exit
Exp
FillChar
Finalize
FloatToStr
FloatToStrF
FmtLoadStr
FmtStr
Format
FormatDateTime
FormatFloat
FreeMem
GetMem
GetParentForm
Halt
Hi
High
Inc
Table 8.3
Procedure or
function
Description
Initialize
Insert
Int
IntToStr
Length
Lo
Low
LowerCase
MaxIntValue
MaxValue
MinIntValue
MinValue
New
Now
Ord
Pos
Pred
Ptr
Random
ReallocMem
Round
SetLength
SetString
ShowException
ShowMessage
ShowMessageFmt
Sin
SizeOd
Sqr
Sqrt
Str
StrToCurr
StrToDate
StrToDateTime
StrToFloat
StrToInt
StrToTime
StrUpper
Succ
8-9
Table 8.3
Procedure or
function
Description
Sum
Time
TimeToStr
Trunc
UniqueString
UpCase
UpperCase
VarArrayCreate
VarArrayDimCount
VarARrayHighBound
VarArrayLock
VarArrayLowBound
VarArrayOf
VarArrayRedim
VarArrayRef
VarArrayUnlock
VarAsType
VarCast
VarClear
Clears a variant.
CarCopy
Copies a variant.
VarToStr
VarType
For information on format strings, see Format strings in the online Help.
8-10
Part
II
Special topics
Part II
The chapters in Part II cover specialized language features and advanced topics.
These chapters include:
Chapter 9, Dynamic-link libraries and packages
Chapter 10, Object interfaces
Chapter 11, Memory management
Chapter 12, Program control
Chapter 13, Inline assembler code
Special topics
Chapter
Chapter9
Calling DLLs
Before you can call routines defined in a DLL, you must import them. This can be
done in two ways: by declaring an external procedure or function, or by calling the
Windows API directly. Whichever method you use, the routines are not linked to
your application until runtime. This means that the DLL need not be present when
you compile your program. It also means that there is no compile-time validation of
attempts to import a routine.
Object Pascal does not support importing of variables from DLLs.
Static loading
The simplest way to import a procedure or function is to declare it using the external
directive. For example,
procedure DoSomething; external 'MYLIB.DLL';
If you include this declaration in a program, MYLIB.DLL 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 DLL.
9-1
Calling DLLs
Dynamic loading
You can access routines in a DLL through direct calls to Windows library functions,
including LoadLibrary, FreeLibrary, and GetProcAddress (all declared in Delphis
Windows unit). In this case, use procedural-type variables to reference the imported
routines. For example,
uses Windows, ...;
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('DATETIME.DLL');
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 DLL is not loaded until the code containing
the call to LoadLibrary executes. The DLL is later unloaded by the call to FreeLibrary.
This allows you to conserve memory, and to run your program even when some of
the DLLs it uses are not present.
9-2
Writing DLLs
Writing DLLs
The structure of a DLL is identical to that of a program, except that a DLL begins with
the reserved word library (instead of program).
The following example shows a DLL with two exported functions, Min and Max.
library MinMax;
function Min(X, Y: Integer): Integer; stdcall;
begin
if X < Y then Min := X else Min := Y;
end;
function Max(X, Y: Integer): Integer; stdcall;
begin
if X > Y then Max := X else Max := Y;
end;
exports
Min,
Max;
begin
end.
If you want your DLL to be available to applications written in other languages, its
safest to specify stdcall in the declarations of exported functions. Other languages
may not support Object Pascals default register calling convention.
DLLs 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 DLLs initialization code. For
example,
library Editors;
uses EdInit, EdInOut, EdFormat, EdPrint;
exports
InitEditors,
DoneEditors index 17 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 units exports clauseswithout the need for an exports clause of its own.
Only routines that a library explicitly exports are available for importing by other
libraries or programs.
9-3
Writing DLLs
When you export an overloaded function or procedure from a DLL, you must specify
its parameter list in the exports clause. For example,
exports
Divide(X, Y: Integer) name 'Divide_Ints',
Divide(X, Y: Real) name 'Divide_Reals';
9-4
Writing DLLs
When a DLL is unloaded, the librarys exit procedures are executed by repeated calls
to the address stored in ExitProc, until ExitProc becomes nil. The initialization parts of
all units used by a library are executed before the librarys initialization code, and the
finalization parts of those units are executed after the librarys exit procedure.
9-5
Writing DLLs
and assign the address of the procedure to the DLLProc variable. When Windows
calls the procedure, it passes to it one of the following values (defined in the Windows
unit).
DLL_PROCESS_DETACH
DLL_THREAD_ATTACH
DLL_THREAD_DETACH
In the body of the procedure, you can specify actions to take depending on which
parameter is passed to the procedure.
9-6
Packages
Packages
A package is a specially compiled dynamic-link library used by Delphi applications,
the Delphi IDE, or both. Runtime packages provide functionality when a user runs an
application. Design-time packages are used to install components in Delphis 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 DLLs, package libraries are stored in files that end
with the .BPL (Borland package library) 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.
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.
9-7
Packages
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,
contains MyUnit in 'C:\MyProject\MyUnit.pas';
Note
Naming packages
A compiled package involves several generated files. For example, the source file for
the package called VCL50 is VCL50.DPK, from which the compiler generates an
executable and a binary image called VCL50.BPL and VCL50.DCP, respectively. VCL50
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.
9-8
Packages
All units included directly in a packages contains clause, or indirectly in the uses
clauses of those units, are bound into the package at compile time. The units
contained (directly or indirectly) in a package cannot be contained in any other
packages referenced in requires clause of that package.
A unit cannot be contained (directly or indirectly) in more than one package used by
the same application.
Compiling packages
Packages are ordinarily compiled from the Delphi IDE using .DPK files generated by
the Package editor. You can also compile .DPK files directly from the command line.
When you build a project that contains a package, the package is implicitly
recompiled if necessary.
Generated files
The following table lists the files produced by the successful compilation of a
package.
Table 9.1
File extension
Contents
DCP
A binary image containing a package header and the concatenation of all DCU
files in the package. A single DCP file is created for each package. The base
name for the DCP is the base name of the DPK source file.
DCU
A binary image for a unit file contained in a package. One DCU is created,
when necessary, for each unit file.
BPL
The runtime package. This file is a Windows DLL with special Delphi-specific
features. The base name for the BPL is the base name of the DPK source file.
Directive
Purpose
{$IMPLICITBUILD OFF}
{$WEAKPACKAGEUNIT ON}
{$DENYPACKAGEUNIT ON}
9-9
Packages
Table 9.2
Directive
Purpose
{$DESIGNONLY ON}
{$RUNONLY ON}
Including {$DENYPACKAGEUNIT ON} in source code prevents the unit file from
being packaged. Including {$G} or {IMPORTEDDATA OFF} may prevent a
package from being used in the same application with other packages.
Other compiler directives may be included, if appropriate, in package source code.
Switch
Purpose
$G
LE path
LN path
LUpackageName[;packageName2;...]
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.
9-10
Chapter
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.
Object interfaces
10-1
Interface types
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(IUnknown)
['{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;
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
['{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}']
where each x is a hexadecimal digit (0 through 9 or A through F).
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-9.)
10-2
Interface types
The TGUID and PGUID types, declared in the System unit, are used to manipulate
GUIDs.
type
PGUID
TGUID
D1:
D2:
D3:
D4:
end;
= ^TGUID;
= packed record
Longword;
Word;
Word;
array[0..7] of Byte;
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: IUnknown; const IID: TGUID): Boolean;
Calling conventions
The default calling convention is register, but interfaces shared among modules
(especially if they are written in different languages) should declare all methods with
stdcall. Use safecall to implement methods of dual interfaces (as described in Dual
interfaces on page 10-12) and CORBA interfaces.
For more information about calling conventions, see Calling conventions on
page 6-4.
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.
COM objects do not have properties.
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
Object interfaces
10-3
Implementing interfaces
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 classs declaration,
after the name of the classs 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, IUnknown)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
property RefCount: Integer read FRefCount;
end;
10-4
Implementing interfaces
end;
maps IMallocs Alloc and Free methods onto TMemoryManagers Allocate and Deallocate
methods.
A method resolution clause cannot alter a mapping introduced by an ancestor class.
Object interfaces
10-5
Implementing interfaces
A class can also reimplement an entire interface that it inherits from an ancestor class.
This involves relisting the interface in the descendant classs declaration. For
example,
type
IWindow = interface
['{00000115-0000-0000-C000-000000000146}']
procedure Draw;
end;
TWindow = class(TInterfacedObject, IWindow) // TWindow implements IWindow
procedure Draw;
end;
TFrameWindow = class(TWindow, IWindow) // TFrameWindow reimplements IWindow
procedure Draw;
end;
10-6
Implementing interfaces
type
IMyInterface = interface
procedure P1;
procedure P2;
end;
TMyClass = class(TObject, IMyInterface)
FMyInterface: IMyInterface;
property MyInterface: IMyInterface read FMyInterface implements IMyInterface;
end;
var
MyClass: TMyClass;
MyInterface: IMyInterface;
begin
MyClass := TMyClass.Create;
MyClass.FMyInterface := ... // some object whose class implements IMyInterface
MyInterface := MyClass;
MyInterface.P1;
end;
procedure TMyImplClass.P2;
procedure TMyClass.MyP1;
var
MyClass: TMyClass;
MyInterface: IMyInterface;
Object interfaces
10-7
Interface references
begin
MyClass := TMyClass.Create;
MyClass.FMyImplClass := TMyImplClass.Create;
MyInterface := MyClass;
MyInterface.P1;
// calls TMyClass.MyP1;
MyInterface.P2;
// calls TImplClass.P2;
end;
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 limitations.
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 TSomethings declaration to
TSomething = class(TInterfacedObject, IAncestor, IDescendant)
10-8
Interface references
Interface assignment-compatibility
A class type is assignment-compatible with any interface type implemented by the
class. An interface type is 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 IUnknown variable. A variant whose type code is varEmpty or varDispatch can be
assigned to an IDispatch variable.
Interface typecasts
Interface types follow the same rules as class types in variable and value typecasts.
Class-type expressions can be cast to interface typesfor example,
IMyInterface(SomeObject)provided the class implements the interface.
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
IUnknown. 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
Object interfaces
10-9
Automation objects
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 objects class implements
interface), the interface query returns an interface reference to object.
Automation objects
An object whose class implements the IDispatch interface (declared in the System unit)
is an Automation object.
10-10
Automation objects
Object interfaces
10-11
Automation objects
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.
Dual interfaces
A dual interface is an interface that supports both compile-time binding and runtime
binding through Automation. Dual interfaces must descend from IDispatch.
All methods of a dual interface (except from those inherited from IUnknown and
IDispatch) must use the safecall convention, and all method parameter and result
types must be automatable. (The automatable types are Byte, Currency, Real, Double,
Real48, Integer, Single, Smallint, AnsiString, ShortString, TDateTime, Variant,
OleVariant, and WordBool.)
10-12
Chapter
11
Memory management
Chapter11
This chapter explains how programs use memory and describes the internal formats
of Object Pascal data types.
Memory management
11-1
the combined size of all currently allocated memory blocks. Applications can use
these variables to display status information for debugging.
The System unit provides two procedures, GetMemoryManager and
SetMemoryManager, that allow applications to intercept low-level memory manager
calls. The System unit also provides a function called GetHeapStatus that returns a
record containing detailed memory-manager status information. For further
information about these routines, see the online Help.
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 applications 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.
An applications 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
(1M) respectively. An application is guaranteed to have the minimum stack size
available, and an applications stack is never allowed to grow larger than the
maximum stack size. If there is not enough memory available to satisfy an
applications minimum stack requirement, Windows will report an error upon
attempting to start the application.
If an 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.)
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 heapallocated, 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.
11-2
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
Memory management
11-3
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.
39
23
11
52
11-4
15
63
Pointer types
A Pointer type is stored in 4 bytes as a 32-bit address. The pointer value nil is stored
as zero.
Offset
Contents
32-bit reference-count
Memory management
11-5
Table 11.1
Offset
Contents
0..Length 1
character string
Length
NULL character
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.
Offset
Contents
0..Length 1
character string
Length
NULL character
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.
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)
11-6
Offset
Contents
32-bit reference-count
array elements
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. 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.
Table 11.4
Type
Alignment
Ordinal types
Real types
Array types
Record types
Memory management
11-7
Table 11.4
Type
Alignment
Set 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.
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.
File types
File types are represented as records. Typed files and untyped files occupy 332 bytes,
which are laid out as follows:
type
TFileRec = record
Handle: Integer;
Mode: Integer;
RecSize: Cardinal;
Private: array[1..28] of Byte;
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 = record
Handle: Integer;
Mode: Integer;
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;
11-8
=
=
=
=
$D7B0;
$D7B1;
$D7B2;
$D7B3;
where fmClosed indicates that the file is closed, fmInput and fmOutput indicate that the
file is a text file that has been reset (fmInput) or rewritten (fmOutput), and fmInOut
indicates that the file variable is a typed or an 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.
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
objects 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. VMTs are built automatically by the
compiler, and are never directly manipulated by a program. Pointers to VMTs, 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 pointersone per user-defined virtual method in
the class typein order of declaration. Each slot contains the address of the
Memory management
11-9
corresponding virtual methods entry point. This layout is compatible with a C++ vtable and with COM. At negative offsets, a VMT contains a number of fields that are
internal to Object Pascals implementation. Applications should use the methods
defined in TObject to query this information, since the layout is likely to change in
future implementations of Object Pascal.
Table 11.5
Offset
Type
Description
76
Pointer
72
Pointer
68
Pointer
64
Pointer
60
Pointer
56
Pointer
52
Pointer
48
Pointer
44
Pointer
40
Cardinal
36
Pointer
32
Pointer
28
Pointer
24
Pointer
20
Pointer
16
Pointer
12
Pointer
Pointer
Pointer
Pointer
Pointer
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 unit defines constants
and types for variants.
11-10
The TVarData type represents the internal structure of a Variant variable, which 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 to Delphi. Variants containing a varString value
should never be passed to a non-Delphi function. Delphis Automation support
automatically converts varString variants to varOleStr variants before passing them as
parameters to external functions.
Memory management
11-11
11-12
Chapter
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 routines calling convention. For information about calling
conventions, see Calling conventions on page 6-4.
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.
Program control
12-1
a call to Test passes A in EAX as a 32-bit integer, B in EDX as a pointer to a Char, and
D in ECX as a pointer to a long-string memory block; C and E are pushed onto the
stack as two double-words and a 32-bit pointer, in that order.
12-2
Function results
The following conventions are used for returning function result values.
Ordinal results are returned 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 coprocessors 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, or 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.
Program control
12-3
Exit procedures
Exit procedures
Exit procedures ensure that specific actionssuch as updating and closing filesare
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 programs
terminationwhether 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 finalization sections, rather than exit procedures, be used for
all exit behavior. (See The finalization section on page 3-5.) Exit procedures are
available only for .EXE or .DLL targets; 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 isnt 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.
12-4
Exit procedures
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.
An exit procedure can learn the cause of termination by examining the ExitCode
integer variable and the ErrorAddr pointer variable. In case of normal termination,
ExitCode is zero and ErrorAddr is nil. In case of termination through a call to Halt,
ExitCode contains the value passed to Halt and ErrorAddr is nil. In case of termination
due to a runtime error, ExitCode contains the error code and ErrorAddr contains the
address of the invalid statement.
The last exit procedure (the one installed by the runtime library) closes the Input and
Output files. If ErrorAddr is not nil, it outputs a runtime error message. To output
your own runtime error message, install an exit procedure that examines ErrorAddr
and outputs a message if its not nil; before returning, set ErrorAddr to nil so that the
error is not reported again by other exit procedures.
Once the runtime library has called all exit procedures, it returns to Windows,
passing the value stored in ExitCode as a return code.
Program control
12-5
12-6
Chapter
13
Inline assembler code
Chapter13
The built-in assembler allows you to write Intel assembler code within Object Pascal
programs. It implements a large subset of the syntax supported by Turbo Assembler
and Microsofts Macro Assembler, including all 8086/8087 and 80386/80387 opcodes
and all but a few of Turbo Assemblers expression operators. Moreover, the built-in
assembler allows you to use Object Pascal identifiers in assembler statements.
Except for DB, DW, and DD (define byte, word, and double word), none of Turbo
Assemblers directives (such as EQU, PROC, STRUC, SEGMENT, and MACRO) are
supported by the built-in assembler. Operations implemented through Turbo
Assembler directives, however, are largely matched by corresponding Object Pascal
constructions. For example, most EQU directives correspond to constant, variable,
and type declarations; the PROC directive corresponds to procedure and function
declarations; and the STRUC directive corresponds to record types.
As an alternative to the built-in assembler, you can link to .OBJ files that contain
external procedures and functions. See Linking to .OBJ files on page 6-6 for more
information.
13-1
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, BP points to the current stack frame, SP points to the top
of the stack, SS contains the segment address of the stack segment, and DS contains
the segment address of the data segment. Except for EDI, ESI, ESP, EBP, and EBX, an
asm statement can assume nothing about register contents on entry to the statement.
{ OK }
{ OK }
{ Error! }
{ Error! }
Labels
Labels are used in built-in assembler statements as they are in Object Pascalby
writing the label and a colon before a statement. There is no limit to a labels length,
but only the first 32 characters are significant. As in Object Pascal, labels must be
declared in a label declaration part in the block containing the asm statement. There
is one exception to this rule: 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 doesnt
have to be declared.
13-2
Instruction opcodes
The built-in assembler supports the following opcodes.
LOCK
REP
REPE
REPZ
REPNE
REPNZ
SEGES
SEGCS
SEGSS
SEGDS
SEGFS
SEGGS
ADC,mLeft
ADD,mLeft
AND,mLeft
AAA,mAX
AAS,mAX
AAD,mAX
AAM,mAX
BOUND,
mNONE
BSF,mLeft
BSR,mLeft
BT
BTC,mLeft
BTR,mLeft
BTS,mLeft
CALL,mNONE
CMP
CBW,mAX
CWDE,mAX
CWD,
<mAX,mDX>
CDQ,
<mAX,mDX>
CLC
CLD
CLI
CMC
CMPSB,
<mSIDI>
CMPSW,
<mSIDI>
CMPSD,
<mSIDI>
DAA,mAX
DAS,mAX
DEC,mLeft
DIV,mLeft
ENTER,
mNONE
HLT
IDIV,mLeft
IMUL,mLeft
IN,mLeft
INC,mLeft
INSB,mDI
INSW,mDI
INSD,mDI
INT
INTO
IRET
IRETD
JMP
JO
JNO
JC
JB
JNAE
JNC
JAE
JNB
JE
JZ
JNE
JNZ
JBE
JNA
JA
JNBE
JS
JNS
JP
JPE
JNP
JPO
JL
JNGE
JGE
JNL
JLE
JNG
JG
JNLE
JCXZ
JECXZ
LAHF,mAX
LEA,mLeft
LEAVE,
mNONE
LDS,mSpecial
LES,mSpecial
LFS,mSpecial
LGS,mSpecial
LSS,mSpecial
LODSB,
<mAX,mDI>
LODSW,
<mAX,mDI>
LODSD,
<mAX,mDI>
LOOP,mCX
LOOPE,mCX
LOOPZ,mCX
LOOPNE,mCX
LOOPNZ,mCX
LOOPD,mCX
LOOPDE,mCX
LOOPDZ,mCX
LOOPDNE,
mCX
LOOPDNZ,
mCX
MOV,mLeft
MOVSX,mLeft
MOVZX,mLeft
MOVSB,
<mSIDI>
MOVSW,
<mSIDI>
MOVSD,
<mSIDI>
MUL,mLeft
NEG,mLeft
NOP
NOT,mLeft
OR,mLeft
OUT
OUTSB,mSI
OUTSW,mSI
OUTSD,mSI
POP,mLeft
POPF
POPA,mSpecial
POPAD,
mSpecial
POPFD,
mSpecial
PUSH
PUSHF
PUSHA
PUSHAD
PUSHFD
RET
RETN
RETF
SUB,mLeft
SBB,mLeft
RCL,mLeft
RCR,mLeft
ROL,mLeft
ROR,mLeft
SAL,mLeft
SHL,mLeft
SAR,mLeft
SHR,mLeft
SHLD,mLeft
SHRD,mLeft
SAHF
SCASB,mDI
SCASW,mDI
SCASD,mDI
STC
13-3
STD
STI
STOSB,mDI
STOSW,mDI
STOSD,mDI
TEST
WAIT
XCHG,<mLeft,
mRight>
XLAT,mAX
XOR,mLeft
SETA,mLeft
SETAE,mLeft
SETB,mLeft
SETBE,mLeft
SETC,mLeft
SETE,mLeft
SETG,mLeft
SETGE,mLeft
SETL,mLeft
SETLE,mLeft
SETNA,mLeft
SETNAE,mLeft
SETNB,mLeft
SETNBE,mLeft
SETNC,mLeft
SETNE,mLeft
SETNG,mLeft
SETNGE,mLeft
SETNL,mLeft
SETNLE,mLeft
SETNO,mLeft
SETNP,mLeft
SETNS,mLeft
SETNZ,mLeft
SETO,mLeft
SETP,mLeft
SETPE,mLeft
SETPO,mLeft
SETS,mLeft
SETZ,mLeft
ARPL
LAR,mLeft
CLTS
LGDT
SGDT
LIDT
SIDT
LLDT
SLDT
LMSW
SMSW
LSL,mLeft
LTR,mLeft
STR,mLeft
VERR
VERW
BSWAP,mLeft
XADD,mLeft
CMPXCHG,
<mLeft,mAX>
INVD
WBINVD
INVLPG
FLD,m87
FILD,m87
FST,m87
FSTP,m87
FIST,m87
FISTP,m87
FADD,m87
FADDP,m87
FIADD,m87
FSUB,m87
FSUBP,m87
FSUBR,m87
FSUBRP,m87
FISUB,m87
FISUBR,m87
FMUL,m87
FMULP,m87
FIMUL,m87
FDIV,m87
FDIVP,m87
FDIVR,m87
FDIVRP,m87
FIDIV,m87
FIDIVR,m87
FCOM,m87
FCOMP,m87
FCOMPP,m87
FICOM,m87
FICOMP,m87
F2XM1,m87
FABS,m87
FBLD,m87
FBSTP,m87
FCHS,m87
FDECSTP,m87
FFREE,m87
FINCSTP,m87
FLD1,m87
FLDCW,m87
FLDENV,m87
FLDL2E,m87
FLDL2T,m87
FLDLG2,m87
FLDLN2,m87
FLDPI,m87
FLDZ,m87
FNOP,m87
FPREM,m87
FPATAN,m87
FPTAN,m87
FRNDINT,m87
FRSTOR,m87
FSCALE,m87
FSETPM,m87
FSQRT,m87
FTST,m87
FWAIT,m87
FXAM,m87
FXCH,m87
FXTRACT,m87
FYL2X,m87
FYL2XP1,m87
FCLEX,m87
FNCLEX,m87
FDISI,m87
FNDISI,m87
FENI,m87
FNENI,m87
FINIT,m87
FNINIT,m87
FSAVE,m87
FNSAVE,m87
FSTCW,m87
FNSTCW,m87
FSTENV,m87
FNSTENV,m87
FSTSW,m87
FNSTSW,m87
FUCOM,m87
FUCOMP,m87
FUCOMPP,m87
FPREM1,m87
FCOS,m87
FSIN,m87
FSINCOS,m87
13-4
(JMP), and to all conditional jump instructions when the target is a label (not a
procedure or function).
For an unconditional jump instruction (JMP), the built-in assembler generates a short
jump (one-byte opcode followed by a one-byte displacement) if the distance to the
target label is 128 to 127 bytes. Otherwise it generates a near jump (one-byte opcode
followed by a two-byte displacement).
For a conditional jump instruction, a short jump (one-byte opcode followed by a onebyte displacement) is generated if the distance to the target label is 128 to 127 bytes.
Otherwise, the built-in assembler generates a short jump with the inverse condition,
which jumps over a near jump to the target label (five bytes in total). For example, the
assembler statement
JC
Stop
where Stop isnt within reach of a short jump, is converted to a machine code
sequence that corresponds to this:
JNC
JMP
Skip:
Skip
Stop
Jumps to the entry points of procedures and functions are always near.
Assembler directives
The built-in assembler supports three assembler 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 pointerthat 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
pointerthat is, a word that contains the offset part of the address, followed by a
word that contains the segment part of the address.
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 assembler statements. To
generate uninitialized or initialized data in the data segment, you should use Object
Pascal var or const declarations.
13-5
0FFH
0,99
'A'
'Hello world...',0DH,0AH
12,"Delphi"
0FFFFH
0,9999
'A'
'BA'
MyVar
MyProc
0FFFFFFFFH
0,999999999
'A'
'DCBA'
MyVar
MyProc
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
One byte }
Two bytes }
Ord('A') }
String followed by CR/LF }
Object Pascal style string }
One word }
Two words }
Same as DB 'A',0 }
Same as DB 'A','B' }
Offset of MyVar }
Offset of MyProc }
One double-word }
Two double-words }
Same as DB 'A',0,0,0 }
Same as DB 'A','B','C','D' }
Pointer to MyVar }
Pointer to MyProc }
DB
DW
DD
?
?
?
MOV
MOV
MOV
AL,ByteVar
BX,WordVar
ECX,IntVar
The built-in assembler doesnt support such variable declarations. The only kind of
symbol that can be defined in an inline assembler statement is a label. All variables
must be declared using Object Pascal syntax; the preceding construction can be
replaced by
var
ByteVar: Byte;
WordVar: Word;
IntVar: Integer;
asm
MOV
AL,ByteVar
MOV
BX,WordVar
MOV
ECX,IntVar
end;
Operands
Built-in assembler operands are expressions that consist of constants, registers,
symbols, and operators.
13-6
Expressions
AH
BX
DI
EBX
ESP
OFFSET
SP
AL
BYTE
DL
ECX
FS
OR
SS
AND
CH
DS
EDI
GS
PTR
ST
AX
CL
DWORD
EDX
HIGH
QWORD
TBYTE
BH
CS
DX
EIP
LOW
SHL
TYPE
BL
CX
EAX
ES
MOD
SHR
WORD
BP
DH
EBP
ESI
NOT
SI
XOR
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 doesnt
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.
13-7
Expressions
Z,X+Y
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
ADD
MOV
end;
EAX,X
EAX,Y
Z,EAX
EAX,X+4
this code doesnt 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
ADD
end;
EAX,X
EAX,4
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.
13-8
Expressions
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'
"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.
Table 13.2
String
Value
'a'
00000061H
'ba'
00006261H
'cba'
00636261H
'dcba'
64636261H
'a '
00006120H
'
a'
20202061H
'a' * 2
000000E2H
'a'-'A'
00000020H
not 'a'
FFFFFF9EH
13-9
Expressions
Registers
The following reserved symbols denote CPU registers:.
Table 13.3
CPU registers
AX BX CX DX
AL BL CL DL
CS DS SS ES
FS GS
AH BH CH DH
ST
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 registersfor 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 Object Pascal identifiers in
assembler 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;
13-10
Expressions
The following table summarizes the kinds of symbol that can be used in asm statements.
Table 13.4
Symbol
Value
Class
Type
Label
Address of label
Memory reference
SHORT
Constant
Value of constant
Immediate value
Type
Memory reference
Size of type
Field
Offset of field
Memory
Size of type
Variable
Address of variable
Memory reference
Size of type
Procedure
Address of procedure
Memory reference
NEAR
Function
Address of function
Memory reference
NEAR
Unit
Immediate value
@Code
Memory reference
0FFF0H
@Data
Memory reference
0FFF0H
@Result
Memory reference
Size of type
EAX,Count
13-11
Expressions
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;
EAX,P.X
EDX,P.Y
ECX,R.A.X
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
MOV
MOV
MOV
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. Object Pascals
labels, variables, typed constants, procedures, and functions belong to this category.
Expressions that arent registers and arent associated with memory locations are
immediate values. This group includes Object Pascals 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
EBX,Count
MOV
ECX,[Start]
MOV
EDX,OFFSET Count
end;
13-12
{
{
{
{
MOV
MOV
MOV
MOV
EAX,xxxx }
EBX,[xxxx] }
ECX,[xxxx] }
EDX,xxxx }
Expressions
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
MOV
end;
EAX,OFFSET [Start]
EBX,[OFFSET Count]
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 typeor, 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
13-13
Expressions
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
enclosed in square brackets:
MOV
MOV
AL,[100H]
BX,[100H]
The built-in assembler permits both of these instructions, because the expression
[100H] has no typeit just means the contents of address 100H in the data
segment, and the type can be determined from the first operand (byte for AL, word
for BX). In cases where the type cant be determined from another operand, the builtin assembler requires an explicit typecast:
INC
IMUL
The following table summarizes the predefined type symbols that the built-in
assembler provides in addition to any currently declared Object Pascal types.
Table 13.5
Symbol
Type
BYTE
WORD
DWORD
QWORD
TBYTE
10
Expression operators
The built-in assembler provides a variety of operators. Precedence rules are different
from Object Pascal; for example, in an asm statement, AND has lower precedence
than the addition and subtraction operators. The following table lists the built-in
assemblers expression operators in decreasing order of precedence.
Table 13.6
Operators
Remarks
Precedence
highest
&
(), [], ., HIGH, LOW
+,
unary + and
:
OFFSET, SEG, TYPE, PTR, *, /, MOD,
SHL, SHR, +,
NOT, AND, OR, XOR
13-14
binary + and
lowest
Expressions
Operator
Description
&
(...)
[...]
Structure member selector. The result is the sum of the expression before the period
and the expression after the period, with the type of the expression after the period.
Symbols belonging to the scope identified by the expression before the period can be
accessed in the expression after the period.
HIGH
Returns the high-order 8 bits of the word-sized expression following the operator.
The expression must be an absolute immediate value.
LOW
Returns the low-order 8 bits of the word-sized expression following the operator. The
expression must be an absolute immediate value.
Unary plus. Returns the expression following the plus with no changes. The
expression must be an absolute immediate value.
Unary minus. Returns the negated value of the expression following the minus. The
expression must be an absolute immediate value.
Addition. The expressions can be immediate values or memory references, but only
one of the expressions can be a relocatable value. If one of the expressions is a
relocatable value, the result is also a relocatable value. If either of the expressions is a
memory reference, the result is also a memory reference.
Subtraction. The first expression can have any class, but the second expression must
be an absolute immediate value. The result has the same class as the first expression.
Segment override. Instructs the assembler that the expression after the colon belongs
to the segment given by the segment register name (CS, DS, SS, FS, GS, or ES) before
the colon. The result is a memory reference with the value of the expression after the
colon. When a segment override is used in an instruction operand, the instruction is
prefixed with an appropriate segment-override prefix instruction to ensure that the
indicated segment is selected.
OFFSET
Returns the offset part (double word) of the expression following the operator. The
result is an immediate value.
TYPE
Returns the type (size in bytes) of the expression following the operator. The type of
an immediate value is 0.
PTR
Typecast operator. The result is a memory reference with the value of the expression
following the operator and the type of the expression in front of the operator.
Multiplication. Both expressions must be absolute immediate values, and the result
is an absolute immediate value.
13-15
Table 13.7
Operator
Description
Integer division. Both expressions must be absolute immediate values, and the result
is an absolute immediate value.
MOD
SHL
Logical shift left. Both expressions must be absolute immediate values, and the result
is an absolute immediate value.
SHR
Logical shift right. Both expressions must be absolute immediate values, and the
result is an absolute immediate value.
NOT
Bitwise negation. The expression must be an absolute immediate value, and the
result is an absolute immediate value.
AND
Bitwise AND. Both expressions must be absolute immediate values, and the result is
an absolute immediate value.
OR
Bitwise OR. Both expressions must be absolute immediate values, and the result is an
absolute immediate value.
XOR
Bitwise exclusive OR. Both expressions must be absolute immediate values, and the
result is an absolute immediate value.
13-16
The automatically generated entry and exit code for the routine looks like this:
PUSH
MOV
SUB
MOV
POP
RET
EBP
EBP,ESP
ESP,Locals
ESP,EBP
EBP
Params
If locals include variants, long strings, or interfaces, they are initialized to zero but
not finalized.
Locals is the size of the local variables and Params is the size of the parameters. If
both Locals and Params are zero, there is no entry code, and the exit code consists
simply of a RET instruction.
Assembler functions return their results as follows.
Ordinal values are returned in AL (8-bit values), AX (16-bit values), or EAX (32-bit
values).
Real values are returned in ST(0) on the coprocessors register stack. (Currency
values are scaled by 10000.)
Pointers, including long strings, are returned in EAX.
Short strings and variants are returned in the temporary location pointed to by
@Result.
13-17
13-18
Appendix
A
Object Pascal grammar
Appendix A
ConstSection
TypeSection
VarSection
ExportedHeading
A-1
LabelDeclSection
ConstSection
TypeSection
VarSection
ProcedureDeclSection
TypeId
SimpleType
StrucType
PointerType
StringType
ProcedureType
VariantType
ClassRefType
REAL48
REAL
SINGLE
DOUBLE
EXTENDED
CURRENCY
COMP
A-2
SHORTINT
SMALLINT
INTEGER
BYTE
LONGINT
INT64
WORD
->
->
->
->
->
BOOLEAN
CHAR
WIDECHAR
LONGWORD
PCHAR
STRING
ANSISTRING
WIDESTRING
STRING '[' ConstExpr ']'
A-3
-> IN
-> IS
-> AS
AddOp ->
->
->
->
'+'
'-'
OR
XOR
MulOp ->
->
->
->
->
->
->
'*'
'/'
DIV
MOD
AND
SHL
SHR
CompoundStmt
ConditionalStmt
LoopStmt
WithStmt
A-4
CDECL
REGISTER
DYNAMIC
VIRTUAL
EXPORT
EXTERNAL
FAR
FORWARD
MESSAGE
OVERRIDE
OVERLOAD
PASCAL
REINTRODUCE
SAFECALL
STDCALL
ProcedureHeading
FunctionHeading
ConstructorHeading
DestructorHeading
A-5
A-6
Index
Symbols
- 4-4, 4-6, 4-9, 4-10
" 13-9
# 4-4
$ 4-4, 4-5
(*, *) 4-5
(, ) 4-2, 4-12, 4-14, 5-6, 5-41, 6-2,
6-3, 6-9, 7-2, 10-1
* 4-2, 4-6, 4-10
+ 4-4, 4-6, 4-9, 4-10
, 3-5, 4-23, 5-6, 5-20, 5-22, 6-9,
7-16, 9-4, 9-7, 9-8, 10-6, 13-2
. 3-2, 4-2, 4-13, 5-25, 9-7, 10-5
/ 4-2, 4-6
// 4-5
: 4-2, 4-17, 4-23, 5-20, 5-21, 5-36,
5-41, 6-3, 6-9, 7-16, 7-18, 7-27,
13-2
:= 4-17, 4-25
named parameters 10-12
; 3-2, 3-5, 4-16, 4-19, 4-24, 5-20,
5-21, 5-36, 6-2, 6-3, 6-9, 7-2, 7-6,
9-4, 9-7, 10-1, 10-3, 10-10
before else 4-22
< 4-2, 4-9, 4-10
<= 4-9, 4-10
<> 4-9, 4-10
= 4-2, 4-9, 4-10, 4-17, 5-35, 5-36,
5-38, 5-41, 6-9, 6-16, 10-5
> 4-2, 4-9, 4-10
>= 4-9, 4-10
@ 4-6, 4-11, 5-25, 5-29, 5-42, 7-16
@@ 5-29
@Result 13-10, 13-16
[, ] 4-13, 5-10, 5-11, 5-16, 5-30,
6-13, 6-18, 7-16, 7-18, 7-19, 10-1,
10-10
^ 4-6, 4-9, 5-18, 5-26
and variants 5-32
pointer overview 5-25
_ 4-2
{, } 4-5, 10-1, 10-10
3-6, 4-4, 6-7, 9-8, 10-1, 10-10
A
$A directive 11-7, 11-8
absolute (directive) 5-37
absolute addresses 5-37
absolute expressions
(assembler) 13-13
abstract methods 7-11
access specifiers 7-1, 7-16
array properties 7-18
Automation 7-6
calling convention 6-5, 7-17
index specifiers and 7-19
overloading 7-12, 7-17
overriding 7-21
actual parameters 6-17
Add method (TCollection) 7-9
addition 4-6
pointers 4-9
Addr function 5-25
_AddRef method 10-2, 10-5,
10-9
address operator 4-11, 5-25,
5-29, 5-42
properties and 7-16
alignment (data) 5-15, 11-7
See also internal data formats
AllocMemCount variable 11-1
AllocMemSize variable 11-1
alphanumeric characters 4-1,
4-2
ampersand See Symbols
ancestors 7-2, 7-3
and 4-7, 4-8
ANSI characters 5-4, 5-11, 5-12
AnsiChar type 5-4, 5-10, 5-12,
5-26, 11-3
AnsiString type 5-9, 5-11, 5-12,
5-14, 5-26
See also long strings
memory management 11-5
variant arrays and 5-32
Append procedure 8-2, 8-4, 8-5,
8-6
Application variable 2-5, 3-3
arithmetic operators 4-6, 5-4
array properties 7-5, 7-18
default 7-19
in dispatch interfaces 10-11
storage specifiers and 7-20
arrays 5-3, 5-16 to 5-20
array of const 6-15
accessing with
PByteArray 5-26
accessing with
PWordArray 5-26
B
$B directive 4-8
base types 5-7, 5-16, 5-17
Index
I-1
C
C++ 10-1, 11-10
calling conventions 5-28, 6-4,
12-1
access specifiers 6-5, 7-17
DLLs 9-3
interfaces 10-3, 10-6
methods 12-3
Cardinal type 5-3
caret See Symbols
I-2
D
data alignment 5-15, 11-7
data formats, internal 11-2 to
11-11
data types See types
DCC32.CFG 2-4
DCC32.EXE 2-4
.DCP files 2-3, 9-9
.DCU files 2-3, 3-1, 3-7, 9-8, 9-9
Dec procedure 5-3, 5-4
declarations 4-1, 4-16, 4-27
class 7-1, 7-7, 7-8, 7-16, 10-5
Index
I-3
E
E (in numerals) 4-4
else (reserved word) 4-21, 4-23,
7-27
empty set 5-16
end (reserved word) 3-2, 4-19,
4-23, 5-20, 5-21, 6-2, 6-3, 7-2,
7-27, 7-30, 9-7, 10-1, 10-10, 13-1
end-of-line character 4-1, 8-3
enumerated types 5-6, 11-3
Eof function 8-5
Eoln function 8-5
equality operator 4-10
error handling See exceptions
ErrorAddr variable 12-5
EStackOverflow exception 11-2
EVariantError exception 5-31
event handlers 2-7, 7-5
events 2-7, 7-5
example programs 2-3 to 2-7
except (reserved word) 7-27
ExceptAddr function 7-30
Exception class 7-25, 7-30
exception handlers 7-25, 7-27
identifiers in 7-28
ExceptionInformation
variable 9-6
exceptions 4-19, 7-13, 7-14, 7-25
to 7-30
constructors 7-26, 7-30
I-4
declaring 7-25
destroying 7-26, 7-27
DLLs 9-5, 9-6
file I/O 8-3
handling 7-26, 7-28, 7-30
in initialization section 7-26
nested 7-29
propagation 7-27, 7-29, 7-30
raising 7-25
re-raising 7-28
standard exceptions 7-30
standard routines 7-30
ExceptObject function 7-30
.EXE files 2-3
Exit procedure 6-1
exception handlers 7-27
in try...finally block 7-30
exit procedures 9-4, 12-4 to 12-5
packages and 12-4
ExitCode variable 9-4, 12-5
ExitProc variable 9-4, 9-5, 12-4
export (directive) 6-5
exports clause 4-27, 9-4
overloaded routines 9-4
expressions 4-1, 4-5
assembler 13-7 to 13-16
extended syntax 4-5
Extended type 4-7, 5-8, 5-9,
5-26, 11-5
external (directive) 6-6, 9-1, 9-2
F
False 5-5, 11-3
far (directive) 6-5
fields 5-20 to 5-23, 7-1, 7-7
See also records, classes
publishing 7-5
file (reserved word) 5-23
file I/O 8-1 to 8-6
exceptions 8-3
file variables 8-2
FilePos function 8-2
files
as parameters 6-10
file types 5-23, 8-2
generated 2-2, 2-3, 9-8, 9-9
initializing 5-37
memory 11-8
source code 2-1
text 8-2, 8-3
typed 5-23, 8-2
untyped 5-24, 8-2, 8-4
variants and 5-29
FileSize function 8-2
finalization section 3-3, 3-5, 12-4
G
-$G- compiler switch 9-10
$G directive 9-9
generic types 5-1
GetHeapStatus function 11-2
GetMem procedure 5-25, 5-37,
9-6, 11-1, 11-2
GetMemoryManager
procedure 11-2
GetProcAddress function 9-2
getter See read specifier
global identifiers 4-27
global variables 5-36
DLLs 9-5
interfaces 10-9
memory management 11-2
GlobalAlloc 11-1
globally unique identifiers See
GUIDs
goto statements 4-18
grammar (formal) A-1 to A-6
H
$H directive 5-10, 6-13
Halt procedure 12-4, 12-5
heading
program 2-1, 3-1, 3-2
routine 6-1
unit 3-3
heap memory 5-37, 11-2
Hello world! 2-3
HelpContext property 7-30
hexadecimal numerals 4-4
hiding class members 7-7, 7-11,
7-21
See also overloaded methods
reintroduce 7-11
hiding interface
implementations 10-6
High function 5-3, 5-4, 5-11,
5-17, 5-19, 6-14
HInstance variable 9-5
I
$I directive 8-3
IDE 1-1
See also Delphi
identifiers 4-1, 4-2, 4-3
global and local 4-27
in exception handlers 7-28
qualified 3-6
scope 4-27 to 4-28
IDispatch 10-9, 10-10
dual interfaces 10-12
if...then statements 4-21
nested 4-22
immediate values
(assembler) 13-12
implementation section 3-3, 3-4,
3-7
and forward declarations 6-6
methods 7-8
scope 4-28
uses clause 3-7
implements (directive) 7-21,
10-6
$IMPLICITBUILD directive 9-9
$IMPORTEDDATA
directive 9-9
importing routines from
DLLs 9-1
in (reserved word) 3-6, 4-10,
5-16, 5-32, 9-8
Integrated Translation
Environment 2-2
interface declarations 3-4
default paramters 6-17
interface section 3-3, 3-4, 3-7
forward declarations and 6-6
methods 7-8
scope 4-28
uses clause 3-7
interfaces 7-2, 10-1 to 10-12
accessing 10-8 to 10-10
Automation 10-10
calling conventions 10-3
compatibility 10-9
delegation 10-6
dispatch interface
types 10-10
dual interfaces 10-12
GUIDs 10-1, 10-2, 10-10
implementing 10-4 to 10-7
interface references 10-8 to
10-10
interface types 10-1 to 10-4
memory management 11-2
method resolution
clauses 10-5, 10-6
properties 10-1, 10-3, 10-6
querying 10-9
records and 5-22
typecasts 10-9
internal data formats 11-2 to
11-11
intersection (sets) 4-10
IntToHex function 5-4
IntToStr function 5-4
Invoke method 10-10
IOResult function 8-3, 8-4
is 4-11, 5-32, 7-23
IsLibrary variable 9-5
italics 1-2
IUnknown 10-2, 10-5, 10-9,
10-12
J
$J directive 5-40
Java 10-1
jump instructions
(assembler) 13-4
L
labels 4-1, 4-4, 4-18
assembler 13-2
-$LE- compiler switch 9-10
Length function 5-10, 5-17, 5-19
Index
I-5
M
$M directive 7-4, 7-5
main form 2-5 to 2-7
$MAXSTACKSIZE
directive 11-2
members See sets
members, of classes 7-1
interfaces 10-2
visibility 7-4
memory 4-1, 5-2, 5-24, 5-25,
5-30, 5-37, 7-14
DLLs 9-5
heap 5-37
management 11-1 to 11-11
overlays (in records) 5-22
shared memory manager 9-6
memory references
(assembler) 13-12
message (directive) 7-15
interfaces 10-6
message dispatching 7-15
message handlers 7-15
inherited 7-15
overriding 7-15
Message property 7-30
Messages unit 7-15, 7-16
metaclasses 7-22
method pointers 4-11, 5-27
I-6
N
name (directive) 6-7, 9-4
named parameters 10-12
names
See also identifiers
exported routines (DLLs) 9-4
functions 6-3, 6-4
identifiers 4-16
packages 9-8
programs 3-1, 3-2
units 3-3, 3-6
naming conflicts 3-6, 4-28
near (directive) 6-5
negation 4-7
bitwse 4-8
nested conditionals 4-22
nested exceptions 7-29
O
.OBJ files
calling routines in 6-6
Object Inspector (Delphi) 7-5
object interfaces See interfaces,
COM, CORBA
object types 7-3
objects 4-20, 7-1
See also classes
of object 5-27
comparison 4-11
files and 5-23
memory 11-9
of (reserved word) 4-23, 5-16,
5-17, 5-23, 5-27, 6-13, 6-15, 7-22
of object (method pointers) 5-27
OleAuto unit 7-5
OleVariant type 5-26, 5-33
on (reserved word) 7-27
opcodes (assembler) 13-2, 13-3
open array constructors 6-15,
6-18
open array parameters 6-13,
6-18
and dynamic arrays 6-14
Open function 8-4, 8-5
OpenString 6-13
operands 4-6
operators 4-6 to 4-12
assembler 13-14
class 7-23
precedence 4-12, 7-24
or 4-7, 4-8
Ord function 5-3, 5-4
ordinal types 5-2 to 5-8
ordinality 5-2
out (output) parameters 6-10,
6-11, 6-17
out (reserved word) 6-9, 6-10,
6-11
outer block 4-28
OutlineError 7-30
output (program
parameter) 3-2
output See file I/O
Output variable 8-3
overloaded methods 7-12
access specifiers 7-12, 7-17
publishing 7-5
overloaded procedures and
functions 6-6, 6-7
default parameters 6-8, 6-17
DLLs 9-4
forward declarations 6-8
overriding interface
implementations 10-5
overriding methods 7-10, 10-5
hiding and 7-11
overriding properties 7-20
access specifiers and 7-21
Automation 7-6
hiding and 7-21
P
$P directive 6-13
package files 2-1
packages 9-7 to 9-10
compiler directives 9-9
compiler switches 9-10
compiling 9-9
declaring 9-7
loading dynamically 9-7
loading statically 9-7
thread variables 9-8
uses clause and 9-7
packed (reserved word) 5-15,
11-7
packed arrays 4-5, 4-9, 5-17
packed records 11-8
packed strings 5-17
comparison 4-11
pairs of symbols 4-2
PAnsiChar type 5-12, 5-26
PAnsiString type 5-26
parameters 5-28, 6-2, 6-3, 6-9 to
6-17
See also overloaded
procedures and functions
actual 6-17
array 6-10, 6-13
Index
I-7
as parameters 7-16
declaring 7-16, 7-18
default 7-19, 10-2
interfaces 10-3
overriding 7-6, 7-20
read-only 7-18
write-only 7-18
protected class members 7-4,
7-5
prototypes 6-1
PShortString type 5-26
PString type 5-26
PTextBuf type 5-26
Ptr function 5-25
public class members 7-4, 7-5
public identifiers (interface
section) 3-4
published class members 7-4,
7-5
$M directive 7-5
restrictions 7-5
PVariant type 5-26
PVarRec type 5-26
PWideChar type 5-12, 5-13, 5-26
PWideString type 5-26
PWordArray type 5-26
Q
qualified identifiers 3-6, 4-2,
4-28, 5-21
in typecasts 4-14, 4-15
pointers 5-25
with Self 7-9
querying (interfaces) 10-9
QueryInterface method 10-2,
10-5, 10-10
quotation marks See Symbols
quoted strings 4-4, 5-42
assembler 13-9
QWORD type (assembler) 13-14
R
raise (reserved word) 4-19, 7-25,
7-27, 7-28
range-checking 5-4, 5-5, 5-8
Read procedure 8-2, 8-3, 8-4,
8-5, 8-6
read specifier 7-6, 7-16
array properties 7-18
index specifier and 7-19
object interfaces 10-1, 10-3,
10-6
overloading 7-12, 7-17
Readln procedure 8-5, 8-6
I-8
S
$S directive 11-2
safecall (calling convention) 6-4,
12-2
constructors and
destructors 12-4
dual interfaces 10-12
interfaces 10-3
Self 12-3
scope 4-27 to 4-28
classes 7-2
records 5-21
type identifiers 5-35
Seek procedure 8-2
SeekEof function 8-5
SeekEoln function 8-5
Self 7-9
calling conventions 12-3
class methods 7-24
semicolon See Symbols
separators 4-1, 4-5
SetLength procedure 5-10, 5-15,
5-17, 5-19, 6-14
SetMemoryManager
procedure 11-2
sets
empty 5-16
memory 11-6
operators 4-10
publishing 7-5
set constructors 4-13
modifying 8-1
scope 4-28
uses clause and 8-1
SysUtils unit 3-5, 5-26, 6-9, 6-15,
7-25, 7-26, 7-30
DLLs 9-6
uses clause and 8-1
T
$T directive 4-11
tag (records) 5-22
TAutoObject 7-5
TBYTE type (assembler) 13-14
TClass 7-3, 7-22, 7-23
TCollection 7-22
Add method 7-9
TCollectionItem 7-22
TDateTime 5-31
text files 8-2, 8-3
Text type 5-23, 8-3
TextBuf type 5-26
text-file device drivers 8-4
TextFile type 5-23
TGUID 10-3
then (reserved word) 4-21
thread variables 5-38
in packages 9-8
threadvar 5-38
TInterfacedObject 10-2, 10-4,
10-5
to (reserved word) 4-25
TObject 7-3, 7-15, 7-23
tokens 4-1
TPersistent 7-5
True 5-5, 11-3
true constants 5-38
Trunc function 5-4
try...except statements 4-19,
7-26
try...finally statements 4-19,
7-30
TTextRec type 5-26
Turbo Assembler 13-1, 13-6
TVarData 5-30, 11-11
TVarRec 5-26
TVarRec type 6-15
TWordArray 5-26
type identifiers 5-2
typecasts 4-14 to 4-15, 7-7
checked 7-24, 10-9
in constant declarations 5-39
interface 10-9
untyped parameters 6-12
variants 5-30
type-checking (objects) 7-23
Index
I-9
U
unary operators 4-6
Unassigned (variants) 5-30,
5-31, 5-32
underscores 4-2
Unicode 5-4, 5-12
union (sets) 4-10
UniqueString procedure 5-15
unit files 2-1, 3-1, 3-3
I-10
V
Val procedure 8-6
value parameters 6-10, 6-17,
12-1
open array constructors 6-18
value typecasts 4-14
var (reserved word) 5-36, 6-9,
6-10, 12-1
VarArrayCreate function 5-32
VarArrayDimCount
function 5-32
VarArrayHighBound
function 5-32
VarArrayLock function 5-32,
10-11
VarArrayLowBound
function 5-32
VarArrayOf function 5-32
VarArrayRedim function 5-32
VarArrayRef function 5-32
VarArrayUnlock
procedure 5-32, 10-11
VarAsType function 5-30
VarCast procedure 5-30
variable (var) parameters 6-10,
6-17, 12-1
variable parameters 6-17
variable typecasts 4-14
variables 4-6, 5-36 to 5-38
absolute addresses 5-37
declaring 5-36
dynamic 5-37
file 8-2
from DLLs 9-1
global 5-36, 10-9
heap-allocated 5-37
initializing 5-36, 5-37
local 5-36, 6-8
memory management 11-2
thread 5-38
W
$WEAKPACKAGEUNIT
directive 9-9
while statements 4-19, 4-24,
4-25
wide characters and
strings 5-12
memory management 11-2
standard routines 8-7
WideChar type 4-9, 5-4, 5-10,
5-12, 5-26, 11-3
WideCharLenToString
function 8-7
WideCharToString function 8-7
WideString type 5-9, 5-12,
5-26
memory management 11-6
Windows 2-2, 5-12, 5-31, 6-5,
7-15, 8-4
DLLs 9-1, 9-5
example application 2-5
memory management 11-1,
11-2
messages 7-15
variants and 11-11
Windows unit 9-2
with statements 4-19, 4-20, 5-21
X
$X directive 4-5
xor 4-7, 4-8
Y
Y2K issues 1-2
year 2000 issues 1-2
Z
-$Z- compiler switch 9-10
$Z directive 11-3
Index
I-11
I-12