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 related to dates are now in the DateUtils unit, but existing date functions have not been moved out of SysUtils in order to avoid incompatibilities with existing code.
The exception to this rule relates to some of the variant support functions, which were moved out of the System unit to avoid unwanted linkage of specific Windows libraries, even in programs that didn't use those features. These variant functions are now part of the Variants unit, described later in the chapter.
A little fine-tuning has also been applied to reduce the minimum size of an executable file, which is at times enlarged by the unwanted inclusion of global variables or initialization code.
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 hosts the source code of units that make up the new RTL package, which comprises both the function-based library and the core classes, discussed at the end of this chapter and in Chapter 4 ("Core Library Classes").
I'll give a short overview of the role of each unit and an overview of the groups of functions included. I'll also devote more space to the newer units. I won't provide a detailed list of the functions included, because the online help includes similar reference material. However, I've tried to pick
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 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.
Recent Changes in 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 implementations now shared by Delphi and Kylix. Along this line, there are new names for interface types, totally revised support for variants, new pointer types, dynamic array support, and functions to customize the management of exception objects.
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-characters support, from the Exception class and several other derived exception classes to a plethora of string-formatting constants and routines. In particular, later in this chapter we'll focus on some of the unit's file management routines.
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 9x 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 terminated.
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.
Recent SysUtils Functions
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 generally returns '–1' and '0' for true and false values. If the second optional parameter is specified, the function returns the first string in the TrueBoolStrs and FalseBoolStrs arrays (by default 'TRUE' and 'FALSE'):
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 typecast. The TryStrToFloat and TryStrToCurr functions try to convert a string into a floating-point or currency value and will return False in case of error instead of generating an exception (as the classic StrToFloat and StrToCurr functions do).
The AnsiDequotedStr function, which removes quotes from a string, matches the AnsiQuoteStr function added in Delphi 5. Speaking of strings, as of Delphi 6 there is much-improved support for wide strings, with a series of routines including WideUpperCase, WideLowerCase, WideCompareStr, WideSameStr, WideCompareText, WideSameText, and WideFormat. All of these functions work like their AnsiString counterparts.
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 carriage-return and line-feed sequences, and new global variables for text files have been introduced in the System unit, as described earlier. The FileCreate function has an overloaded version in which you can specify file-access rights the Unix way. The ExpandFileName function can locate files (on case-sensitive file systems) even when their cases don't exactly correspond. The functions related to path delimiters (backslash or slash) have been made more generic than in earlier versions of Delphi and renamed accordingly. (For example, the old IncludeTrailingBackslash function is now better known as IncludingTrailingPathDelimiter.)
Speaking of files, Delphi 7 adds to the SysUtils unit the GetFileVersion function, which reads the version number from the version information optionally added to a Windows executable file (which is why this function won't work on Linux).
Delphi 7 Extended String Formatting Routines
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 user modifies the Regional Settings in Control Panel while your program is running, the program will respond to the broadcast message by updating the variables, probably losing your hard-coded changes.
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, logarithmic and exponential functions, rounding functions, polynomial evaluations, almost 30 statistical functions, and a dozen financial functions.
Describing all the functions of this unit would be rather tedious, although some readers are probably very interested in Delphi's mathematical capabilities. For this reason, I've decided to focus on math functions introduced in the latest versions of Delphi (particularly Delphi 6) and then cover one specific topic that often confuses Delphi programmers— rounding.
New Math Functions
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:
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 numbers you pass to it as a parameter.
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 inexact; a floating-point number is an approximation of a theoretical real value. When you do mathematical operations on floating-point numbers, the inexactness of the original values accumulates in the results. Multiplying and dividing by the same number might not return exactly the original number but one that is very close to it. The SameValue function allows you to check whether two values are close enough in value to be considered equal. You can specify how close the two numbers should be or let Delphi compute a reasonable error range for the representation you are using. (This is why the function is overloaded.) Similarly, the IsZero function compares a number to zero, with the same "fuzzy logic."
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 modulus) at once. The RoundTo function allows you to specify the rounding digit—allowing, for example, rounding to the nearest thousand or to two decimals:
RoundTo (123827, 3); // result is 124,000 RoundTo (12.3827, -2); // result is 12.38
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 demonstrate different types of 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:
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.
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 impractical they sound. (I actually used some of these functions in Chapter 2, to build the TDate class.)
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 decrementing by each of the possible time intervals, encoding and "recoding" (replacing one element of the TDateTime value, such as the day, with a new one), and doing "fuzzy" comparisons (approximate comparisons where a difference of a millisecond will still make two dates equal). Overall, DateUtils is quite interesting and not terribly difficult to use.
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).
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 buttons do simple calls to three other new functions, with the following lines of code (one for each):
// duplicate (3 times) a string ShowMessage (DupeString (EditSample.Text, 3)); // reverse the string ShowMessage (ReverseString (EditSample.Text)); // choose a random string ShowMessage (RandomFrom (strArray));
From Pos to PosEx
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.
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.
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, specifically for the fields of a dataset (an area in which variants are heavily used), but this change had the side effect of breaking existing code. Now you can control this behavior using the NullEqualityRule and NullMagnitudeRule global variables, each of which assumes one of the following values:
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.
Custom Variants and Complex Numbers
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 demonstrates:
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.
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."
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.
|Copyright © 2004-2021 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide||