Range check error delphi 7

range check error delphi 7

error on conversion to the left hand side. program Demo;. {$APPTYPE CONSOLE}. {$Q+,R+} { Turn on overflow and range checking }. uses SysUtils;. If a range check fails, an ERangeError exception is raised (or the program is terminated if exception handling is not enabled). So the reason. byte array when it is empty - it's a range check error and unneeded Changed: Fixed up compiler defines so that we work on Delphi 5, Delphi 7.

Range check error delphi 7 - remarkable, this

Delphi Reference

Delphi Pascal supports several extensions to the standard Pascal data types. Like any Pascal language, Delphi supports enumerations, sets, arrays, integer and enumerated subranges, records, and variant records. If you are accustomed to C or C++, make sure you understand these standard Pascal types, because they can save you time and headache. The differences include the following:

  • Instead of bit masks, sets are usually easier to read.
  • You can use pointers instead of arrays, but arrays are easier and offer bounds-checking.
  • Records are the equivalent of structures, and variant records are like unions.

8 Chapter 1 - Delphi Pascal

Integer Types

The basic integer type is Integer. The Integer type represents the natural size of an integer, given the operating system and platform. Currently, Integer represents a 32-bit integer, but you must not rely on that. The future undoubtedly holds a 64-bit operating system running on 64-bit hardware, and calling for a 64-bit Integer type. To help cope with future changes, Delphi defines some types whose size depends on the natural integer size and other types whose sizes are fixed for all future versions of Delphi. Table 1-2 lists the standard integer types. The types marked with natural size might change in future versions of Delphi, which means the range will also change. The other types will always have the size and range shown.

Real Types

Delphi has several floating-point types. The basic types are Single, Double, and Extended Single and Double correspond to the standard sizes for the IEEE-754 standard, which is the basis for floating-point hardware on Intel platforms and in Windows. Extended is the Intel extended precision format, which conforms to the minimum requirements of the IEEE-754 standard for extended double precision. Delphi defines the standard Pascal Real type as a synonym for Double. See the descriptions of each type in Chapter 5 for details about representation.

The floating-point hardware uses the full precision of the Extended type for its computations, but that doesn't mean you should use Extended to store numbers. Extended takes up 10 bytes, but the Double type is only 8 bytes and is more efficient to move into and out of the floating-point unit. In most cases, you will get better performance and adequate precision by using Double.

Errors in floating-point arithmetic, such as dividing by zero, result in runtime errors. Most Delphi applications use the SysUtils unit, which maps runtime errors into exceptions, so you will usually receive a floating-point exception for such errors. Read more about exceptions and errors in "Exception Handling," later in this chapter.

The floating-point types also have representations for infinity and not-a-number (NaN). These special values don't arise normally unless you set the floating-point control word. You can read more about infinity and NaN in the IEEE-754 standard, which is available for purchase from the IEEE. Read about the floating-point control word in Intel's architecture manuals, especially the Pentium Developer's Manual, volume 3, Architecture and Programming Manual. Intel's manuals are available online at http://developer.intel.com/design/processor/

Delphi also has a fixed-point type, Currency This type represents numbers with four decimal places in the range -922,337,203,685,477 5808 to 922,337,203,685,477. 5807, which is enough to store the gross income for the entire planet, accurate to a hundredth of a cent. The Currency type employs the floating-point processor, using 64 bits of precision in two's complement form. Because Currency is a floating-point type, you cannot use any integer operators (such as bit shifting or masking).

The floating-point unit (FPU) can perform calculations in single-precision, double-precision, or extended-precision mode. Delphi sets the FPU to extended precision, which provides full support for the Extended and Currency types. Some Windows API functions, however, change the FPU to double precision. At double precision, the FPU maintains only 53 bits of precision instead of 64.

When the FPU uses double precision, you have no reason to use Extended values, which is another reason to use Double for most computations. A bigger problem is the Currency type. You can try to track down exactly which functions change the FPU control word and reset the precision to extended precision after the errant functions return. (See the Set8087CW function in Chapter 5 ) Another solution is to use the Int64 type instead of Currency, and implement your own fixed-point scaling in the manner shown in Example 1-6.

