In this chapter, we have discussed the foundations of object-oriented programming (OOP) in Delphi. We have
This is
Understanding the secrets of Delphi's language and library is
In the meantime, Chapter 3 will give you an overview of the Delphi run-time library (
The Delphi programming language favors an object-oriented approach, tied with a visual development style. This is where Delphi shines, and we will cover component-based and visual development in this book; however, I want to underline the fact that many of Delphi's ready-to-use features come from its run-time library (RTL). This is a large collection of functions you can use to perform simple
There is a second reason to
In the most recent versions of Delphi, the RTL has a new structure and several new units. Borland added new units because it also added many new functions. In most cases, you'll find the existing functions in the units where they used to be, but the new functions appear in specific units. For example, new functions
The exception to this rule
| Warning |
Some of your Delphi 4 and Delphi 5 code might need to use the Variants unit to recompile. Delphi is smart enough to
|
A little fine-tuning has also been applied to reduce the minimum
|
|
While touching up the RTL, Borland
As a simple test, I've built the MiniSize program, which is not an attempt to build the smallest possible program, but rather an attempt to build a very small program that does something interesting: It
program
MiniSize;
uses
Windows;
{$R *.RES}
var
nSize: Integer; hFile: THandle; strSize: String;
begin
//
open
the current file and read the size
hFile := CreateFile (PChar (ParamStr (0)), 0, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0); nSize := GetFileSize (hFile, nil); CloseHandle (hFile);
// copy the size to a string and show it
SetLength (strSize, 20); Str (nSize, strSize); MessageBox (0, PChar (strSize),
'Mini Program'
, MB_OK);
end.
The program opens its own executable file, after retrieving its
If you compile this program with Delphi 5, you obtain an executable size of 18,432 bytes. Delphi 6
Notice that decisions of this type always imply a few trade-offs. In eliminating the overhead of variants from Delphi applications that don't use them, for example, Borland added a little extra
What is really important, in my opinion, is the size of full-blown Delphi applications based on run-time packages. A simple test with a do-nothing program, the MiniPack example, shows an executable size of 17,408 bytes.
|
|
In the following sections you'll find a list of the RTL units in Delphi, including all the units available (with the complete source code) in the
Source\Rtl\Sys
subfolder of the
Delphi
directory and some of those available in the subfolder
Source\Rtl\Common
. This second directory
| Note |
The original VCL package present up to version 5 of Delphi has been split into the VCL and RTL packages, so that nonvisual applications using run-time packages don't have the overhead of also deploying visual portions of the VCL. This change also helps with Linux compatibility, because the new package is shared between the VCL and CLX libraries. In addition, notice that the package
|
I'll give a short overview of the role of each unit and an overview of the groups of functions included. I'll also
a few interesting or
System is the core unit of the RTL and is automatically included in any compilation (through an automatic and implicit uses statement referring to it). If you try adding the unit to the uses statement of a program, you'll get the following compile-time error:
[Error] Identifier redeclared: System
The System unit includes, among other things:
The TObject class, which is the base class of any class defined in the Object Pascal language, including all the classes of the VCL. (This class is discussed later in this chapter.)
The
IInterface
,
IInvokable
,
IUnknown
, and
IDispatch
interfaces, as well as the simple implementation class
TInterfacedObject
.
IInterface
was added in Delphi 6 to
Some variant support code, including the variant type constants, the TVarData record type and the new TVariantManager type, a large number of variant conversion routines, and variant record and dynamic array support. This area has seen a lot of changes compared to Delphi 5. The basic information on variants is provided in Chapter 10 of Essential Pascal (for more information see Appendix C, "Free Companion Books on Delphi Programming").
Many base data types, including pointer and array types and the TDateTime type described in Chapter 2, "The Delphi Programming Language."
Memory allocation routines, such as GetMem and FreeMem , and the actual memory manager, defined by the TMemoryManager record and accessed by the GetMemoryManager and SetMemoryManager functions. For information, the GetHeapStatus function returns a THeapStatus data structure. Two global variables ( AllocMemCount and AllocMemSize ) hold the number and total size of allocated memory blocks. There is more on memory and the use of these functions in Chapter 8, "The Architecture of Delphi Applications" (see in particular the ObjsLeft example).
Package and module support code, including the PackageInfo pointer type, the GetPackage-InfoTable global function, and the EnumModules procedure (package internals are discussed in Chapter 12).
A rather long list of global variables, including the Windows application instance MainInstance; IsLibrary , indicating whether the executable file is a library or a stand-alone program; IsConsole , indicating console applications; IsMultiThread , indicating whether there are secondary threads; and the command-line string CmdLine . (The unit also includes ParamCount and ParamStr for easy access to command-line parameters.) Some of these variables are specific to the Windows platform, some are also available on Linux, and others are specific to Linux.
Thread-support code, with the BeginThread and EndThread functions; file support records and file-related routines; wide string and OLE string conversion routines; and many other low-level and system routines (including a number of automatic conversion functions).
The companion unit of System, called SysInit, includes the system initialization code, with functions you'll seldom use directly. This is another unit that is always implicitly included, because it is used by the System unit.
I've already described some interesting features of the System unit in the previous section's list. Most of the changes in recent Delphi versions relate to making the core RTL more cross-platform portable, replacing Windows-specific features with generic
| Note |
If you read the source code of
System.pas
, you'll notice some heavy use of conditional compilation, with many instances of
{$IFDEF
LINUX}
and
{$IFDEF
MSWINDOWS}
used to discriminate between the two operating systems. Notice that for Windows, Borland uses the
MSWINDOWS
define to
|
For example, an addition for compatibility between Linux and Windows relates to line breaks in text files. The DefaultTextLineBreakStyle variable affects the behavior of routines that read and write files, including most text-streaming routines. The possible values for this global variable are tlbsLF (the default in Kylix) and tlbsCRLF (the default in Delphi). The line-break style can also be set on a file-by-file basis with SetTextLineBreakStyle function.
Similarly, the global sLineBreak string constant has the value #13#10 in the Windows version of the IDE and the value #10 in the Linux version. Another change is that the System unit now includes the TFileRec and TTextRec structures, which were defined in the SysUtils unit in earlier versions of Delphi.
The SysConst unit defines a few constant strings used by the other RTL units for displaying messages. These strings are declared with the resourcestring keyword and saved in the program resources. Like other resources, they can be translated by means of the Integrated Translation Manager or the External Translation Manager.
The SysUtils unit is a collection of system utility functions of various types. Unlike other RTL units, it is largely an operating system–dependent unit. The SysUtils unit has no specific focus, but it includes a bit of everything, from string management to locale and multibyte-
Some features of SysUtils are used every day by every programmer, such as the IntToStr or Format string-formatting functions; other features are lesser known, such as the Windows version information global variables. These indicate the Windows platform (Window 9 x or NT/2000/XP), the operating system version and build number, and the service pack installed. They can be used as in the following code, extracted from the WinVersion example:
case Win32Platform of VER_PLATFORM_WIN32_WINDOWS: ShowMessage ( 'Windows 9x' ); VER_PLATFORM_WIN32_NT: ShowMessage ( 'Windows NT' ); end; ShowMessage ( 'Running on Windows: ' + IntToStr (Win32MajorVersion) + '.' + IntToStr (Win32MinorVersion) + ' (Build ' + IntToStr (Win32BuildNumber) + ') ' + #10#13 + 'Update: ' + Win32CSDVersion);
The second code fragment produces a message like the one in the following graphic (of course, on the operating-system version you have installed):
Another little-known feature of this unit is the
TMultiReadExclusiveWriteSynchronizer
class— probably the VCL class with the longest name. Borland has defined an alias name for the class, which is much shorter:
TMREWSync
(the two classes are identical). This class supports multithreading: It allows you to work with resources that can be used by multiple threads at the same time for reading (multiread) but must be used by a single thread when writing (exclusive-write). This means writing cannot begin until all the reading threads have
The implementation of the TMultiReadExclusiveWriteSynchronizer class has been updated in Delphi 7, but similar improvements are available in an informal patch released after Delphi 6 update 2. The new version of the class is more optimized and less subject to deadlocks, which are often a problem with synchronization code.
| Note |
The multiread synchronizer is unique in that it supports recursive locks and promotion of read locks to write locks. The main purpose of the class is to allow multiple threads easy, fast access to read from a shared resource, but still allow one thread to gain exclusive control of the resource for relatively infrequent updates. Delphi includes other synchronization classes, declared in the SyncObjs unit (available under Source/Rtl/Common ) and closely mapped to operating-system synchronization objects (such as events and critical sections in Windows). |
Over the last couple of versions, Delphi has added some new functions within the SysUtils unit. One of these areas relates to Boolean-to-string conversion. The
BoolToStr
function
BoolToStr (True) // returns '-1' BoolToStr (False, True) // returns 'FALSE' by default
The reverse function is StrToBool , which can convert a string containing either one of the values of the two Boolean arrays mentioned or a numeric value. In the latter case, the result will be true unless the numeric value is zero. You can see a simple demo of the use of the Boolean conversion functions in the StrDemo example, later in this chapter.
Other functions recently added to SysUtils relate to floating-point conversions to currency and date time types: You can use
FloatToCurr
and
FloatToDateTime
to avoid an explicit
The
AnsiDequotedStr
function, which
Three functions ( TryStrToDate , TryEncodeDate , and TryEncodeTime ) try to convert a string to a date or to encode a date or time, without raising an exception, similar to the Try functions previously mentioned. In addition, the DecodeDateFully function returns more detailed information, such as the day of the week, and the CurrentYear function returns the year of today's date.
A portable, friendly, overloaded version of the GetEnvironmentVariable function uses string parameters instead of PChar parameters and is definitely easier to use than the original version based on PChar pointers:
function GetEnvironmentVariable(Name: string): string;
Other functions relate to interface support. Two overloaded versions of the little-known Support function allow you to check whether an object or a class supports a given interface. The function corresponds to the behavior of the is operator for classes and is mapped to the QueryInterface method. Here's an example:
var W1: IWalker; J1: IJumper; begin W1 := TAthlete.Create; // more code... if Supports (w1, IJumper) then begin J1 := W1 as IJumper; Log (J1.Walk); end;
SysUtils also includes an IsEqualGUID function and two functions for converting strings to GUIDs and vice versa. The function CreateGUID has been moved to SysUtils, as well, to make it available on Linux (with a custom implementation, of course).
Finally, more features were added in recent versions to improve cross-platform support. The
AdjustLineBreaks
function can now do different types of
adjustments
to
Speaking of files, Delphi 7 adds to the SysUtils unit the
GetFileVersion
function, which reads the version number from the version information
Most of Delphi's string formatting routines (see Appendix C, "Free Companion Books on Delphi," for instructions on how to get an e-book introducing some of them) use global variables to determine decimal and thousand separators, date/time formats, and so on. The values of these variables are first read from the system (Windows regional settings) when a program starts, and you are free to override each of them. However, if the
If you need different output formats in different places within a program, you can take advantage of the new set of overloaded string formatting routines; they take an extra parameter of type TFormatSettings , including all the relevant settings. For example, there are now two versions of Format :
function Format( const Format: string; const Args: array of const ): string; overload; function Format( const Format: string; const Args: array of const; const FormatSettings: TFormatSettings): string; overload;
Tens of functions have this new extra parameter, which is then used instead of the global settings. However, you can initialize it with the default settings of the computer on which your program is running by calling the new GetLocaleFormatSettings function (available only on Windows, not Linux).
The Math unit hosts a collection of mathematical functions: about 40 trigonometric functions,
Describing all the functions of this unit would be rather
Recent versions add to the Math unit quite a number of features. They include support for infinite constants ( Infinity and NegInfinity ) and related comparison functions ( IsInfinite and IsNan ), along with new trigonometric functions for cosecants and cotangents, and new angle-conversion functions.
A handy feature is the availability of an overloaded IfThen function, which returns one of two possible values depending on a Boolean expression. (A similar function is available also for strings.) You can use it, for example, to compute the minimum of two values:
nMin := IfThen (nA < nB, na, nB);
| Note |
The IfThen function is similar to the ?: operator of the C/C++ language. I find it handy because you can replace a complete if / then / else statement with a much shorter expression, writing less code and often declaring fewer temporary variables. |
You can use
RandomRange
and
RandomFrom
instead of the traditional
Random
function to gain more control over the random values produced by the RTL. The first function returns a number within two extremes you specify, and the second selects a random value from an array of possible
The InRange Boolean function can be used to check whether a number is within two other values. The EnsureRange function, on the other hand, forces the value to be within the specified range. The return value is the number itself or the lower limit or upper limit, in the event the number is out of range. Here is an example:
// do something only if value is within min and max if InRange (value, min, max) then ... // make sure the value is between min and max value := EnsureRange (value, min, max); ...
Another set of useful functions relates to comparisons. Floating-point numbers are fundamentally
The CompareValue function uses the same rule for floating-point numbers but is available also for integers; it returns one of the three constants LessThanValue , EqualsValue , and GreaterThanValue (corresponding to –1, 0, and 1). Similarly, the new Sign function returns –1, 0, or 1 to indicate a negative value, zero, or a positive value.
The
DivMod
function is equivalent to both the div and mod operations, returning the result of the integer division and the remainder (or
RoundTo (123827, 3); // result is 124,000 RoundTo (12.3827, -2); // result is 12.38
| Warning |
Notice that the
RoundTo
function uses a positive number to indicate the power of 10 to round to (for example,
2
for hundreds) or a negative number for the number of decimal places. This is exactly the
|
There have also been some changes to the standard rounding operations provided by the Round function: You can now control how the FPU (the floating-point unit of the CPU) does the rounding by calling the SetRoundMode function. Other functions control the FPU precision mode and its exceptions.
Delphi's classic Round function and the newer RoundTo functions are mapped to the CPU/ FPU rounding algorithms. By default, Intel CPUs use banker's rounding , which is also the type of rounding typically found in spreadsheet applications.
Banker's rounding is based on the assumption that when you're rounding numbers that lie exactly between two values (the .5 numbers), rounding them all up or all down will statistically increase or reduce the total amount (of money, in general). For this reason, the rule of banker's rounding indicates that .5 numbers should be rounded down or up depending on whether the number (without decimals) is odd or even. This way, the rounding will be balanced, at least statistically. You can see an example of the output of banker's rounding in Figure 3.1, which shows the output of the Rounding example I've built to
Figure 3.1:
The Rounding example, demon-strated banker's rounding and arithmetic rounding
The program also uses another type of rounding provided by the Math unit in the SimpleRoundTo function, which uses asymmetric arithmetic rounding . In this case, all .5 numbers are rounded to the upper value. However, as highlighted in the Rounding example, the function doesn't work as expected when rounding to a decimal digit (that is, when you pass a negative second parameter). In this case, due to the representation errors of floating-point numbers, the rounding trims the values; for example, it turns 1.15 into 1.1 instead of the expected 1.2. The solution is to multiply the value by ten before rounding, round it to zero decimal digits, and then divide it, as demonstrated in the sample program:
SimpleRoundTo (d * 10, 0) / 10)
The ConvUtils unit contains the core of the conversion engine introduced in Delphi 6. It uses the conversion constants defined by a second unit, StdConvs. I'll cover these two units later in this chapter and show how to extend them with new measurement units.
| Note |
Delphi 7 makes only a single improvement to the conversion unit: It adds support for stones (the British unit of measurement that is equivalent to 14
|
The DateUtils unit is a collection of date- and time-related functions. It includes functions for picking values from a TDateTime variable or counting values from a given interval, such as
// pick value function DayOf( const AValue: TDateTime): Word; function HourOf( const AValue: TDateTime): Word; // value in range function WeekOfYear( const AValue: TDateTime): Integer; function HourOfWeek( const AValue: TDateTime): Integer; function SecondOfHour( const AValue: TDateTime): Integer;
Some of these functions are quite odd, such as
MilliSecondOfMonth
or
SecondOfWeek
, but Borland developers have decided to provide a complete set of functions, no matter how
There are functions for computing the initial or final value of a given time interval (day, week, month, year) including the current date, and for range checking and querying; for example:
function DaysBetween( const ANow, AThen: TDateTime): Integer; function WithinPastDays( const ANow, AThen: TDateTime; const ADays: Integer): Boolean;
Other functions cover incrementing and
The StrUtils unit was introduced in Delphi 6 with some new string-related functions. One of the key features of this unit is the availability of many string comparison functions. There are functions based on a soundex algorithm ( AnsiResembleText ), and others that provide lookup in arrays of strings ( AnsiMatchText and AnsiIndexText ), substring location, and text replacement (including AnsiContainsText and AnsiReplaceText ).
| Note |
Soundex
is an algorithm that compares names based on how they sound rather than how they are spelled. The algorithm computes a number for each word sound, so that by comparing two such numbers you can determine whether two names sound similar. The system was first applied in 1880 by the U.S. Bureau of the Census; it was patented in 1918 and is now in the public domain. The soundex code is an indexing system that
|
Beside comparisons, other functions provide a two-way test (the nice IfThen function, similar to the one we've already seen for numbers), duplicate and reverse strings, and replace substrings. Most of these string functions were added as a convenience to Visual Basic programmers migrating to Delphi.
I've used some of these functions in the StrDemo example, which uses also some of the Boolean-to-string conversions defined within the SysUtils unit. The program is little more than a test for a few of these functions. For example, it uses the soundex comparison between the strings entered in two edit boxes, converting the resulting Boolean into a string and showing it:
ShowMessage (BoolToStr (AnsiResemblesText (EditResemble1.Text, EditResemble2.Text), True));
The program also showcases the AnsiMatchText and AnsiIndexText functions, after filling a dynamic array of strings (called strArray ) with the values of the strings inside a list box. I could have used the simpler IndexOf method of the TStrings class, but doing so would have defeated the purpose of the example. The two list comparisons are as follows:
procedure TForm1.ButtonMatchesClick(Sender: TObject); begin ShowMessage (BoolToStr (AnsiMatchText(EditMatch.Text, strArray), True)); end; procedure TForm1.ButtonIndexClick(Sender: TObject); var nMatch: Integer; begin nMatch := AnsiIndexText(EditMatch.Text, strArray); ShowMessage (IfThen (nMatch >= 0, 'Matches the string number ' + IntToStr (nMatch), 'No match' )); end;
Notice the use of the IfThen function in the last few lines of code; it has two alternative output strings, depending on the result of the initial test ( nMatch >= 0 ).
Three more
// duplicate (3 times) a string ShowMessage (DupeString (EditSample.Text, 3)); // reverse the string ShowMessage (ReverseString (EditSample.Text)); // choose a random string ShowMessage (RandomFrom (strArray));
Delphi 7 adds a little to the StrUtils unit. The new PosEx function will be handy to many developers and is worth a brief mention. When searching for multiple occurrences of a string within another one, a classic Delphi solution was to use the Pos function and repeat the search over the remaining portion of the string. For example, you could count the occurrences of a string inside another string with code like this:
function CountSubstr (text, sub: string): Integer; var nPos: Integer; begin Result := 0; nPos := Pos (sub, text); while nPos > 0 do begin Inc (Result); text := Copy (text, nPos + Length (sub), MaxInt); nPos := Pos (sub, text); end; end;
The new PosEx function allows you to specify the starting position of the search within a string, so you don't need to alter the original string (wasting quite some time). Thus the previous code can be simplified in the following way:
function CountSubstrEx (text, sub: string): Integer; var nPos: Integer; begin Result := 0; nPos := PosEx (sub, text, 1); // default while nPos > 0 do begin Inc (Result); nPos := PosEx (sub, text, nPos + Length (sub)); end; end;
Both code snippets are used in a trivial way in the StrDemo example discussed earlier.
The Types unit holds data types common to multiple operating systems. In past versions of Delphi, the same types were defined by the Windows unit; now they've been moved to this common unit, shared by Delphi and Kylix. The types defined here are simple and include, among others, the TPoint , TRect , and TSmallPoint record structures plus their related pointer types.
| Warning |
Notice that you will have to update old Delphi programs that refer to
TRect
or
TPoint
, by adding the Types unit in the
uses
statement;
|
Variants and VarUtils are two more units introduced in Delphi 6 to host the variant-related portion of the library. The Variants unit contains generic code for variants. As mentioned earlier, some of the routines in this unit have been moved here from the System unit. Functions include generic variant support, variant arrays, variant copying, and dynamic array to variant array conversions. In addition, the TCustomVariantType class defines customizable variant data types.
The Variants unit is totally platform independent and uses the VarUtils unit, which contains OS-dependent code. In Delphi, this unit uses the system APIs to manipulate variant data; in Kylix, it uses custom code provided by the RTL library.
| Note |
In Delphi 7, these units have been extended and some
|
A specific area that has seen significant improvement in Delphi 7 is the ability to control the behavior of variant implementations, particularly comparison rules. Delphi 6 saw a change in the variant code so that
null
values cannot be compared with other values. This behavior is correct from a formal point of view,
ncrError Any type of comparison causes an exception to be raised, because you cannot compare an undefined value; this was the (new) default behavior in Delphi 6.
ncrStrict Any type of comparison always fails (returning False), regardless of the values.
ncrLoose Equality tests succeed only among null values (a null is different from any other value). In comparisons null values are considered like empty values or zeros.
Other settings like NullStrictConvert and NullAsStringValue control how conversion is accomplished in case of null values. I suggest that you carry out your own experiments using the VariantComp example available with the code for this chapter. As you can see in Figure 3.2, this program has a form with a RadioGroup you can use to change the settings of the NullEqualityRule and NullMagnitudeRule global variables, and a few buttons to perform various comparisons.
Figure 3.2:
The form of the VariantComp example at design time
Another recent extension to the concept of variants is the possibility of extending the type system with custom variants. This technique allows you to define a new data type that, contrary to a class, overloads standard arithmetic operators.
A variant is a type holding both the type specification and the actual value. One variant can contain a string; another can contain a number. The system defines automatic conversions among variant types, allowing you to mix them inside operations (including custom variants). This flexibility comes at a high cost: Operations on variants are much slower than on native types, and variants use extra memory.
As an example of a custom variant type, Delphi ships with an interesting definition for complex numbers, found in the VarCmplx unit (available in source-code format in the
Rtl\Common
folder). You can create complex variants by using one of the overloaded
VarComplexCreate
functions and use them in any expression, as the following code fragment
var v1, v2: Variant; begin v1 := VarComplexCreate (10, 12); v2 := VarComplexCreate (10, 1); ShowMessage (v1 + v2 + 5);
The complex numbers are defined using classes, but they are surfaced as variants by inheriting a new class from the TCustomVariantType class (defined in the Variants unit), overriding a few virtual abstract functions, and creating a global object that takes care of the registration within the system.
Besides these internal definitions, the Variants unit includes a long list of routines for operating on variants, including mathematical and trigonometric operations. I'll leave them to your study, because not all readers will be interested in complex numbers for their programs.
| Warning |
Building a custom variant is
|
The DelphiMM and ShareMem units relate to memory management. The standard Delphi memory manager is declared in the System unit.
The DelphiMM unit defines an alternative memory manager library to be used when passing strings from an executable to a DLL (a Windows dynamic linking library), both built with Delphi. This memory manager library is compiled by default in the Borlndmm.dll library file you'll have to deploy with your program.
The interface to this memory manager is defined in the ShareMem unit. You must include this unit (it's required to be the first unit) in the projects of both your executable and library, as described in more detail in Chapter 10, "Libraries and Packages."
| Note |
Unlike Delphi, Kylix has no DelphiMM and ShareMem units, because memory management is provided in the native Linux libraries (in particular, Kylix uses malloc from glibc) and so is effectively shared among different modules. In Kylix, however, applications with multiple modules must use the ShareExcept unit, which allows exceptions raised in a module to be surfaced to another module. |
ComConst, ComObj, and ComServ provide low-level COM support. These units are not really part of the RTL, from my point of view, so I won't discuss them here in any detail. You can refer to Chapter 12 for all the related information. These units have not changed much in recent versions of Delphi.