Delphi Programming Guide
Delphi Programmer 

Menu  Table of contents
Bookmark and Share

Part I - Foundations
  Chapter 1 Delphi 7 and Its IDE
  Chapter 2 The Delphi Programming Language
  Chapter 3 The Run-Time Library
  Chapter 4 Core Library classes
  Chapter 5 Visual Controls
  Chapter 6 Building the User Interface
  Chapter 7 Working with Forms
Part II - Delphi Object-Oriented Architectures
  Chapter 8 The Architecture of Delphi Applications
  Chapter 9 Writing Delphi Components
  Chapter 10 Libraries and Packages
  Chapter 11 Modeling and OOP Programming (with ModelMaker)
  Chapter 12 From COM to COM+
Part III - Delphi Database-Oriented Architectures
  Chapter 13 Delphi's Database Architecture
  Chapter 14 Client/Server with dbExpress
  Chapter 15 Working with ADO
  Chapter 16 Multitier DataSnap Applications
  Chapter 17 Writing Database Components
  Chapter 18 Reporting with Rave
Part IV - Delphi, the Internet, and a .NET Preview
  Chapter 19 Internet Programming: Sockets and Indy
  Chapter 20 Web Programming with WebBroker and WebSnap
  Chapter 21 Web Programming with IntraWeb
  Chapter 22 Using XML Technologies
  Chapter 23 Web Services and SOAP
  Chapter 24 The Microsoft .NET Architecture from the Delphi Perspective
  Chapter 25 Delphi for .NET Preview: The Language and the RTL
       
  Appendix A Extra Delphi Tools by the Author
  Appendix B Extra Delphi Tools from Other Sources
  Appendix C Free Companion Books on Delphi
       
  Index    
  List of Figures    
  List of tables    
  List of Listings    
  List of Sidebars  

 
Previous Section Next Section

The Units of the RTL

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.

Warning 

Some of your Delphi 4 and Delphi 5 code might need to use the Variants unit to recompile. Delphi is smart enough to acknowledge this requirement and auto-include the Variants unit in projects that use the Variant type, issuing only a warning.

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").

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 names in Delphi 6 and 7 don't include the version number; when they are compiled, though, the BPL does have the version in its filename, as discussed in more detail in Chapter 10 ("Libraries and Packages").

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
a few interesting or little-known functions, and I will discuss them shortly.

The System and SysInit Units

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 underscore the point that the interface type in the Delphi language definition is in no way dependent on the Windows operating system. IInvokable was added in Delphi 6 to support SOAP-based invocation.

  • 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.

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.

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 indicate the entire platform, because WINDOWS was used in 16-bit versions of the OS (and contrasts with the symbol WIN32).

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 SysUtils and SysConst Units

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.

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).

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

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:

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 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
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 opposite of the Round function used by spreadsheets such as Excel.

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.

Rounding Headaches

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.

Click To expand
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 and StdConvs Units

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 pounds). In any case, if you have to deal with units of measurements in your code, you'll appreciate the features available in this engine.

The DateUtils Unit

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

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 translates a name into a four-character code consisting of one letter and three numbers. More information is available at www.nara.gov/genealogy/coding.html.

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

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; otherwise they won't compile.

The Variants and VarUtils Units

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 bugs have been patched. The variant implementation has been heavily reworked behind the scenes to improve the speed of this technology and decrease the memory footprint of its code.

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:

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

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.

Warning 

Building a custom variant is certainly not an easy task, and I can hardly find reasons for using them instead of objects and classes. With a custom variant you gain the advantage of using operator overloading on your own data structures, but you lose compile-time checking, make the code much slower, miss several OOP features, and have to write a lot of rather complex code.

The DelphiMM and ShareMem Units

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.

COM-Related Units

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.


 
Previous Section Next Section


 


 


Copyright © 2004-2016 "Delphi Sources". Delphi Programming Guide
     Twitter     Facebook