Example 1-6: Using Int64 to Store Currency Values resourcestring slnvalidCurrency = 'Invalid Currency string: ''%s'''; const

Currency64Decimals =4; // number of fixed decimal places Currency64Scale = 10000; // 10**Decimal64Decimals type

Currency64 = type Int64;

function Currency64ToString(Value: Currency64): string; begin

Result := Format('%d%s%.4d', [Value div Currency64Scale, DecimalSeparator, Abs(Value mod Currency64Scale)]);

end;

10 Chapter 1 - Delphi Pascal

Example 1-6: Using Int64 to Store Currency Values (continued)

function StringToCurrency64(const Str: string): Currency64; var

Code: Integer; Fraction: Integer;

FractionString: string[Currency64Decimals]; I: Integer; begin

// Convert the integer part and scale by Currency64Scale

Result := Result * Currency64Scale;

if Code = 0 then

  • integer part only in Str Exit else if Str[Code] = DecimalSeparator then begin
  • The user might specify more or fewer than 4 decimal points, // but at most 4 places are meaningful. FractionString := Copy(Str, Code+1, Currency64Deciraals); // Pad missing digits with zeros.

for I := Length(FractionString)+1 to Currency64Decimals do

FractionString[I] := '0'; SetLength(FractionString, Currency64Decimals);

// Convert the fractional part and add it to the result. Val(FractionString, Fraction, Code); if Code = 0 then begin if Result < 0 then

Result := Result - Fraction else

// The string is not a valid currency string (signed, fixed point // number).

raise EConvertError.CreateFmt(sInvalidCurrency, [Str]); end;

Arrays

In additional to standard Pascal arrays, Delphi defines several extensions for use in special circumstances. Dynamic arrays are arrays whose size can change at runtime. Open arrays are array parameters that can accept any size array as actual arguments. A special case of open arrays lets you pass an array of heterogeneous types as an argument to a routine. Delphi does not support conformant arrays, as found in ISO standard Pascal, but open arrays offer the same functionality

Dynamic arrays

A dynamic array is an array whose size is determined at runtime. You can make a dynamic array grow or shrink while the program runs. Declare a dynamic array without an index type. The index is always an integer, and always starts at zero. At runtime you can change the size of a dynamic array with the SetLength procedure. Assignment of a dynamic array assigns a reference to the same array Unlike strings, dynamic arrays do not use copy-on-write, so changing an element of a dynamic array affects all references to that array Delphi manages dynamic arrays using reference counting so when an array goes out of scope, its memory is automatically freed. Example 1-7 shows how to declare and use a dynamic array

Example 1- 7/ Using a Dynamic Array var

I: Integer;

Data: array of Double; // Dynamic array storing Double values F: TextFile; // Read data from this file

Value: Double; begin

while not Eof(F) do begin

ReadLn(F, Value);

// Inefficient, but simple way to grow a dynamic array. In a real // program, you should increase the array size in larger chunks, // not one element at a time. SetLength(Data, Length(Data) + 1); Data[Length(Data)] := Value; end;

CloseFile(F); end;

Delphi checks array indices to make sure they are in bounds. (Assuming you have not disabled range checks; see the $R directive in Chapter 8.) Empty dynamic arrays are an exception. Delphi represents an empty dynamic array as a nil pointer. If you attempt to access an element of an empty dynamic array, Delphi dereferences the nil pointer, resulting in an access violation, not a range check error.

Open arrays

You can declare a parameter to a function or procedure as an open array. When calling the routine, you can pass any size array (with the same base type) as an argument. The routine should use the Low and High functions to determine the bounds of the array (Delphi always uses zero as the lower bound, but the Low and High, functions tell the maintainer of your code exactly what the code is

12 Chapter 1 - Delphi Pascal doing. Hard-coding 0 is less clear.) Be sure to declare the parameter as const if the routine does not need to modify the array, or as var if the routine modifies the array contents.

