Procedures and functions
Procedures and functions let you nest additional blocks in the main program block. Each procedure or function declaration has a heading followed by a block. See Chapter 7, “Blocks, locality, and scope,” for a definition of a block. A procedure is activated by a procedure statement; a function is activated by the evaluation of an expression that contains its call and returns a value to that expression.
This chapter discusses the different types of procedure and function declarations and their parameters.
Procedure declarations
A procedure declaration associates an identifier with a block as a procedure; that procedure can then be activated by a procedure statement.
procedure declaration
procedure heading
subroutine block
near ;
far export cdecl
block
external directive asm block forward
inline directive
The procedure heading names the procedure’s identifier and specifies the formal parameters (if any). The syntax for a formal parameter list is shown in the section “Parameters” on page 75.
A procedure is activated by a procedure statement, which states the procedure’s identifier and actual parameters, if any. The statements to be executed on activation are noted in the statement part of the procedure’s block. If the procedure’s identifier is used in a procedure statement within the procedure’s block, the procedure is executed recursively (it calls itself while executing).
Here’s an example of a procedure declaration:
procedure NumString(N: Integer; var S: string); var
V: Integer;
begin
V := Abs(N);
S := '';
repeat
S := Chr(N mod 10 + Ord('0')) + S; N := N div 10;
until N = 0;
if N < 0 then S := '-' + S;
end;
Near and far declarations
Object Pascal supports two procedure and function call models: near and far. In terms of code size and execution speed, the near call model is the more efficient, but near procedures and functions can only be called from within the module they are declared in. On the other hand, far procedures and functions can be called from any module, but the code for a far call is slightly less efficient.
The compiler automatically selects the correct call model based on a procedure’s or function’s declaration: Procedures and functions declared in the interface part of a unit use the far call model—they can be called from other modules. Procedures and functions declared in a program or in the implementation part of a unit use the near call model—they can only be called from within that program or unit.
For some purposes, a procedure or function may be required to use the far call model. For example, if a procedure or function is to be assigned to a procedural variable, it has to use the far call model. The $F compiler directive can be used to
override the compiler’s automatic call model selection. Procedures and functions compiled in the {$F+} state always use the far call model; in the {$F-} state, the compiler automatically selects the correct model. The default state is {$F-}.
To force a specific call model, a procedure or function declaration can optionally specify a near or far directive before the block—if such a directive is present, it overrides the setting of the $F compiler directive as well as the compiler’s automatic call model selection.
Export declarations
The export directive makes a procedure or function exportable by forcing the routine to use the far call model and generating special procedure entry and exit code.
Procedures and functions must be exportable in these cases:
-
Procedures and functions that are exported by a DLL (dynamic-link
library)
-
Callback procedures and functions in a Windows program
Chapter 12, “Dynamic-link libraries,” discusses how to export procedures and functions in a DLL. Even though a procedure or function is compiled with an export directive, the actual exporting of the procedure or function doesn’t occur until the routine is listed in a library’s exports clause.
Callback procedures and functions are routines in your application that are called by Windows and not by your application itself. Callback routines must be compiled with the export directive, but they don’t have to be listed in an exports clause. Here are some examples of common callback procedures and functions:
-
Window procedures
-
Dialog procedures
-
Enumeration callback procedures
-
Memory-notification procedures
-
Window-hook procedures (filters)
Object Pascal automatically generates smart callbacks for procedures and functions that are exported by a Windows program. Smart callbacks alleviate the need to use the MakeProcInstance and FreeProcInstance Windows API routines when creating callback routines. See “Entry and exit code” on page 176.
cdecl declarations
The cdecl directive specifies that a procedure or function should use C calling conventions. C calling conventions differ from Pascal calling conventions in that parameters are pushed on the stack in reverse order, and that the caller (as opposed to the callee) is responsible for removing the parameters from the stack after the call. The cdecl directive is useful for interfacing with dynamic-link libraries written in C
or C++, but for regular (non-imported) procedures and functions, Pascal calling conventions are more efficient.
Forward declarations
A procedure or function declaration that specifies the directive forward instead of a block is a forward declaration. Somewhere after this declaration, the procedure must be defined by a defining declaration. The defining declaration can omit the formal parameter list and the function result, or it can optionally repeat it. In the latter case, the defining declaration’s heading must match exactly the order, types, and names of parameters, and the type of the function result, if any.
No forward declarations are allowed in the interface part of a unit.
The forward declaration and the defining declaration must appear in the same procedure and function declaration part. Other procedures and functions can be declared between them, and they can call the forward-declared procedure.
Therefore, mutual recursion is possible.
The forward declaration and the defining declaration constitute a complete procedure or function declaration. The procedure or function is considered declared at the forward declaration.
This is an example of a forward declaration:
procedure Walter(M, N: Integer); forward;
procedure Clara(X, Y: Real);
begin
ƒ
Walter(4, 5); ƒ
end;
procedure Walter;
begin
ƒ
Clara(8.3, 2.4);
ƒ
end;
A procedure’s or function’s defining declaration can be an external or assembler declaration; however, it can’t be a near, far, export, interrupt, or inline declaration or another forward declaration.
External declarations
With external declarations, you can interface with separately compiled procedures and functions written in assembly language. They also allow you to import procedures and functions from DLLs.
external directive
string constant |
|
---|---|
integer constant |
|
External directives consisting only of the reserved word external are used in conjunction with {$L filename} directives to link with external procedures and functions implemented in .OBJ files. For more details about linking with assembly language, see Chapter 20.
These are examples of external procedure declarations:
procedure MoveWord(var Source, Dest; Count: Word); external; procedure MoveLong(var Source, Dest; Count: Word); external;
procedure FillWord(var Dest; Data: Integer; Count: Word); external; procedure FillLong(var Dest; Data: Longint; Count: Word); external;
{$L BLOCK.OBJ}
External directives that specify a dynamic-link library name (and optionally an import name or an import ordinal number) are used to import procedures and functions from dynamic-link libraries. For example, this external declaration imports a function called GlobalAlloc from the DLL called KERNEL (the Windows kernel):
function GlobalAlloc(Flags: Word; Bytes: Longint): THandle; far; external 'KERNEL' index 15;
To read more about importing procedures and functions from a DLL, see Chapter 12.
The external directive takes the place of the declaration and statement parts in an imported procedure or function. Imported procedures and functions must use the far call model selected by using a far procedure directive or a {$F+} compiler directive. Aside from this requirement, imported procedures and functions are just like regular procedures and functions.
Assembler declarations
With assembler declarations, you can write entire procedures and functions in inline assembly language.
asm block
For more details on assembler procedures and functions, see Chapter 19.
Inline declarations
The inline directive enables you to write machine code instructions in place of a block of Object Pascal code.
inline directive
See the inline statement syntax diagram on page 213.
When a normal procedure or function is called, the compiler generates code that pushes the procedure’s or function’s parameters onto the stack and then generates a CALL instruction to call the procedure or function. When you call an inline procedure or function, the compiler generates code from the inline directive instead of the CALL. Therefore, an inline procedure or function is expanded every time you refer to it, just like a macro in assembly language.
Here’s a short example of two inline procedures:
procedure DisableInterrupts; inline($FA); { CLI }
procedure EnableInterrupts; inline($FB); { STI }
Function declarations
A function declaration defines a block that computes and returns a value.
function declaration
function heading
result type
The function heading specifies the identifier for the function, the formal parameters (if any), and the function result type.
A function is activated by the evaluation of a function call. The function call gives the function’s identifier and actual parameters, if any, required by the function. A function call appears as an operand in an expression. When the expression is evaluated, the function is executed, and the value of the operand becomes the value returned by the function.
The statement part of the function’s block specifies the statements to be executed upon activation of the function. The block should contain at least one assignment statement that assigns a value to the function identifier. The result of the function is the last value assigned. If no such assignment statement exists or if it isn’t executed, the value returned by the function is undefined.
If the function’s identifier is used in a function call within the function’s block, the function is executed recursively.
Every function implicitly has a local variable Result of the same type as the function's return value. Assigning to Result has the same effect as assigning to the name of the function. In addition, however, you can refer to Result in an expression, which refers to the current return value rather than generating a recursive function call.
Functions can return any type, whether simple or complex, standard or user- defined, except old-style objects (as opposed to classes), and files of type text or file of. The only way to handle objects as function results is through object pointers.
Following are examples of function declarations:
function Max(A: Vector; N: Integer): Extended;
var
X: Extended;
I: Integer;
begin
X := A[1];
for I := 2 to N do
if X < A[I] then X := A[I]; Max := X;
end;
function Power(X: Extended; Y: Integer): Extended;
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;
Like procedures, functions can be declared as near, far, export, forward, external, assembler, or inline.
Parameters
The declaration of a procedure or function specifies a formal parameter list. Each parameter declared in a formal parameter list is local to the procedure or function being declared and can be referred to by its identifier in the block associated with the procedure or function.
formal parameter list
parameter declaration
There are four kinds of parameters: value, constant, variable, and untyped. These are characterized as follows:
-
A parameter group without a preceding var and followed by a type
is a list of value parameters.
-
A parameter group preceded by const and followed by a type is a
list of constant parameters.
-
A parameter group preceded by var and followed by a type is a
list of variable parameters.
-
A parameter group preceded by var or const and not
followed by a type is a list of untyped parameters.
String and array-type parameters can be open parameters. Open parameters are described on page 78. A variable parameter declared using the OpenString identifier, or using the string keyword in the {$P+} state, is an open-string parameter. A value, constant, or variable parameter declared using the syntax array of T is an open-array parameter.
Value parameters
A formal value parameter acts like a variable local to the procedure or function, except it gets its initial value from the corresponding actual parameter upon activation of the procedure or function. Changes made to a formal value parameter don’t affect the value of the actual parameter.
A value parameter’s corresponding actual parameter in a procedure statement or function call must be an expression, and its value must not be of file type or of any structured type that contains a file type.
The actual parameter must be assignment-compatible with the type of the formal value parameter. If the parameter type is string, then the formal parameter is given a size attribute of 255.
Constant parameters
A formal constant parameter acts like a local read-only variable that gets its value from the corresponding actual parameter upon activation of the procedure or function. Assignments to a formal constant parameter are not allowed, and likewise a formal constant parameter can’t be passed as an actual variable parameter to another procedure or function.
A constant parameter’s corresponding actual parameter in a procedure statement or function must follow the same rules as an actual value parameter.
In cases where a formal parameter never changes its value during the execution of a procedure or function, a constant parameter should be used instead of a value parameter. Constant parameters allow the implementor of a procedure or function to protect against accidental assignments to a formal parameter. Also, for structured- and string-type parameters, the compiler can generate more efficient code when constant parameters are used instead of value parameters.
Variable parameters
A variable parameter is used when a value must be passed from a procedure or function to the caller. The corresponding actual parameter in a procedure statement or function call must be a variable reference. The formal variable parameter represents the actual variable during the activation of the procedure or function, so any changes to the value of the formal variable parameter are reflected in the actual parameter.
Within the procedure or function, any reference to the formal variable parameter accesses the actual parameter itself. The type of the actual parameter must be identical to the type of the formal variable parameter (you can bypass this restriction through untyped parameters).
Note File types can be passed only as variable parameters.
The $P compiler directive controls the meaning of a variable parameter declared using the string keyword. In the default {$P+} state, string indicates that the parameter is an open-string parameter. In the {$P-} state, string corresponds to a string type with a size attribute of 255. See page 78 for information on open-string parameters.
If referencing an actual variable parameter involves indexing an array or finding the object of a pointer, these actions are executed before the activation of the procedure or function.
Untyped parameters
When a formal parameter is an untyped parameter, the corresponding actual parameter can be any variable or constant reference, regardless of its type. An untyped parameter declared using the var keyword can be modified, whereas an untyped parameter declared using the const keyword is read-only.
Within the procedure or function, the untyped parameter is typeless; that is, it is incompatible with variables of all other types, unless it is given a specific type through a variable typecast.
This is an example of untyped parameters:
function Equal(var Source, Dest; Size: Word): Boolean;
type
TBytes = array[0..65534] of Byte;
var
N: Word;
begin
N := 0;
while (N < Size) and (TBytes(Dest)[N] = TBytes(Source)[N]) do
Inc(N);
Equal := N = Size;
end;
This function can be used to compare any two variables of any size. For example, given these declarations,
type
TVector = array[1..10] of Integer; TPoint = record
X, Y: Integer;
end; var
Vec1, Vec2: TVector;
N: Integer;
P: TPoint;
the function then calls
Equal(Vec1, Vec2, SizeOf(TVector)) Equal(Vec1, Vec2, SizeOf(Integer) * N) Equal(Vec[1], Vec1[6], SizeOf(Integer) * 5) Equal(Vec1[1], P, 4)
which compares Vec1 to Vec2, the first N components of Vec1 to the first N components of Vec2, the first five components of Vec1 to the last five components of Vec1, and Vec1[1] to P.X and Vec1[2] to P.Y.
While untyped parameters give you greater flexibility, they can be riskier to use. The compiler can’t verify that operations on untyped variables are valid.
Open parameters
Open parameters allow strings and arrays of varying sizes to be passed to the same procedure or function.
Open-string parameters
Open-string parameters can be declared in two ways:
-
Using the string keyword in the {$P+} state
-
Using the OpenString identifier
By default, parameters declared with the string keyword are open-string parameters. If, for reasons of backward compatibility, a procedure or function is compiled in the {$P-} state, the OpenString identifier can be used to declare open- string parameters. OpenString is declared in the System unit and denotes a special string type that can only be used in the declaration of string parameters. OpenString is not a reserved word; therefore, OpenString can be redeclared as a user-defined identifier.
For an open-string parameter, the actual parameter can be a variable of any string type. Within the procedure or function, the size attribute (maximum length) of the formal parameter will be the same as that of the actual parameter.
Open-string parameters behave exactly as variable parameters of a string type, except that they can’t be passed as regular variable parameters to other procedures and functions. They can, however, be passed as open-string parameters again.
In this example, the S parameter of the AssignStr procedure is an open-string parameter:
procedure AssignStr(var S: OpenString);
begin
S := '0123456789ABCDEF';
end;
Because S is an open-string parameter, variables of any string type can be passed to
AssignStr:
var
S1: string[10]; S2: string[20];
begin
AssignStr(S1); { S1 = '0123456789' } AssignStr(S2); { S2 = '0123456789ABCDEF' }
end;
Within AssignStr, the maximum length of the S parameter is the same as that of the actual parameter. Therefore, in the first call to AssignStr, the assignment to the S parameter truncates the string because the declared maximum length of S1 is 10.
When applied to an open-string parameter, the Low standard function returns zero, the High standard function returns the declared maximum length of the actual parameter, and the SizeOf function returns the size of the actual parameter.
In the next example, the FillString procedure fills a string to its maximum length with a given character. Notice the use of the High standard function to obtain the maximum length of an open-string parameter.
procedure FillString(var S: OpenString; Ch: Char);
begin
S[0] := Chr(High(S)); { Set string length } FillChar(S[1], High(S), Ch); { Set string characters }
end;
Note Value and constant parameters declared using the OpenString identifier or the string keyword in the {$P+} state are not open-string parameters. Instead, such parameters behave as if they were declared using a string type with a maximum length of 255 and the High standard function always returns 255 for such parameters.
Open-array parameters
A formal parameter declared using the syntax
array of T
is an open-array parameter. T must be a type identifier, and the actual parameter must be a variable of type T, or an array variable whose element type is T. Within the procedure or function, the formal parameter behaves as if it was declared as
array[0..N - 1] of T
where N is the number of elements in the actual parameter. In effect, the index range of the actual parameter is mapped onto the integers 0 to N - 1. If the actual parameter is a simple variable of type T, it is treated as an array with one element of type T.
A formal open-array parameter can be accessed by element only. Assignments to an entire open array aren’t allowed, and an open array can be passed to other procedures and functions only as an open-array parameter or as an untyped variable parameter.
Open-array parameters can be value, constant, and variable parameters and have the same semantics as regular value, constant, and variable parameters. In particular, assignments to elements of a formal open array constant parameter are not allowed, and assignments to elements of a formal open array value parameter don’t affect the actual parameter.
Note For an open array value parameter, the compiler creates a local copy of the actual parameter within the procedure or function’s stack frame. Therefore, be careful not to overflow the stack when passing large arrays as open array value parameters.
When applied to an open-array parameter, the Low standard function returns zero, the High standard function returns the index of the last element in the actual array parameter, and the SizeOf function returns the size of the actual array parameter.
The Clear procedure in the next example assigns zero to each element of an array of Double, and the Sum function computes the sum of all elements in an array of Double. Because the A parameter in both cases is an open-array parameter, the subroutines can operate on any array with an element type of Double.
procedure Clear(var A: array of Double);
var
I: Word;
begin
for I := 0 to High(A) do A[I] := 0;
end;
function Sum(const A: array of Double): Double;
var
I: Word;
S: Double;
begin
S := 0;
for I := 0 to High(A) do S := S + A[I]; Sum := S;
end;
When the element type of an open-array parameter is Char, the actual parameter may be a string constant. For example, given the procedure declaration,
procedure PrintStr(const S: array of Char);
var
I: Integer;
begin
for I := 0 to High(S) do
if S[I] <> #0 then Write(S[I]) else Break;
end;
the following are valid procedure statements:
PrintStr('Hello world'); PrintStr('A');
When passed as an open-character array, an empty string is converted to a string with one element containing a NULL character, so the statement PrintStr(‘‘) is identical to the statement PrintStr(#0).
Open-array constructors
Open-array constructors allow open-array parameters to be constructed directly within procedure and function calls. When a formal parameter of a procedure or function is an open-array value parameter or an open-array constant parameter, the corresponding actual parameter in a procedure or function call can be an open-array constructor.
open array constructor
An open-array constructor consists of one or more expressions separated by commas and enclosed in square brackets. Each expression must be assignment compatible with the element type of the open-array parameter. The use of an open- array constructor corresponds to creating a temporary array variable, and initializing the elements of the temporary array with the values given by the list of expressions. For example, given the declaration of the Sum function above, the statement
X := Sum([A, 3.14159, B + C]);
corresponds to
Temp[0] := A;
Temp[1] := 3.14159;
Temp[2] := B + C;
X := Sum(Temp);
where Temp is a temporary array variable with three elements of type Double.
Type variant open-array parameters
A type variant open-array parameter allows an open array of expressions of varying types to be passed to a procedure or function. A type variant open-array parameter is declared using the syntax
array of const
The array of const syntax is analogous to array of TVarRec. The TVarRec type is a variant record type which can represent values of integer, boolean, character, real, string, pointer, class, and class reference types. The TVarRec type is declared in the System unit as follows
type
TVarRec = record case VType: Byte of
vtInteger: (VInteger: Longint); vtBoolean: (VBoolean: Boolean); vtChar: (VChar: Char); vtExtended: (VExtended: PExtended); vtString: (VString: PString); vtPointer: (VPointer: Pointer); vtPChar: (VPChar: PChar); vtObject: (VObject: TObject); vtClass: (VClass: TClass);
end;
The VType field determines which value field is currently defined. Notice that for real or string values, the VExtended or VString field contains a pointer to the value rather than the value itself. The vtXXXX value type constants are also declared in the System unit:
const
vtInteger = 0;
vtBoolean = 1;
vtChar = 2;
vtExtended = 3;
vtString = 4;
vtPointer = 5;
vtPChar = 6;
vtObject = 7;
vtClass = 8;
The MakeStr function below takes a type variant open-array parameter and returns a string that is the concatenation of the string representations of the arguments. The AppendStr, IntToStr, FloatToStr, and StrPas functions used by MakeStr are defined in the SysUtils unit.
function MakeStr(const Args: array of const): string; const
BoolChars: array[Boolean] of Char = ('F', 'T');
var
I: Integer;
begin
Result := '';
for I := 0 to High(Args) do with Args[I] do
case VType of
vtInteger: AppendStr(Result, IntToStr(VInteger)); vtBoolean: AppendStr(Result, BoolChars[VBoolean]); vtChar: AppendStr(Result, VChar);
vtExtended: AppendStr(Result, FloatToStr(VExtended^)); vtString: AppendStr(Result, VString^);
vtPChar: AppendStr(Result, StrPas(VPChar)); vtObject: AppendStr(Result, VObject.ClassName); vtClass: AppendStr(Result, VClass.ClassName);
end;
end;
When a formal parameter of a procedure or function is a type variant open-array value parameter or a type variant open-array constant parameter, the corresponding actual parameter in a procedure or function call can be an open-array constructor.
The use of an open-array constructor for a type variant open-array parameter creates a temporary array of TVarRec and initializes the elements according to the type and value of each expression listed in the open-array constructor. For example, given the above function MakeStr, the statement
S := MakeStr(['Test ', 100, '-', True, '-', 3.14159]);
produces the following string
'Test 100-T-3.14159'
The following table lists the possible expression types in a type variant open-array constructor, and the corresponding value type codes.
Table 8-1 Type variant open-array expressions
vtInteger Any integer type
vtBoolean Any boolean type
vtChar Any character type
vtExtended Any real type
vtString Any string type
vtPointer Any pointer type except PChar
vtPChar PChar or array[0..X] of Char
vtObject Any class type
vtClass Any class reference type
C h a p t e r