The declaration for an open array argument looks like the declaration for a dynamic array, which can cause some confusion. When used as a parameter, an array declaration without an index type is an open array When used to declare a local or global variable, a field in a class, or a new type, an array declaration without an index means a dynamic array.

You can pass a dynamic array to a routine that declares its argument as an open array, and the routine can access the elements of the dynamic array, but cannot change the array's size. Because open arrays and dynamic arrays are declared identically, the- only way to declare a parameter as a dynamic array is to declare a new type identifier for the dynamic array type, as shown below procedure CantGrow(var Data: array of integer); begin

// Data is an open array, so it cannot change size, end;

type

TArrayOflnteger = array of integer; // dynamic array type procedure Grow (var Data: TArrayOflnteger); begin

// Data is a dynamic array, so it can change size. SetLength(Data, Length(Data) + 1); end;

You can pass a dynamic array to the CantGrow procedure, but the array is passed as an open array, not as a dynamic array The procedure can access or change the elements of the array, but it cannot change the size of the array

If you must call a Delphi function from another language, you can pass an open array argument as a pointer to the first element of the array and the array length minus one as a separate 32-bit integer argument. In other words, the lower bound for the array index is always zero, and the second parameter is the upper bound.

You can also create an open array argument by enclosing a series of values in square brackets. The open array expression can be used only as an open array argument, so you cannot assign such a value to an array-type variable. You cannot use this construct for a var open array. Creating an open array on the fly is a convenient shortcut, avoiding the need to declare a const array:

The Slice function is another way to pass an array to a function or procedure. Slice lets you pass part of an array to a routine. Chapter 5 describes Slice in detail.

Type variant open arrays

Another kind of open array parameter is the type variant open array, or array of const. A variant open array lets you pass a heterogeneous array, that is, an array where each element of the array can have a different type. For each array element,

Delphi creates a TVarRec record, which stores the element's type and value. The array of TVarRec records is passed to the routine as a const open array The routine can examine the type of each element of the array by checking the VType member of each TVarRec record. Type variant open arrays give you a way to pass a variable size argument list to a routine in a type-safe manner.

TVarRec is a variant record similar to a Variant, but implemented differently Unlike a Variant, you can pass an object reference using TVarRec. Chapter 6, System Constants, lists all the types that TVarRec supports. Example 1-8 shows a simple example of a routine that converts a type variant open array to a string.

Example 1-8: Converting Type Variant Data to a String function AsString(const Args: array of const): string; var

I: Integer; S: String; begin

for I := Low(Args) to High(Args) do begin case Args[I].VType of vtAnsiString:

S := PChar(Args[I].VAnsiString); vtBoolean:

if Args[I].VBoolean then

S := Args[I].VClass.ClassName; vtCurrency:

S := FloatToStr(Args[I].VCurrencyA); vtExtended:

S := FloatToStr(Args[I] ,VExtendedA); Vtlnt64:

S := IntToStr(Args[I].VInt64A); vtlnteger:

S := IntToStr(Args[I].VInteger); vtlnterface:

S := Args[I].VObject.ClassName; VtPChar:

S := Format('%p', [Args[I].VPointer]); vtPWideChar:

S := Args[I].VFWideChar; vtString:

14 Chapter 1 - Delphi Pascal

Example 1-8: Converting Type Variant Data to a String (continued)

vtVariant:

S := Args[I].VWideChar; vtWideString:

S := WideString(Args[I].VWideString); else raise Exception.CreateFmt('Unsupported VType=%d',

end;

Strings

Delphi has four kinds of strings: short, long, wide, and zero-terminated. A short string is a counted array of characters, with up to 255 characters in the string. Short strings are not used much in Delphi programs, but if you know a string will have fewer than 255 characters, short strings incur less overhead than long strings.

Long strings can be any size, and the size can change at runtime. Delphi uses a copy-on-write system to minimize copying when you pass strings as arguments to routines or assign them to variables. Delphi maintains a reference count to free the memory for a string automatically when the string is no longer used.

Wide strings are also dynamically allocated and managed, but they do not use reference counting. When you assign a wide string to a WideString variable, Delphi copies the entire string.

Delphi checks string references the same way it checks dynamic array references, that is, Delphi checks subscripts to see if they are in range, but an empty long or wide string is represented by a nil pointer. Testing the bounds of an empty long or wide string, therefore, results in an access violation instead of a range check error.

A zero-terminated string is an array of characters, indexed by an integer starting from zero. The string does not store a size, but uses a zero-valued character to mark the end of the string. The Windows API uses zero-terminated strings, but you should not use them for other purposes. Without an explicit size, you lose the benefit of bounds checking, and performance suffers because some operations require two passes over the string contents or must process the string contents more slowly, always checking for the terminating zero value. Delphi will also treat a pointer to such an array as a string.

For your convenience, Delphi stores a zero value at the end of long and wide strings, so you can easily cast a long string to the type PAnsiChar, PChar, or PWideChar to obtain a pointer to a zero-terminated string. Delphi's PChar type is the equivalent of char* in C or C++

String literals

You can write a string literal in the standard Pascal way, or use a pound sign (#) followed by an integer to specify a character by value, or use a caret (A) followed by a letter to specify a control character. You can mix any kind of string to form a single literal, for example:

'Normal string: '#13#10'Next line (after CR-LF),AI'That was a ''TAB'''

The caret (A) character toggles the sixth bit ($40) of the character's value, which changes an upper case letter to its control character equivalent. If the character is lowercase, the caret clears the fifth and sixth bits ($60). This means you can apply the caret to nonalphabetic characters. For example, is the same as ' r1 because ' 2' has the ordinal value $32, and toggling the $40 bit makes it $72, which is the ordinal value for ' r'. Delphi applies the same rules to every character, so you can use the caret before a space, tab, or return, with the result that your code will be completely unreadable.

Mixing string types

You can freely mix all different kinds of strings, and Delphi does its best to make sense out of what you are trying to do. You can concatenate different kinds of strings, and Delphi will narrow a wide string or widen a narrow string as needed. To pass a string to a function that expects a PChar parameter, just cast a long string to PChar. A short string does not automatically have a zero byte at the end, so you need to make a temporary copy, append a #0 byte, and take the address of the first character to get a PChar value.

Unicode and multibyte strings

Delphi supports Unicode with its WideChar, WideString and FWideChar types. All the usual string operations work for wide strings and narrow (long or short) strings. You can assign a narrow string to a WideString variable, and Delphi automatically converts the string to Unicode. When you assign a wide string to a long (narrow) string, Delphi uses the ANSI code page to map Unicode characters to multibyte characters.

A multibyte string is a string where a single character might occupy more than one byte. (The Windows term for a multibyte character set is double-byte character set.) Some national languages (e.g., Japanese and Chinese) use character sets that are much larger than the 256 characters in the ANSI character set. Multibyte character sets use one or two bytes to represent a character, allowing many more characters to be represented. In a multibyte string, a byte can be a single character, a lead byte (that is, the first byte of a multibyte character), or a trailing byte (the second byte of a multibyte character). Whenever you examine a string one character at a time, you should make sure that you test for multibyte characters because the character that looks like, say, the letter "A" might actually be the trailing byte of an entirely different character.

Ironically, some of Delphi's string handling functions do not handle multibyte strings correctly Instead, the SysUtils unit has numerous string functions that work correctly with multibyte strings. Handling multibyte strings is especially impor-

16 Chapter 1 - Delphi Pascal tant for filenames, and the SysUtils unit has special functions for working with multibyte characters in filenames. See Appendix B, The SysUtils Unit, for details.

Windows NT and Windows 2000 support narrow and wide versions of most API functions. Delphi defaults to the narrow versions, but you can call the wide functions just as easily For example, you can call CreateFileW to create a file with a Unicode filename, or you can call CreateFileA to create a file with an ANSI filename. CreateFile is the same as CreateFileA. Delphi's VCL uses the narrow versions of the Windows controls, to maintain compatibility with all versions of Windows. (Windows 95 and 98 do not support most Unicode controls.)

Boolean Types

Delphi has the usual Pascal Boolean type, but it also has several other types that make it easier to work with the Windows API. Numerous API and other functions written in C or C++ return values that are Boolean in nature, but are documented as returning an integer. In C and C++, any non-zero value is considered True, so Delphi defines the LongBool, WordBool, and ByteBool values with the same semantics.

For example, if you must call a function that was written in C, and the function returns a Boolean result as a short integer, you can declare the function with the WordBool return type and call the function as you would any other Boolean-type function in Pascal:

function SomeCFunc: WordBool; external "TheCDll.dll';

if SomeCFunc then ...

It doesn't matter what numeric value SomeCFunc actually returns; Delphi will treat zero as False and any other value as True. You can use any of the C-like logical types the same way you would the native Delphi Boolean type. The semantics are identical. For pure Delphi code, you should always use Boolean.

Variants

Delphi supports OLE variant types, which makes it easy to write an OLE automation client or server. You can use Variants in any other situation where you want a variable whose type can change at runtime. A Variant can be an array, a string, a number, or even an IDispatch interface. You can use the Variant type or the OleVariant type. The difference is that an OleVariant takes only COM-compat-ible types, in particular, all strings are converted to wide strings. Unless the distinction is important, this book uses the term Variant to refer to both types.

A Variant variable is always initialized to Unassigned. You can assign almost any kind of value to the variable, and it will keep track of the type and value. To learn the type of a Variant, call the VarType function. Chapter 6 lists the values that VarType can return. You can also access Delphi's low-level implementation of Variants by casting a Variant to the TVarData record type. Chapter 5 describes TVarData in detail.

When you use a Variant in an expression, Delphi automatically converts the other value in the expression to a Variant and returns a Variant result. You can assign that result to a statically typed variable, provided the Variant's type is compatible with the destination variable.

The most common use for Variants is to write an OLE automation client. You can assign an IDispatch interface to a Variant variable, and use that variable to call functions the interface declares. The compiler does not know about these functions, so the function calls are not checked for correctness until runtime. For example, you can create an OLE client to print the version of Microsoft Word installed on your system, as shown in the following code. Delphi doesn't know anything about the Version property or any other method or property of the Word OLE client. Instead, Delphi compiles your property and method references into calls to the IDispatch interface. You lose the benefit of compile-time checks, but you gain the flexibility of runtime binding. (If you want to keep the benefits of type safety, you will need a type library from the vendor of the OLE automation server. Use the IDE's type library editor to extract the COM interfaces the server's type library defines. This is not part of the Delphi language, so the details are not covered in this book.)

WordApp: Variant; begin try

WordApp := Create01e0bject('Word.Application'); Writeljn (WordApp.Version) ; except

WriteLnl'Word is not installed'); end; end;

Pointers

Pointers are not as important in Delphi as they are in C or C++ Delphi has real arrays, so there is no need to simulate arrays using pointers. Delphi objects use their own syntax, so there is no need to use pointers to refer to objects. Pascal also has true pass-by-reference parameters. The most common use for pointers is interfacing to C and C++ code, including the Windows API.

C and C++ programmers will be glad that Delphi's rules for using pointers are more C-like than Pascal-like. In particular, type checking is considerably looser for pointers than for other types. (But see the $T and $TypedAddress directives, in Chapter 8, which tighten up the loose rules.)

The type Pointer is a generic pointer type, equivalent to void* in C or C++ When you assign a pointer to a variable of type Pointer, or assign a Pointer-type expression to a pointer variable, you do not need to use a type cast. To take the address of a variable or routine, use Addr or @ (equivalent to & in C or C++). When using a pointer to access an element of a record or array, you can omit the dereference operator (A). Delphi can tell that the reference uses a pointer, and supplies the A operator automatically

You can perform arithmetic on pointers in a slightly more restricted manner than you can in C or C++. Use the Inc or Dec statements to advance or retreat a pointer value by a certain number of base type elements. The actual pointer value

18 Chapter 1 - Delphi Pascal changes according to the size of the pointer's base type. For example, incrementing a pointer to an Integer advances the pointer by 4 bytes:

IntPtr: AInteger; begin

Inc(IntPtr); // Make IntPtr point to the next Integer, 4 bytes later Inc(IntPtr, 3); // Increase IntPtr by 12 bytes = 3 * SizeOf(Integer)

Programs that interface directly with the Windows API often need to work with pointers explicitly For example, if you need to create a logical palette, the type definition of TLogPalette requires dynamic memory allocation and pointer manipulation, using a common C hack of declaring an array of one element. In order to use TLogPalette in Delphi, you have to write your Delphi code using C-like style, as shown in Example 1-9.

Example 1-9: Using a Pointer to Create a Palette

// Create a gray-scale palette with NumColors entries in it. type

TNumColors = 1..256; function MakeGrayPalette (NumColors: TNumColors) : HPalette; var

Palette: PLogPalette; // pointer to a TLogPalette record

I: TNumColors; Gray: Byte; begin

  • TLogPalette has a palette array of one element. To allocate // memory for the entire palette, add the size of NumColors-1 // palette entries. GetMem(Palette, SizeOf(TLogPalette) +
  • NumColors-1)*SizeOf(TPaletteEntry));

//In standard Pascal, you must write PaletteA.palVersion, // but Delphi dereferences the pointer automatically. Palette.palVersion := $300; Palette.palNumEntries := NumColors;

for I := 1 to NumColors do begin

  • Use a linear scale for simplicity, even though a logarithmic // scale gives better results. Gray := I * 255 div NumColors;
  • Turn off range checking to access palette entries past the first. {$R-}

Palette.palPalEntry[I-l].peRed := Gray; Palette.palPalEntry[I-l].peGreen := Gray; Palette.palPalEntry[I-l].peBlue := Gray; Palette.palPalEntry[1-1] .peFlags := 0;

Example 1-9: Using a Pointer to Create a Palette (continued)

II Delphi does not dereference pointers automatically when used // alone, as in the following case: Result := CreatePalette(Palette"); finally

FreeMem(Palette); end; end;

Function and Method Pointers

Delphi lets you take the address of a function, procedure, or method, and use that address to call the routine. For the sake of simplicity, all three kinds of pointers are called procedure pointers.

A procedure pointer has a type that specifies a function's return type, the arguments, and whether the pointer is a method pointer or a plain procedure pointer. Source code is easier to read if you declare a procedure type and then declare a variable of that type, for example:

type

TProcedureType = procedure(Arg: Integer); TFunctionType = function(Arg: Integer): string; var

Proc: TProcedureType; Func: TFunctionType; begin

Proc := SomeProcedure;

Proc(42); // Call Proc as though it were an ordinary procedure

Usually, you can assign a procedure to a procedure variable directly. Delphi can tell from context that you are not calling the procedure, but are assigning its address. (A strange consequence of this simple rule is that a function of no arguments whose return type is a function cannot be called in the usual Pascal manner. Without any arguments, Delphi thinks you are trying to take the function's address. Instead, call the function with empty parentheses—the same way C calls functions with no arguments.)

You can also use the @ or Addr operators to get the address of a routine. The explicit use of @ or Addr provides a clue to the person who must read and maintain your software.

Use a nil pointer for procedure pointers the same way you would for any other pointer. A common way to test a procedure variable for a nil pointer is with the Assigned function:

if Assigned(Proc) then Proc(42);

Type Declarations

Delphi follows the basic rules of type compatibility that ordinary Pascal follows for arithmetic, parameter passing, and so on. Type declarations have one new trick, though, to support the IDE. If a type declaration begins with the type keyword,

20 Chapter 1 - Delphi Pascal

Delphi creates separate runtime type information for that type, and treats the new type as a distinct type for var and out parameters. If the type declaration is just a synonym for another type, Delphi does not ordinarily create separate Ri ll for the type synonym. With the extra type keyword, though, separate RTTI tables let the IDE distinguish between the two types. You can read more about RTTI in Chapter 3-

Continue reading here: Variables and Constants

Was this article helpful?