CARVIEW |
By Ray Lischner
Price: $29.95 USD
£20.95 GBP
Cover | Table of Contents | Colophon
Table of Contents
- Chapter 1: Delphi Pascal
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterDelphi Pascal is an object-oriented extension of traditional Pascal. It is not quite a proper superset of ISO standard Pascal, but if you remember Pascal from your school days, you will easily pick up Delphi's extensions. Delphi is not just a fancy Pascal, though. Delphi adds powerful object-oriented features, without making the language too complicated. Delphi has classes and objects, exception handling, multithreaded programming, modular programming, dynamic and static linking, OLE automation, and much, much more.This chapter describes Delphi's extensions to Pascal. You should already be familiar with traditional Pascal or one of the other popular extensions to Pascal, such as Object Pascal. If you already know Borland's Object Pascal from the Turbo Pascal products, you will need to learn a new object model (detailed in ), plus other new features.Borland uses the name "Object Pascal" to refer to Delphi's programming language, but many other languages use the same name, which results in confusion. This book uses the name "Delphi Pascal" to refer to the Delphi programming language, leaving Object Pascal for the many other object-oriented variations of Pascal.Delphi Pascal is a modular programming language, and the basic module is called a unit. To compile and link a Delphi program, you need a program source file and any number of additional units in source or object form. The program source file is usually called a project source file because the project can be a program or a library—that is, a dynamically linked library (DLL).When Delphi links a program or library, it can statically link all the units into a single .exe or .dll file, or it can dynamically link to units that are in packages. A package is a special kind of DLL that contains one or more units and some extra logic that enables Delphi to hide the differences between a statically linked unit and a dynamically linked unit in a package. See the section Section 1.4," later in this chapter, for more information about packages.Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Units
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterDelphi Pascal is a modular programming language, and the basic module is called a unit. To compile and link a Delphi program, you need a program source file and any number of additional units in source or object form. The program source file is usually called a project source file because the project can be a program or a library—that is, a dynamically linked library (DLL).When Delphi links a program or library, it can statically link all the units into a single .exe or .dll file, or it can dynamically link to units that are in packages. A package is a special kind of DLL that contains one or more units and some extra logic that enables Delphi to hide the differences between a statically linked unit and a dynamically linked unit in a package. See the section Section 1.4," later in this chapter, for more information about packages.Some units represent forms. A form is Delphi's term for a window you can edit with Delphi's GUI builder. A form description is stored in a .dfm file, which contains the form's layout, contents, and properties.Every .dfm file has an associated .pas file, which contains the code for that form. Forms and .dfm files are part of Delphi's integrated development environment (IDE), but are not part of the formal Delphi Pascal language. Nonetheless, the language includes several features that exist solely to support Delphi's IDE and form descriptions. Read about these features in depth in .A binary .dfm file is actually a 16-bit .res (Windows resource) file, which maintains compatibility with the first version of Delphi. Versions 2 and later produce only 32-bit programs, so Delphi's linker converts the .dfm resource to a 32-bit resource automatically. Thus, binary .dfm files are usually compatible with all versions of Delphi. Delphi 5 also supports textual .dfm files. These files are plain text and are not compatible with prior versions of Delphi, at least not without conversion back to the binary format. The only way to tell whether aAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Programs
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterA Delphi program looks similar to a traditional Pascal program, starting with the
program
keyword and using abegin
-end
block for the main program. Delphi programs are usually short, though, because the real work takes place in one or more separate units. In a GUI application, for example, the main program usually calls an initialization procedure, creates one or more forms (windows), and calls a procedure for the Windows event loop.For compatibility with standard Pascal, Delphi allows a parameter list after the program name, but—like most modern Pascal compilers—it ignores the identifiers listed there.In a GUI application, you cannot use the standard Pascal I/O procedures because there is no input device to read from and no output device to write to. Instead, you can compile a console application, which can read and write using standard Pascal I/O routines. (See , to learn about the$AppType
directive, which tells Delphi to build a console or a GUI application.)A program'suses
declaration lists the units that make up the program. Each unit name can be followed by anin
directive that specifies a filename. The IDE and compiler use the filename to locate the units that make up the project. Units without anin
directive are usually library units, and are not part of the project's source code. If a unit has an associated form, the IDE also stores the form name in a comment. Example 1.3 shows a typical program source file.Example 1.3. A Typical Program Fileprogram Typical; uses Forms, Main in 'Main.pas' {MainForm}, MoreStuff in 'MoreStuff.pas' {Form2}, Utils in 'Utils.pas'; {$R *.RES} begin Application.Initialize; Application.CreateForm(TMainForm, MainForm); Application.CreateForm(TForm2, Form2); Application.Run; end.
TheForms
unit is part of the standard Delphi library, so it does not have anin
directive and source file. The other units have source filenames, so Delphi's IDE manages those files as part of the project. To learn about the$R
compiler directive, see . TheApplication
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Libraries
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterA Delphi library compiles to a standard Windows DLL. A library source file looks the same as a program source file, except that it uses the
library
keyword instead ofprogram
. A library typically has anexports
declaration, which lists the routines that the DLL exports. Theexports
declaration is optional, and if you intend to use a unit in a library, it's usually best to put theexports
declaration in the unit, close to the subroutine you are exporting. If you don't use the unit in a library, theexports
declaration has no impact.The main body of the library—itsbegin
-end
block—executes each time the library is loaded into an application. Thus, you don't need to write a DLL procedure to handle theDLL_PROCESS_ATTACH
event. For process detach and thread events, though, you must write a handler. Assign the handler to theDllProc
variable. Delphi takes care of registering the procedure with Windows, and Windows calls the procedure when a process detaches or when a thread attaches or detaches. Example 1.4 shows a simple DLL procedure.Example 1.4. DLL Attach and Detach Viewerlibrary Attacher; uses Windows; procedure Log(const Msg: string); begin MessageBox(0, PChar(Msg), 'Attacher', Mb_IconInformation + Mb_OK); end; procedure AttachDetachProc(Reason: Integer); begin case Reason of Dll_Process_Detach: Log('Detach Process'); Dll_Thread_Attach: Log('Attach Thread'); Dll_Thread_Detach: Log('Detach Thread'); else Log('Unknown reason!'); end; end; begin // This code runs each time the DLL is loaded into a new process. Log('Attach Process'); DllProc := @AttachDetachProc; end.
When using a DLL, you must be careful about dynamic memory. Any memory allocated by a DLL is freed when the DLL is unloaded. Your application might retain pointers to that memory, though, which can cause access violations or worse problems if you aren't careful. The simplest solution is to use theShareMem
unit as the first unit in your application and in every library the application loads. TheAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Packages
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterDelphi can link a unit statically with a program or library, or it can link units dynamically. To link dynamically to one or more units, you must put those units in a package, which is a special kind of DLL. When you write a program or library, you don't need to worry about how the units will be linked. If you decide to use a package, the units in the package are not linked into your .exe or .dll, but instead, Delphi compiles a reference to the package's DLL (which has the extension .bpl for Borland Package Library).Packages avoid the problems of DLLs, namely, managing memory and class identities. Delphi keeps track of the classes defined in each unit and makes sure that the application and all associated packages use the same class identity for the same class, so the
is
andas
operators work correctly.Delphi's IDE uses packages to load components, custom forms, and other design-time units, such as property editors. When you write components, keep their design-time code in a design-time package, and put the actual component class in a runtime package. Applications that use your component can link statically with the component's .dcu file or link dynamically with the runtime package that contains your component. By keeping the design-time code in a separate package, you avoid linking any extraneous code into an application.Note that the design-time package requires the runtime package because you cannot link one unit into multiple packages. Think of an application or library as a collection of units. You cannot include a unit more than once in a single program—it doesn't matter whether the units are linked statically or dynamically. Thus, if an application uses two packages, the same unit cannot be contained in both packages. That would be the equivalent of linking the unit twice.To build a package, you need to create a .dpk file, or package source file. The .dpk file lists the units the package contains, and it also lists the other packages the new package requires. The IDE includes a convenient package editor, or you can edit theAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Data Types
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterDelphi Pascal supports several extensions to the standard Pascal data types. Like any Pascal language, Delphi supports enumerations, sets, arrays, integer and enumerated subranges, records, and variant records. If you are accustomed to C or C++, make sure you understand these standard Pascal types, because they can save you time and headache. The differences include the following:
- Instead of bit masks, sets are usually easier to read.
- You can use pointers instead of arrays, but arrays are easier and offer bounds-checking.
- Records are the equivalent of structures, and variant records are like unions.
The basic integer type isInteger
. TheInteger
type represents the natural size of an integer, given the operating system and platform. Currently,Integer
represents a 32-bit integer, but you must not rely on that. The future undoubtedly holds a 64-bit operating system running on 64-bit hardware, and calling for a 64-bitInteger
type. To help cope with future changes, Delphi defines some types whose size depends on the natural integer size and other types whose sizes are fixed for all future versions of Delphi. Table 1.2 lists the standard integer types. The types marked with natural size might change in future versions of Delphi, which means the range will also change. The other types will always have the size and range shown.Table 1.2: Standard Integer Types TypeSizeRange in Delphi 5Integer
natural-2,147,483,648 .. 2,147,483,647Cardinal
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Variables and Constants
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterUnlike standard Pascal, Delphi lets you declare the type of a constant, and you can initialize a global variable to a constant value. Delphi also supports multithreaded applications by letting you declare variables that have distinct values in each thread of your application.When you declare the type of a constant, Delphi sets aside memory for that constant and treats it as a variable. You can assign a new value to the "constant," and it keeps that value. In C and C++, this entity is called a static variable.
// Return a unique number each time the function is called. function Counter: Integer; const Count: Integer = 0; begin Inc(Count); Result := Count; end;
At the unit level, a variable retains its value in the same way, so you can declare it as a constant or as a variable. Another way to write the same function is as follows:var Count: Integer = 0; function Counter: Integer; begin Inc(Count); Result := Count; end;
The term "typed constant" is clearly a misnomer, and at the unit level, you should always use an initializedvar
declaration instead of a typed constant. You can force yourself to follow this good habit by disabling the$J
or$WriteableConst
compiler directive, which tells Delphi to treat all constants as constants. The default, however, is to maintain backward compatibility and let you change the value of a typed constant. See for more information about these compiler directives.For local variables in a procedure or function, you cannot initialize variables, and typed constants are the only way to keep values that persist across different calls to the routine. You need to decide which is worse: using a typed constant or declaring the persistent variable at the unit level.Delphi has a unique kind of variable, declared withthreadvar
instead ofvar
. The difference is that athreadvar
variable has a separate value in each thread of a multithreaded application. An ordinary variable has a single value that is shared among all threads. Athreadvar
variable must be declared at the unit level.Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Exception Handling
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterExceptions let you interrupt a program's normal flow of control. You can raise an exception in any function, procedure, or method. The exception causes control to jump to an earlier point in the same routine or in a routine farther back in the call stack. Somewhere in the stack must be a routine that uses a
try
-except
-end
statement to catch the exception, or else Delphi callsExceptProc
to handle the exception.Delphi has two related statements for dealing with exceptions. Thetry
-except
statement sets up an exception handler that gets control when something goes wrong. Thetry
-finally
statement does not handle exceptions explicitly, but guarantees that the code in thefinally
part of the statement always runs, even if an exception is raised. Usetry
-except
to deal with errors. Usetry
-finally
when you have a resource (such as allocated memory) that must be cleaned up properly, no matter what happens. Thetry
-except
statement is similar to try-catch in C++ or Java. Standard C++ does not havefinally
, but Java does. Some C++ compilers, including Borland's, extend the C++ standard to add the same functionality, e.g., with the__finally
keyword.Like C++ and Java, Delphi'stry
-except
statement can handle all exceptions or only exceptions of a certain kind. Eachtry
-except
statement can declare manyon
sections, where each section declares an exception class. Delphi searches theon
sections in order, trying to find an exception class that matches, or is a superclass of, the exception object's class. Example 1.10 shows an example of how to usetry
-except
.Example 1.10. Using try-except to Handle an Exceptionfunction ComputeSomething: begin try PerformSomeDifficultComputation; except on Ex: EDivideByZero do WriteLn('Divide by zero error'); on Ex: EOverflow do WriteLn('Overflow error'); else raise; // reraise the same exception, to be handled elsewhere end; end;
In a multithreaded application, each thread can maintain its own exception information and can raise exceptions independently from the other threads. See for details.Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - File I/O
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterTraditional Pascal file I/O works in Delphi, but you cannot use the standard
Input
andOutput
files in a GUI application. To assign a filename to aFile
orTextFile
variable, useAssignFile
.Reset
andRewrite
work as they do in standard Pascal, or you can useAppend
to open a file to append to its end. The file must already exist. To close the file, useCloseFile
. Table 1.4 lists the I/O procedures Delphi provides.Table 1.4: File I/O Procedures and Functions RoutineDescriptionAppend
Open an existing file for appending.AssignFile
orAssign
Assign a filename to aFile
orTextFile
variable.BlockRead
Read data from a file.BlockWrite
Write data to a file.CloseFile
orClose
Close an open file.Eof
Returns True for end of file.Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Functions and Procedures
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterDelphi supports several extensions to standard Pascal functions and procedures. You can overload routines by declaring multiple routines with the same name, but different numbers or types of parameters. You can declare default values for parameters, thereby making the parameters optional. Almost everything in this section applies equally to functions and procedures, so the term routine is used for both.You can overload a routine name by declaring multiple routines with the same name, but with different arguments. To declare overloaded routines, use the
overload
directive, for example:function AsString(Int: Integer): string; overload; function AsString(Float: Extended): string; overload; function AsString(Float: Extended; MinWidth: Integer):string; overload; function AsString(Bool: Boolean): string; overload;
When you call an overloaded routine, the compiler must be able to tell which routine you want to call. Therefore, the overloaded routines must take different numbers or types of arguments. For example, using the declarations above, you can tell which function to call just by comparing argument types:Str := AsString(42); // call AsString(Integer) Str := AsString(42.0); // call AsString(Extended) Str := AsString(42.0, 8); // call AsString(Extended, Integer)
Sometimes, unit A will declare a routine, and unit B uses unit A, but also declares a routine with the same name. The declaration in unit B does not need theoverload
directive, but you might need to use unit A's name to qualify calls to A's version of the routine from unit B. A derived class that overloads a method from an ancestor class should use theoverload
directive.Sometimes, you can use default parameters instead of overloaded routines. For example, consider the following overloaded routines:function AsString(Float: Extended): string; overload; function AsString(Float: Extended; MinWidth: Integer):string; overload;
Most likely, the first overloaded routine converts its floating-point argument to a string using a predefined minimum width, say, 1. In fact, you might even write the firstAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Chapter 2: The Delphi Object Model
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterDelphi's support for object-oriented programming is rich and powerful. In addition to traditional classes and objects, Delphi also has interfaces (similar to those found in COM and Java), exception handling, and multithreaded programming. This chapter covers Delphi's object model in depth. You should already be familiar with standard Pascal and general principles of object-oriented programming.Think of a class as a record on steroids. Like a record, a class describes a type that comprises any number of parts, called fields. Unlike a record, a class can also contain functions and procedures (called methods), and properties. A class can inherit from another class, in which case it inherits all the fields, methods, and properties of the ancestor class.An object is a dynamic instance of a class. An object is always allocated dynamically, on the heap, so an object reference is like a pointer (but without the usual Pascal caret operator). When you assign an object reference to a variable, Delphi copies only the pointer, not the entire object. When your program finishes using an object, it must explicitly free the object. Delphi does not have any automatic garbage collection (but see the section Section 2.2," later in this chapter).For the sake of brevity, the term object reference is often shortened to object, but in precise terms, the object is the chunk of memory where Delphi stores the values for all the object's fields. An object reference is a pointer to the object. The only way to use an object in Delphi is through an object reference. An object reference usually comes in the form of a variable, but it might also be a function or property that returns an object reference.A class, too, is a distinct entity (as in Java, but unlike C++). Delphi's representation of a class is a read-only table of pointers to virtual methods and lots of information about the class. A class reference is a pointer to the table. (, describes in depth the layout of the class tables.) The most common use for a class reference is to create objects or to test the type of an object reference, but you can use class references in many other situations, including passing class references as routine parameters or returning a class reference from a function. The type of a class reference is called aAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Classes and Objects
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThink of a class as a record on steroids. Like a record, a class describes a type that comprises any number of parts, called fields. Unlike a record, a class can also contain functions and procedures (called methods), and properties. A class can inherit from another class, in which case it inherits all the fields, methods, and properties of the ancestor class.An object is a dynamic instance of a class. An object is always allocated dynamically, on the heap, so an object reference is like a pointer (but without the usual Pascal caret operator). When you assign an object reference to a variable, Delphi copies only the pointer, not the entire object. When your program finishes using an object, it must explicitly free the object. Delphi does not have any automatic garbage collection (but see the section Section 2.2," later in this chapter).For the sake of brevity, the term object reference is often shortened to object, but in precise terms, the object is the chunk of memory where Delphi stores the values for all the object's fields. An object reference is a pointer to the object. The only way to use an object in Delphi is through an object reference. An object reference usually comes in the form of a variable, but it might also be a function or property that returns an object reference.A class, too, is a distinct entity (as in Java, but unlike C++). Delphi's representation of a class is a read-only table of pointers to virtual methods and lots of information about the class. A class reference is a pointer to the table. (, describes in depth the layout of the class tables.) The most common use for a class reference is to create objects or to test the type of an object reference, but you can use class references in many other situations, including passing class references as routine parameters or returning a class reference from a function. The type of a class reference is called a metaclass.Example 2.1 shows several class declarations. A class declaration is a type declaration that starts with the keywordAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Interfaces
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterAn interface defines a type that comprises abstract virtual methods. Although a class inherits from a single base class, it can implement any number of interfaces. An interface is similar to an abstract class (that is, a class that has no fields and all of whose methods are abstract), but Delphi has extra magic to help you work with interfaces. Delphi's interfaces sometimes look like COM (Component Object Model) interfaces, but you don't need to know COM to use Delphi interfaces, and you can use interfaces for many other purposes.You can declare a new interface by inheriting from an existing interface. An interface declaration contains method and property declarations, but no fields. Just as all classes inherit from
TObject
, all interfaces inherit fromIUnknown
. TheIUnknown
interface declares three methods:_AddRef
,_Release
, andQueryInterface
. If you are familiar with COM, you will recognize these methods. The first two methods manage reference counting for the lifetime of the object that implements the interface. The third method accesses other interfaces an object might implement.When you declare a class that implements one or more interfaces, you must provide an implementation of all the methods declared in all the interfaces. The class can implement an interface's methods, or it can delegate the implementation to a property, whose value is an interface. The simplest way to implement the_AddRef
,_Release
, andQueryInterface
methods is to inherit them fromTInterfacedObject
or one of its derived classes, but you are free to inherit from any other class if you wish to define the methods yourself.A class implements each of an interface's methods by declaring a method with the same name, arguments, and calling convention. Delphi automatically matches the class's methods with the interface's methods. If you want to use a different method name, you can redirect an interface method to a method with a different name. The redirected method must have the same arguments and calling convention as the interface method. This feature is especially important when a class implements multiple interfaces with identical method names. See theAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Reference Counting
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThe previous section discusses how Delphi uses reference counting to manage the lifetime of interfaces. Strings and dynamic arrays also use reference counting to manage their lifetimes. The compiler generates appropriate code to keep track of when interface references, strings, and dynamic arrays are created and when the variables go out of scope and the objects, strings, and arrays must be destroyed.Usually, the compiler can handle the reference counting automatically, and everything works the way the you expect it to. Sometimes, though, you need to give a hint to the compiler. For example, if you declare a record that contains a reference counted field, and you use
GetMem
to allocate a new instance of the record, you must callInitialize
, passing the record as an argument. Before callingFreeMem
, you must callFinalize
.Sometimes, you want to keep a reference to a string or interface after the variable goes out of scope, that is, at the end of the block where the variable is declared. For example, maybe you want to associate an interface with each item in aTListView
. You can do this by explicitly managing the reference count. When storing the interface, be sure to cast it toIUnknown
, call_AddRef
, and cast theIUnknown
reference to a raw pointer. When extracting the data, type cast the pointer toIUnknown
. You can then use theas
operator to cast the interface to any desired type, or just let Delphi release the interface. For convenience, declare a couple of subroutines to do the dirty work for you, and you can reuse these subroutines any time you need to retain an interface reference. Example 2.15 shows an example of how you can store an interface reference as the data associated with a list view item.Example 2.15. Storing Interfaces in a List View// Cast an interface to a Pointer such that the reference // count is incremented and the interface will not be freed // until you call ReleaseIUnknown. function RefIUnknown(const Intf: IUnknown): Pointer; begin Intf._AddRef; // Increment the reference count. Result := Pointer(Intf); // Save the interface pointer. end; // Release the interface whose value is stored in the pointer P. procedure ReleaseIUnknown(P: Pointer); var Intf: IUnknown; begin Pointer(Intf) := P; // Delphi releases the interface when Intf goes out of scope. end; // When the user clicks the button, add an interface to the list. procedure TForm1.Button1Click(Sender: TObject); var Item: TListItem; begin Item := ListView1.Items.Add; Item.Caption := 'Stuff'; Item.Data := RefIUnknown(GetIntf as IUnknown); end; // When the list view is destroyed or the list item is destroyed // for any other reason, release the interface, too. procedure TForm1.ListView1Deletion(Sender: TObject; Item: TListItem); begin ReleaseIUnknown(Item.Data); end; // When the user selects the list view item, do something with the // associated interface. procedure TForm1.ListView1Click(Sender: TObject); var Intf: IMyInterface; begin Intf := IUnknown(ListView1.Selected.Data) as IMyInterface; Intf.DoSomethingUseful; end;
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Messages
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterYou should be familiar with Windows messages: user interactions and other events generate messages, which Windows sends to an application. An application processes messages one at a time to respond to the user and other events. Each kind of message has a unique number and two integer parameters. Sometimes a parameter is actually a pointer to a string or structure that contains more complex information. Messages form the heart of Windows event-driven architecture, and Delphi has a unique way of supporting Windows messages.In Delphi, every object—not only window controls—can respond to messages. A message has an integer identifier and can contain any amount of additional information. In the VCL, the
Application
object receives Windows messages and maps them to equivalent Delphi messages. In other words, Windows messages are a special case of more general Delphi messages.A Delphi message is a record where the first two bytes contain an integer message identifier, and the remainder of the record is programmer-defined. Delphi's message dispatcher never refers to any part of the message record past the message number, so you are free to store any amount or kind of information in a message record. By convention, the VCL always uses Windows-style message records (TMessage
), but if you find other uses for Delphi messages, you don't need to feel so constrained.To send a message to an object, fill in the message identifier and the rest of the message record and call the object'sDispatch
method. Delphi looks up the message number in the object's message table. The message table contains pointers to all the message handlers that the class defines. If the class does not define a message handler for the message number, Delphi searches the parent class's message table. The search continues until Delphi finds a message handler or it reaches theTObject
class. If the class and its ancestor classes do not define a message handler for the message number, Delphi calls the object'sDefaultHandler
method. Window controls in the VCL overrideAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Memory Management
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterDelphi manages the memory and lifetime of strings,
Variant
s, dynamic arrays, and interfaces automatically. For all other dynamically allocated memory, you—the programmer—are in charge. It's easy to be confused because it seems as though Delphi automatically manages the memory of components, too, but that's just a trick of the VCL.The VCL'sTComponent
class has two fancy mechanisms for managing object lifetimes, and they often confuse new Delphi programmers, tricking them into thinking that Delphi always manages object lifetimes. It's important that you understand exactly how components work, so you won't be fooled.Every component has an owner. When the owner is freed, it automatically frees the components that it owns. A form owns the components you drop on it, so when the form is freed, it automatically frees all the components on the form. Thus, you don't usually need to be concerned with managing the lifetime of forms and components.When a form or component frees a component it owns, the owner also checks whether it has a published field of the same name as the component. If so, the owner sets that field tonil
. Thus, if your form dynamically adds or removes components, the form's fields always contain valid object references or arenil
. Don't be fooled into thinking that Delphi does this for any other field or object reference. The trick works only for published fields (such as those automatically created when you drop a component on a form in the IDE's form editor), and only when the field name matches the component name.Memory management is thread-safe, provided you use Delphi's classes or functions to create the threads. If you go straight to the Windows API and theCreateThread
function, you must set theIsMultiThread
variable toTrue
. For more information, see .Ordinarily, when you construct an object, Delphi callsNewInstance
to allocate and initialize the object. You can overrideNewInstance
to change the way Delphi allocates memory for the object. For example, suppose you have an application that frequently uses doubly linked lists. Instead of using the general-purpose memory allocator for every node, it's much faster to keep a chain of available nodes for reuse. Use Delphi's memory manager only when the node list is empty. If your application frequently allocates and frees nodes, this special-purpose allocator can be faster than the general-purpose allocator. Example 2.17 shows a simple implementation of this scheme. (See for a thread-safe version of this class.)Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Old-Style Object Types
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterIn addition to class types, Delphi supports an obsolete type that uses the
object
keyword. Old-style objects exist for backward compatibility with Turbo Pascal, but they might be dropped entirely from future versions of Delphi.Old-styleobject
types are more like records than new-style objects. Fields in an old-styleobject
are laid out in the same manner as in records. If theobject
type does not have any virtual methods, there is no hidden field for the VMT pointer, for example. Unlike records,object
types can use inheritance. Derived fields appear after inherited fields. If a class declares a virtual method, its first field is the VMT pointer, which appears after all the inherited fields. (Unlike a new-style object, where the VMT pointer is always first becauseTObject
declares virtual methods.)An old-style object type can have private, protected, and public sections, but not published or automated sections. Because it cannot have a published section, an oldobject
type cannot have any runtime type information. An oldobject
type cannot implement interfaces.Constructors and destructors work differently in old-styleobject
types than in new-styleclass
types. To create an instance of an oldobject
type, call theNew
procedure. The newly allocated object is initialized to all zero. If you declare a constructor, you can call it as part of the call toNew
. Pass the constructor name and arguments as the second argument toNew
. Similarly, you can call a destructor when you callDispose
to free the object instance. The destructor name and arguments are the second argument toDispose
.You don't have to allocate an old-styleobject
instance dynamically. You can treat theobject
type as a record type and declareobject
-type variables as unit-level or local variables. Delphi automatically initializes string, dynamic array, andVariant
fields, but does not initialize other fields in theobject
instance.Unlike new-styleclass
types, exceptions in old-style constructors do not automatically cause Delphi to free a dynamically created object or call the destructor.Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Chapter 3: Runtime Type Information
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterDelphi's Integrated Development Environment (IDE) depends on information provided by the compiler. This information, called Runtime Type Information (RTTI), describes some aspects of classes and other types. It's not a full reflection system such as you find in Java, but it's more complete than type identifiers in C++. For ordinary, everyday use of Delphi, you can ignore the details of RTTI and just let Delphi do its thing. Sometimes, though, you need to look under the hood and understand exactly how RTTI works.The only difference between a published declaration and a public declaration is RTTI. Delphi stores RTTI for published fields, methods, and properties, but not for public, protected, or private declarations. Although the primary purpose of RTTI is to publish declarations for the IDE and for saving and loading .dfm files, the RTTI tables include other kinds of information. For example, virtual and dynamic methods, interfaces, and automated declarations are part of a class's RTTI. Most types also have RTTI called type information. This chapter explains all the details of RTTI.The Virtual Method Table (VMT) stores pointers to all the virtual methods declared for a class and its base classes. The layout of the VMT is the same as in most C++ implementations (including Borland C++ and C++ Builder) and is the same format required for COM, namely a list of pointers to methods. Each virtual method of a class or its ancestor classes has an entry in the VMT.Each class has a unique VMT. Even if a class does not define any of its own virtual methods, but only inherits methods from its base class, it has its own VMT that lists all the virtual methods it inherits. Because each VMT lists every virtual method, Delphi can compile calls to virtual methods as quick lookups in the VMT. Because each class has its own VMT, Delphi uses the VMT to identify a class. In fact, a class reference is really a pointer to a class's VMT, and the
ClassType
method returns a pointer to the VMT.In addition to a table of virtual methods, the VMT includes other information about a class, such as the class name, a pointer to the VMT for the base class, and pointers to many other RTTI tables. The other RTTI pointers appear before the first virtual method in the VMT. Example 3.1 shows a record layout that is equivalent to the VMT. The actual list of virtual methods begins after the end of theAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Virtual Method Table
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThe Virtual Method Table (VMT) stores pointers to all the virtual methods declared for a class and its base classes. The layout of the VMT is the same as in most C++ implementations (including Borland C++ and C++ Builder) and is the same format required for COM, namely a list of pointers to methods. Each virtual method of a class or its ancestor classes has an entry in the VMT.Each class has a unique VMT. Even if a class does not define any of its own virtual methods, but only inherits methods from its base class, it has its own VMT that lists all the virtual methods it inherits. Because each VMT lists every virtual method, Delphi can compile calls to virtual methods as quick lookups in the VMT. Because each class has its own VMT, Delphi uses the VMT to identify a class. In fact, a class reference is really a pointer to a class's VMT, and the
ClassType
method returns a pointer to the VMT.In addition to a table of virtual methods, the VMT includes other information about a class, such as the class name, a pointer to the VMT for the base class, and pointers to many other RTTI tables. The other RTTI pointers appear before the first virtual method in the VMT. Example 3.1 shows a record layout that is equivalent to the VMT. The actual list of virtual methods begins after the end of theTVmt
record. In other words, you can convert aTClass
class reference to a pointer to aTVmt
record by subtracting the size of the record, as shown in Example 3.1.Example 3.1. Structure of a VMTtype PVmt = ^TVmt; TVmt = record SelfPtr: TClass; // Points forward to the start // of the VMT // The following pointers point to other RTTI tables. If a class // does not have a table, the pointer is nil. Thus, most classes // have a nil IntfTable and AutoTable, for example. IntfTable: PInterfaceTable; // Interface table AutoTable: PAutoTable; // Automation table InitTable: PInitTable; // Fields needing finalization TypeInfo: PTypeInfo; // Properties & other info FieldTable: PFieldTable; // Published fields MethodTable: PMethodTable; // Published methods DynMethodTable: PDynMethodTable; // List of dynamic methods ClassName: PShortString; // Points to the class name InstanceSize: LongInt; // Size of each object, in bytes ClassParent: ^TClass; // Immediate base class // The following fields point to special virtual methods that // are inherited from TObject. SafeCallException: Pointer; AfterConstruction: Pointer; BeforeDestruction: Pointer; Dispatch: Pointer; DefaultHandler: Pointer; NewInstance: Pointer; FreeInstance: Pointer; Destroy: Pointer; // Here begin the virtual method pointers. // Each virtual method is stored as a code pointer, e.g., // VirtualMethodTable: array[1..Count] of Pointer; // But the compiler does not store the count of the number of // method pointers in the table. end; var Vmt: PVmt; begin // To get a PVmt pointer from a class reference, cast the class // reference to the PVmt type and subtract the size of the TVmt // record. This is easily done with the Dec procedure: Vmt := PVmt(SomeObject.ClassType); Dec(Vmt);
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Published Declarations
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThe only difference between a published declaration and a public one is that a published declaration tells the compiler to store information in the VMT. Only certain kinds of information can be stored, so published declarations face a number of restrictions:
- In order to declare any published fields, methods, or properties, a class must have RTTI enabled by using the
$M+
directive or by inheriting from a class that has RTTI. (See , for details.) - Fields must be of class type (no other types are allowed). The class type must have RTTI enabled.
- Array properties cannot be published. The type of a published property cannot be a pointer, record, or array. If it is a set type, it must be small enough to be stored in an integer. In the current release of Delphi, that means the set can have no more than 32 members.
- The published section cannot contain more than one overloaded method with each name. You can overload methods, but only one of the overloaded methods can be published.
TheClasses
unit declaresTPersistent
with the$M+
directive.TPersistent
is usually used as a base class for all Delphi classes that need published declarations. Note thatTComponent
inherits fromTPersistent
.Delphi stores the names and addresses of published methods in a class's RTTI. The IDE uses this information to store the values of event properties in a .dfm file. In the IDE, each event property is eithernil
or contains a method reference. The method reference includes a pointer to the method's entry point. (At design time, the IDE has no true entry point, so it makes one up. At runtime, your application uses the method's real entry point.) To store the value of an event property, Delphi looks up the method address in the class's RTTI, finds the corresponding method name, and stores the name in the .dfm file. To load a .dfm file, Delphi reads the method name and looks up the corresponding method address from the class's RTTI.A class's RTTI stores only the published methods for that class, and not for any ancestor classes. Thus, to look up a method name or address, the lookup might fail for a derived class, in which case, the lookup continues with the base class. TheAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - The TypInfo Unit
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThe
TypInfo
unit declares several types and functions that give you easy access to the published properties of an object and other information. The Object Inspector relies on this information to perform its magic. You can obtain a list of the published properties of a class and get the name and type for each property. Given an object reference, you can get or set the value of any published property.TheTypeInfo
function returns a pointer to a type information record, but if you don't use theTypInfo
unit, you cannot access anything in that record and must instead treat the result as an untypedPointer
. TheTypInfo
unit defines the real type, which isPTypeInfo
, that is, a pointer to aTTypeInfo
record. The type information record contains a type kind and the name of the type. The type kind is an enumerated value that tells you what kind of type it is: integer, floating point, string, etc.Some types have additional type data, as returned by theGetTypeData
function, which returns aPTypeData
pointer. You can use the type data to get the names of an enumerated literal, the limits of an ordinal subrange, and more. Table 3.1 describes the data for each type kind.Table 3.1: Type Kinds and Their Data TTypeKind
LiteralAssociated DatatkArray
No associated data.tkChar
Limits of character subrange.tkClass
Class reference, parent class, unit where class is declared, and published properties.Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Virtual and Dynamic Methods
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThe VMT stores a list of pointers for virtual methods and another table in the VMT, which this section refers to as the dynamic method table, lists both dynamic methods and message handlers.The compiler generates a small negative number for each dynamic method. This negative number is just like a message number for a message handler. To avoid conflicts with message handlers, the compiler does not let you compile a message handler whose message number falls into the range of dynamic method numbers. Once the compiler has done its work, though, any distinction between dynamic methods and message handlers is lost. They both sit in the same table and nothing indicates whether one entry is for a dynamic method and another is for a message handler.The dynamic method table lists only the dynamic methods and message handlers that a class declares; it does not include any methods inherited from ancestor classes. The dynamic method table starts with a 2-byte count of the number of dynamic methods and message handlers, followed by a list of 2-byte method numbers, followed by a list of 4-byte method pointers. The dynamic method table is organized in this fashion (instead of having a list of records, where each record has a method number and pointer) to speed up searching for a method number. Example 3.6 shows the logical layout of a dynamic method table. As with the other tables, you cannot compile this record, because it is not real Pascal, just a description of what a dynamic method table looks like.Example 3.6. The Layout of a Dynamic Method Table
type TDynMethodTable = packed record Count: Word; Indexes: packed array[1..Count] of SmallInt; Addresses: packed array[1..Count] of Pointer; end;
Dispatching a message or calling a dynamic method requires a lookup of the method or message number in theIndexes
array. The table is not sorted and the lookup is linear. Once a match is found, the method at the corresponding address is invoked. If the method number is not found, the search continues with the immediate base class.The only time you should even consider using dynamic methods is when all of the following conditions apply:Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Initialization and Finalization
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterWhen Delphi constructs an object, it automatically initializes strings, dynamic arrays, interfaces, and
Variant
s. When the object is destroyed, Delphi must decrement the reference counts for strings, interfaces, dynamic arrays, and freeVariant
s and wide strings. To keep track of this information, Delphi uses initialization records as part of a class's RTTI. In fact, every record and array that requires finalization has an associated initialization record, but the compiler hides these records. The only ones you have access to are those associated with an object's fields.A VMT points to an initialization table. The table contains a list of initialization records. Because arrays and records can be nested, each initialization record contains a pointer to another initialization table, which can contain initialization records, and so on. An initialization table uses aTTypeKind
field to keep track of whether it is initializing a string, a record, an array, etc.An initialization table begins with the type kind (1 byte), followed by the type name as a short string, a 4-byte size of the data being initialized, a 4-byte count for initialization records, and then an array of zero or more initialization records. An initialization record is just a pointer to a nested initialization table, followed by a 4-byte offset for the field that must be initialized. Example 3.7 shows the logical layout of the initialization table and record, but the declarations depict the logical layout without being true Pascal code.Example 3.7. The Layout of the Initialization Table and Recordtype { Initialization/finalization record } PInitTable = ^TInitTable; TInitRecord = packed record InitTable: ^PInitTable; Offset: LongWord; // Offset of field in object end; { Initialization/finalization table } TInitTable = packed record {$MinEnumSize 1} // Ensure that TypeKind takes up 1 byte. TypeKind: TTypeKind; TypeName: packed ShortString; DataSize: LongWord; Count: LongWord; // If TypeKind=tkArray, Count is the array size, but InitRecords // has only one element; if the type kind is tkRecord, Count is the // number of record members, and InitRecords[] has a // record for each member. For all other types, Count=0. InitRecords: array[1..Count] of TInitRecord; end;
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Automated Methods
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThe automated section of a class declaration is now obsolete because it is easier to create a COM automation server with Delphi's type library editor, using interfaces. Nonetheless, the compiler currently supports automated declarations for backward compatibility. A future version of the compiler might drop support for automated declarations.The
OleAuto
unit tells you the details of the automated method table: The table starts with a 2-byte count, followed by a list of automation records. Each record has a 4-byte dispid (dispatch identifier), a pointer to a short string method name, 4-bytes of flags, a pointer to a list of parameters, and a code pointer. The parameter list starts with a 1-byte return type, followed by a 1-byte count of parameters, and ends with a list of 1-byte parameter types. The parameter names are not stored. Example 3.8 shows the declarations for the automated method table.Example 3.8. The Layout of the Automated Method Tableconst { Parameter type masks } atTypeMask = $7F; varStrArg = $48; atByRef = $80; MaxAutoEntries = 4095; MaxAutoParams = 255; type TVmtAutoType = Byte; { Automation entry parameter list } PAutoParamList = ^TAutoParamList; TAutoParamList = packed record ReturnType: TVmtAutoType; Count: Byte; Types: array[1..Count] of TVmtAutoType; end; { Automation table entry } PAutoEntry = ^TAutoEntry; TAutoEntry = packed record DispID: LongInt; Name: PShortString; Flags: LongInt; { Lower byte contains flags } Params: PAutoParamList; Address: Pointer; end; { Automation table layout } PAutoTable = ^TAutoTable; TAutoTable = packed record Count: LongInt; Entries: array[1..Count] of TAutoEntry; end;
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Interfaces
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterAny class can implement any number of interfaces. The compiler stores a table of interfaces as part of the class's RTTI. The VMT points to the table of interfaces, which starts with a 4-byte count, followed by a list of interface records. Each interface record contains the GUID, a pointer to the interface's VMT, the offset to the interface's hidden field, and a pointer to a property that implements the interface with the
implements
directive. If the offset is zero, the interface property (calledImplGetter
) must be non-nil
, and if the offset is not zero,ImplGetter
must benil
. The interface property can be a reference to a field, a virtual method, or a static method, following the conventions of a property reader (which is described earlier in this chapter, under Section 3.2.3"). When an object is constructed, Delphi automatically checks all the interfaces, and for each interface with a non-zeroIOffset
, the field at that offset is set to the interface'sVTable
(a pointer to its VMT). Delphi defines the types for the interface table, unlike the other RTTI tables, in theSystem
unit. These types are shown in Example 3.9.Example 3.9. Type Declarations for the Interface Tabletype PInterfaceEntry = ^TInterfaceEntry; TInterfaceEntry = record IID: TGUID; VTable: Pointer; IOffset: Integer; ImplGetter: Integer; end; PInterfaceTable = ^TInterfaceTable; TInterfaceTable = record EntryCount: Integer; // Declare the type with the largest possible size, // but the true size of the array is EntryCount elements. Entries: array[0..9999] of TInterfaceEntry; end;
TObject
implements several methods for accessing the interface table. See for the details of theGetInterface
,GetInterfaceEntry
, andGetInterfaceTable
methods.Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Exploring RTTI
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThis chapter introduces you to a class's virtual method table and runtime type information. To better understand how Delphi stores and uses RTTI, you should explore the tables on your own. The code that accompanies this book on the O'Reilly web site includes the Vmt.exe program. The
VmtInfo
unit defines a collection of interfaces that exposes the structure of all the RTTI tables. TheVmtImpl
unit defines classes that implement these interfaces. You can read the source code for theVmtImpl
unit or just explore the Vmt program. See theVmtForm
unit to add types that you want to explore, or to change the type declarations.You can also use theVmtInfo
interfaces in your own programs when you need access to the RTTI tables. For example, you might write your own object persistence library where you need access to a field class table to map class names to class references.The interfaces are self-explanatory. Because they use Delphi's automatic reference counting, you don't need to worry about memory management, either. To create an interface, call one of the following functions:function GetVmtInfo(ClassRef: TClass): IVmtInfo; overload; function GetVmtInfo(ObjectRef: TObject): IVmtInfo; overload; function GetTypeInfo(TypeInfo: PTypeInfo): ITypeInfo;
Use theIVmtInfo
interface and its related interfaces to examine and explore the rich world of Delphi's runtime type information. For example, take a look at theTFont
class, shown in Example 3.10.Example 3.10. Declaration of the TFont Classtype TFont = class(TGraphicsObject) private FColor: TColor; FPixelsPerInch: Integer; FNotify: IChangeNotifier; procedure GetData(var FontData: TFontData); procedure SetData(const FontData: TFontData); protected procedure Changed; override; function GetHandle: HFont; function GetHeight: Integer; function GetName: TFontName; function GetPitch: TFontPitch; function GetSize: Integer; function GetStyle: TFontStyles; function GetCharset: TFontCharset; procedure SetColor(Value: TColor); procedure SetHandle(Value: HFont); procedure SetHeight(Value: Integer); procedure SetName(const Value: TFontName); procedure SetPitch(Value: TFontPitch); procedure SetSize(Value: Integer); procedure SetStyle(Value: TFontStyles); procedure SetCharset(Value: TFontCharset); public constructor Create; destructor Destroy; override; procedure Assign(Source: TPersistent); override; property FontAdapter: IChangeNotifier read FNotify write FNotify; property Handle: HFont read GetHandle write SetHandle; property PixelsPerInch: Integer read FPixelsPerInch write FPixelsPerInch; published property Charset: TFontCharset read GetCharset write SetCharset; property Color: TColor read FColor write SetColor; property Height: Integer read GetHeight write SetHeight; property Name: TFontName read GetName write SetName; property Pitch: TFontPitch read GetPitch write SetPitch default fpDefault; property Size: Integer read GetSize write SetSize stored False; property Style: TFontStyles read GetStyle write SetStyle; end;
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Chapter 4: Concurrent Programming
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThe future of programming is concurrent programming. Not too long ago, sequential, command-line programming gave way to graphical, event-driven programming, and now single-threaded programming is yielding to multithreaded programming.Whether you are writing a web server that must handle many clients simultaneously or writing an end-user application such as a word processor, concurrent programming is for you. Perhaps the word processor checks for spelling errors while the user types. Maybe it can print a file in the background while the user continues to edit. Users expect more today from their applications, and only concurrent programming can deliver the necessary power and flexibility.Delphi Pascal includes features to support concurrent programming—not as much support as you find in languages such as Ada, but more than in most traditional programming languages. In addition to the language features, you can use the Windows API and its semaphores, threads, processes, pipes, shared memory, and so on. This chapter describes the features that are unique to Delphi Pascal and explains how to use Delphi effectively to write concurrent programs. If you want more information about the Windows API and the details of how Windows handles threads, processes, semaphores, and so on, consult a book on Windows programming, such as Inside Windows NT, second edition, by David Solomon (Microsoft Press, 1998).This section provides an overview of multithreaded programming in Windows. If you are already familiar with threads and processes in Windows, you can skip this section and continue with the next section, Section 4.2."A thread is a flow of control in a program. A program can have many threads, each with its own stack, its own copy of the processor's registers, and related information. On a multiprocessor system, each processor can run a separate thread. On a uniprocessor system, Windows creates the illusion that threads are running concurrently, though only one thread at a time gets to run.A process is a collection of threads all running in a single address space. Every process has at least one thread, called theAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Threads and Processes
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThis section provides an overview of multithreaded programming in Windows. If you are already familiar with threads and processes in Windows, you can skip this section and continue with the next section, Section 4.2."A thread is a flow of control in a program. A program can have many threads, each with its own stack, its own copy of the processor's registers, and related information. On a multiprocessor system, each processor can run a separate thread. On a uniprocessor system, Windows creates the illusion that threads are running concurrently, though only one thread at a time gets to run.A process is a collection of threads all running in a single address space. Every process has at least one thread, called the main thread. Threads in the same process can share resources such as open files and can access any valid memory address in the process's address space. You can think of a process as an instance of an application (plus any DLLs that the application loads).Threads in a process can communicate easily because they can share variables. Critical sections protect threads from stepping on each others' toes when they access shared variables. (Read the section Section 4.1.2" later in this chapter, for details about critical sections.)You can send a Windows message to a particular thread, in which case the receiving thread must have a message loop to handle the message. In most cases, you will find it simpler to let the main thread handle all Windows messages, but feel free to write your own message loop for any thread that needs it.Separate processes can communicate in a variety of ways, such as messages, mutexes (short for mutual exclusions), semaphores, events, memory-mapped files, sockets, pipes, DCOM, CORBA, and so on. Most likely, you will use a combination of methods. Separate processes do not share ordinary memory, and you cannot call a function or procedure from one process to another, although several remote procedure call mechanisms exist, such as DCOM and CORBA. Read more about processes and how they communicate in the section Section 4.5" later in this chapter.Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - The TThread Class
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThe easiest way to create a multithreaded application in Delphi is to write a thread class that inherits from
TThread
. TheTThread
class is not part of the Delphi language, but is declared in theClasses
unit. This section describes the class because it is so important in Delphi programming.Override theExecute
method to perform the thread's work. WhenExecute
finishes, the thread finishes. Any thread can create any other thread simply by creating an instance of the custom thread class. Each instance of the class runs as a separate thread, with its own stack.When you need to protect a VCL access or call to the Windows GDI, you can use theSynchronize
method. This method takes a procedure as an argument and calls that procedure in a thread-safe manner. The procedure takes no arguments.Synchronize
suspends the current thread and has the main thread call the procedure. When the procedure finishes, control returns to the current thread. Because all calls toSynchronize
are handled by the main thread, they are protected against race conditions. If multiple threads callSynchronize
at the same time, they must wait in line, and one thread at a time gets access to the main thread. This process is often called serializing because parallel method calls are changed to serial method calls.When writing the synchronized procedure, remember that it is called from the main thread, so if you need to know the true thread ID, read theThreadID
property instead of calling the Windows APIGetCurrentThreadID
function.For example, suppose you want to write a text editor that can print in the background. That is, the user asks to print a file, and the program copies the file's contents (to avoid race conditions when the user edits the file while the print operation is still active) and starts a background thread that formats the file and queues it to the printer.Printing a file involves the VCL, so you must take care to synchronize every VCL access. At first this seems problematic, because almost everything the thread does accesses the VCL'sPrinter
object. Upon closer inspection, you can see that you have several ways to reduce the interaction with the VCL.Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - The BeginThread and EndThread Functions
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterIf you don't want to write a class, you can use
BeginThread
andEndThread
. They are wrappers for the Win32 API callsCreateThread
andExitThread
functions, but you must use Delphi's functions instead of the Win32 API directly. Delphi keeps a global flag,IsMultiThread
, which is True if your program callsBeginThread
or starts a thread usingTThread
. Delphi checks this flag to ensure thread safety when allocating memory. If you call theCreateThread
function directly, be sure to setIsMultiThread
to True.Note that using theBeginThread
andEndThread
functions does not give you the convenience of theSynchronize
method. If you want to use these functions, you must arrange for your own serialized access to the VCL.TheBeginThread
function is almost exactly the same asCreateThread
, but the parameters use Delphi types. The thread function takes aPointer
parameter and returns anInteger
result, which is the exit code for the thread. TheEndThread
function is just like the WindowsExitThread
function: it terminates the current thread. See , for details about these functions. For an example of usingBeginThread
, see the section Section 4.6" at the end of this chapter.Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Thread Local Storage
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterWindows has a feature where each thread can store limited information that is private to the thread. Delphi makes it easy to use this feature, called thread local storage, without worrying about the limitations imposed by Windows. Just declare a variable using
threadvar
instead ofvar
. Ordinarily, Delphi creates a single instance of a unit-level variable and shares that instance among all threads. If you usethreadvar
, however, Delphi creates a unique, separate instance of the variable in each thread.You must declarethreadvar
variables at the unit level, not local to a function or procedure. Each thread has its own stack, so local variables are local to a thread anyway. Becausethreadvar
variables are local to the thread and that thread is the only thread that can access the variables, you don't need to use critical sections to protect them.If you use theTThread
class, you should use fields in the class for thread local variables because they incur less overhead than usingthreadvar
variables. If you need thread local storage outside the thread object, or if you are using theBeginThread
function, usethreadvar
.Be careful when using thethreadvar
variables in a DLL. When the DLL is unloaded, Delphi frees allthreadvar
storage before it calls theDllProc
or the finalization sections in the DLL.Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Processes
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterDelphi has some support for multithreaded applications, but if you want to write a system of cooperating programs, you must resort to the Windows API. Each process runs in its own address space, but you have several choices for how processes can communicate with each other:
- Messages
- Any thread can send a message to any other thread in the same process or in a different process. A typical use for interprocess messages is when one application is trying to control the user interface of another application.
- Events
- An event is a trigger that one thread can send to another. The threads can be in the same or different processes. One thread waits on the event, and another thread sets the event, which wakes up the waiting thread. Multiple threads can wait for the same event, and you can decide whether setting the event wakes up all waiting threads or only one thread.
- Mutexes
- A mutex (short for mutual exclusion) is a critical section that can be shared among multiple processes.
- Semaphores
- A semaphore shares a count among multiple processes. A thread in a process waits for the semaphore, and when the semaphore is available, the thread decrements the count. When the count reaches zero, threads must wait until the count is greater than zero. A thread can release a semaphore to increment the count. Where a mutex lets one thread at a time gain access to a shared resource, a semaphore gives access to multiple threads, where you control the number of threads by setting the semaphore's maximum count.
- Pipes
- A pipe is a special kind of file, where the file contents are treated as a queue. One process writes to one end of the queue, and another process reads from the other end. Pipes are a powerful and simple way to send a stream of information from one process to another—on one system or in a network.
- Memory-mapped files
- The most common way to share data between processes is to use memory-mapped files. A memory-mapped file, as the name implies, is a file whose contents are mapped into a process's virtual address space. Once a file is mapped, the memory region is just like normal memory, except that any changes are stored in the file, and any changes in the file are seen in the process's memory. Multiple processes can map the same file and thereby share data. Note that each process maps the file to a different location in its individual address space, so you can store data in the shared memory, but not pointers.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Futures
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterWriting a concurrent program can be more difficult than writing a sequential program. You need to think about race conditions, synchronization, shared variables, and more. Futures help reduce the intellectual clutter of using threads. A future is an object that promises to deliver a value sometime in the future. The application does its work in the main thread and calls upon futures to fetch or compute information concurrently. The future does its work in a separate thread, and when the main thread needs the information, it gets it from the future object. If the information isn't ready yet, the main thread waits until the future is done. Programming with futures hides much of the complexity of multithreaded programming.Define a future class by inheriting from
TFuture
and overriding theCompute
method. TheCompute
method does whatever work is necessary and returns its result as aVariant
. Try to avoid synchronization and accessing shared variables during the computation. Instead, let the main thread handle communication with other threads or other futures. Example 4.20 shows the declaration for theTFuture
class.Example 4.20. Declaration of the TFuture Classtype TFuture = class private fExceptObject: TObject; fExceptAddr: Pointer; fHandle: THandle; fTerminated: Boolean; fThreadID: LongWord; fTimeOut: DWORD; fValue: Variant; function GetIsReady: Boolean; function GetValue: Variant; protected procedure RaiseException; public constructor Create; destructor Destroy; override; procedure AfterConstruction; override; function Compute: Variant; virtual; abstract; function HasException: Boolean; procedure Terminate; property Handle: THandle read fHandle; property IsReady: Boolean read GetIsReady; property Terminated: Boolean read fTerminated write fTerminated; property ThreadID: LongWord read fThreadID; property TimeOut: DWORD read FTimeOut write fTimeOut; property Value: Variant read GetValue; end;
The constructor initializes the future object, but refrains from starting the thread. Instead,Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Chapter 5: Language Reference
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThis is the big chapter—the language reference. Here you can find every keyword, directive, function, procedure, variable, class, method, and property that is part of Delphi Pascal. Most of these items are declared in the
System
unit, but some are declared inSysInit
. Both units are automatically included in every Delphi unit. Remember that Delphi Pascal is not case sensitive, with the sole exception of theRegister
procedure (to ensure compatibility with C++ Builder).For your convenience, runtime error numbers in this chapter are followed by exception class names. TheSysUtils
unit maps the errors to exceptions. The exceptions are not part of the Delphi language proper, but theSysUtils
unit is used in almost every Delphi project, so the exceptions are more familiar to Delphi programmers than the error numbers.Each item falls into one of a number of categories, which are described in the following list:- Directive
- A directive is an identifier that has special meaning to the compiler, but only in a specific context. Outside of that context, you are free to use directive names as ordinary identifiers. Delphi's source editor tries to help you by showing directives in boldface when they are used in context and in plain text when used as ordinary identifiers. The editor is not always correct, though, because some of the language rules for directives are more complex than the simple editor can handle.
- Function
- Not all functions are really functions; some are built into the compiler. The difference is not usually important because the built-in functions look and act like normal functions, but you cannot take the address of a built-in function. The descriptions in this chapter tell you which functions are built-in and which are ordinary.
- Interface
- A declaration of a standard
interface
.
- Keyword
- A keyword is a reserved identifier whose meaning is determined by the Delphi compiler. You cannot use the keyword as a variable, method, or type name.
- Procedure
- As with functions, some procedures are built into the compiler and are not ordinary procedures, so you cannot take their addresses. Some procedures (such as
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Chapter 6: System Constants
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThis chapter is separate from , to make it easier to find the information you need. Instead of cluttering with all the individual identifiers, this chapter organizes the system constants logically. All the constant literals described in this chapter are defined in the
System
unit, so they are available at all times.TheVarType
function returns the type code of aVariant
. The type code is a small integer that contains a type identifier with the optional modifiersvarArray
andvarByRef
. Any type exceptvarEmpty
andvarNull
can have thevarArray
modifier. Delphi automatically takes care of thevarByRef
modifier. For more information, see the discussion of theVariant
type in . Table 6.1 lists the type identifiers, and Table 6.2 lists the optional modifiers.Table 6.1: Variant Type Identifiers LiteralValueDescriptionvarEmpty
$0000Variant
not assignedvarNull
$0001Null
valuevarSmallint
$000216-bit, signed integervarInteger
$000332-bit, signed integervarSingle
$000432-bit floating-point numbervarDouble
$0005Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Variant Type Codes
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThe
VarType
function returns the type code of aVariant
. The type code is a small integer that contains a type identifier with the optional modifiersvarArray
andvarByRef
. Any type exceptvarEmpty
andvarNull
can have thevarArray
modifier. Delphi automatically takes care of thevarByRef
modifier. For more information, see the discussion of theVariant
type in . Table 6.1 lists the type identifiers, and Table 6.2 lists the optional modifiers.Table 6.1: Variant Type Identifiers LiteralValueDescriptionvarEmpty
$0000Variant
not assignedvarNull
$0001Null
valuevarSmallint
$000216-bit, signed integervarInteger
$000332-bit, signed integervarSingle
$000432-bit floating-point numbervarDouble
$000564-bit floating-point numbervarCurrency
$000664-bit fixed point number with four decimal placesvarDate
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Open Array Types
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterWhen a subroutine parameter is a variant open array (
array
of
const
), Delphi passes the array argument by converting each array element to aTVarRec
record. Each record'sVType
member identifies the member's type. For more information, see the discussion of thearray
keyword in . Table 6.3 lists theVType
values for aTVarRec
record.Table 6.3: Possible Values for TVarRec.VType LiteralValueElement TypevtInteger
0Integer
vtBoolean
1Boolean
vtChar
2Char
vtExtended
3Extended
vtString
4ShortString
vtPointer
5Pointer
vtPChar
6PChar
vtObject
7TObject
vtClass
8TClass
vtWideChar
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Virtual Method Table Offsets
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapter, describes the format of a class's virtual method table (VMT). Delphi does not provide a convenient record for accessing a VMT, but it does define the offsets (in bytes) of the various parts of a VMT. The offsets are relative to the class reference (
TClass
). Note that the offsets change from one version of Delphi to the next. Table 6.4 lists the offset names and values.Table 6.4: Offsets in a Class's Virtual Method Table LiteralValueDescriptionvmtSelfPtr
-76Pointer to the start of the VMTvmtIntfTable
-72Pointer to the interface tablevmtAutoTable
-68Pointer to the automation tablevmtInitTable
-64Pointer to the initialization and finalization tablevmtTypeInfo
-60Pointer to the class'sTTypeInfo
recordvmtFieldTable
-56Pointer to the published field tablevmtMethodTable
-52Pointer to the published method tablevmtDynamicTable
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Runtime Error Codes
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterDelphi does not export literals for its runtime errors. Instead, it defines a number of literals in the implementation section of the
System
andSysInit
units, and other error codes are defined implicitly in theSystem
code. For your convenience, the following tables list all the runtime error numbers that are built into Delphi'sSystem
unit.The internal error codes are used internally to theSystem
unit. If you implement anErrorProc
procedure, these are the error codes you must interpret.TheErrorProc
procedure in theSysUtils
unit maps internal error codes to exceptions. Table 6.5 lists the internal error codes and the exception class that theSysUtils
unit uses for each error.If you do not use theSysUtils
unit, or if an error arises before theSysUtils
unit is initialized or after it is finalized, theErrorProc
procedures in theSystem
unit maps internal error codes to external error codes. Some memory and pointer errors do not produce immediate access violations, but instead corrupt memory in a way that does not let Delphi shut down cleanly. These errors often manifest themselves as runtime errors when the program exits. Table 6.6 lists the external error codes.Table 6.7 lists the I/O error codes, which are reported with an internal error code of zero. Note that any Windows error code can also be an I/O error code. (SysUtils
raisesEInOutError
for all I/O errors.)Table 6.5: Internal Error Codes Error NumberDescriptionSysUtils Exception Class0I/O error; see Table 6.7EInOutError
1Out of memoryEOutOfMemory
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Chapter 7: Operators
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThis chapter describes the symbolic operators, their precedence and semantics. , describes the named operators in depth, but not the symbolic operators, because it's hard to alphabetize symbols. This chapter describes the symbolic operators in depth.Delphi defines the following operators. Each line lists the operators with the same precedence; operator precedence is highest at the start of the list and lowest at the bottom:
@ not ^ + -
(unary operators)
* / div mod and shl shr as
+ - or xor
> < >= <= <> = in is
AVariant
can be a string, number, Boolean, or other value. If an expression mixesVariant
and non-Variant
operands, Delphi converts all operands toVariant
. If theVariant
types do not match, Delphi casts one or both operands as needed for the operation, and produces aVariant
result. See theVariant
type in for more information aboutVariant
type casts.-
@
operator - Returns the address of its operand. The address of a variable or ordinary subroutine is a
Pointer
, or if the$T
or$TypedAddress
compiler directive is used, a typed pointer (e.g.,PInteger
).The address of a method has two parts: a code pointer and a data pointer, so you can assign a method address only to a variable of the appropriate type, and not to a genericPointer
variable. If you take the address of a method using a class reference instead of an object reference, the@
operator returns just the code pointer. For example:
Ptr := @TObject.Free;
When assigning the address of a subroutine to a procedural-type variable, if the subroutine's signature matches the variable's type, you do not need the@
operator. Delphi can tell from the assignment that you are assigning the subroutine's address. If the types do not match, use the@
operator to take the subroutine's address. For example, to assign an error procedure to Delphi'sErrorProc
variable, which is an untypedPointer
, you must use theAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! -
- Unary Operators
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapter
-
@
operator - Returns the address of its operand. The address of a variable or ordinary subroutine is a
Pointer
, or if the$T
or$TypedAddress
compiler directive is used, a typed pointer (e.g.,PInteger
).The address of a method has two parts: a code pointer and a data pointer, so you can assign a method address only to a variable of the appropriate type, and not to a genericPointer
variable. If you take the address of a method using a class reference instead of an object reference, the@
operator returns just the code pointer. For example:
Ptr := @TObject.Free;
When assigning the address of a subroutine to a procedural-type variable, if the subroutine's signature matches the variable's type, you do not need the@
operator. Delphi can tell from the assignment that you are assigning the subroutine's address. If the types do not match, use the@
operator to take the subroutine's address. For example, to assign an error procedure to Delphi'sErrorProc
variable, which is an untypedPointer
, you must use the@
operator:function GetHeight: Integer; begin Result := 42; end; procedure HandleError(Code: Integer; Addr: Pointer); begin ... end; type TIntFunction = function: Integer; var F: TIntFunction; I: Integer; begin // Do not need @ operator. Assign GetHeight address to F. F := GetHeight; // Call GetHeight. I := F; // Need the @ operator because the type of ErrorProc is Pointer. ErrorProc := @HandleError; // Call GetHeight via F, and compare integer results if F = GetHeight then ShowMessage('Heights are equal'); // Compare pointers to learn whether F points to GetHeight. if @F = @GetHeight then ShowMessage('Function pointers are equal');
You can also use the@
operator on the left-hand side of an assignment to assign a procedural pointer that does not have the correct type. For example, the value returned from the Windows API functionGetProcAddress
is a procedure address, but it has the genericPointer
type. A type cast is usually the best way to solve this problem, but some people prefer to use theAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! -
- Multiplicative Operators
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapter
-
*
operator - The multiplication operator is also the set intersection operator. When the operands are sets of the same type, the result is a set that contains all members that are in both operand sets. For example:
var A, B, C: set of 0..7; begin A := [0, 1, 2, 3]; B := [2, 3, 4, 5, 6]; C := A * B; // C := [2,3] A := [1, 0]; C := A * B; // C := []
-
/
operator - Floating-point division. Dividing two integers converts the operands to floating point. Division by zero raises runtime error 7.
-
Div
operator - Integer division. See for details.
-
Mod
operator - Modulus (remainder). See for details.
-
And
operator - Logical or bitwise conjunction. See for details.
-
Shl
operator - Left shift. See for details.
-
Shr
operator - Right shift. See for details.
-
As
operator - Type check and cast of object and interface references. See for details.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! -
- Additive Operators
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapter
-
+
operator - Addition, string concatenation, set union, and
PChar
pointer offset. If both operands are strings, the result is the string concatenation of the two strings. If the operands are sets with the same type, the result is a set that contains all the elements in both operand sets.If one operand has typePChar
orPWideChar
and the other is an integer, the result is a pointer whose value is offset by the integer in character units (not bytes). The integer offset can be positive or negative, but the resulting pointer must lie within the same character array as thePChar
orPWideChar
operand. For example:
var A, B, C: set of 0..7; Text: array[0..5] of Char; P, Q: PChar; begin A := [0, 1, 2]; B := [5, 7, 4]; C := A + B; // C := [0, 1, 2, 4, 5, 7]; Text := 'Hello'; P := Text; Q := P + 3; WriteLn(Q); // Writes 'lo' WriteLn('Hi' + Q); // Writes 'Hilo'
-
-
operator - Subtraction, set difference,
PChar
pointer offset, andPChar
difference. If the operands are sets with the same type, the result is a set that contains the elements that are in the left-hand operand set but not in the right-hand operand.If one operand has typePChar
orPWideChar
and the other is an integer, the result is a pointer whose value is offset by the integer, in character units (not bytes). The integer offset can be positive or negative, but the resulting pointer must lie within the same character array as thePChar
orPWideChar
operand.If both operands are of typePChar
orPWideChar
and both pointers point into the same character array, the difference is the number of characters between the two pointers. The difference is positive if the left-hand operand points to a later character than the right-hand operand, and the difference is negative if the left-hand operand points to a character that appears earlier in the character array:
var A, B, C: set of 0..7; Text: array[0..5] of Char; P, Q: PChar; begin A := [0, 1, 2, 4, 5, 6]; B := [5, 7, 4]; C := A - B; // C := [0, 1, 2, 6]; C := B - A; // C := [7]; Text := 'Hello'; P := Text; Q := P + 3; WriteLn(Q); // Writes 'lo' P := Q - 2; WriteLn(P); // Writes 'ello' WriteLn(Q - P); // Writes 2
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! -
- Comparison Operators
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThe comparison operators let you compare numbers, strings, sets, and pointers.
-
=
operator - Equality. Compare numbers, strings, pointers, and sets for exact equality.Note that comparing floating-point numbers for exact equality rarely works the way you think it should. You should use a fuzzy comparison that accounts for floating-point imprecision. See D. E. Knuth's Seminumerical Algorithms (third edition, Addison Wesley Longman, 1998), section 4.2.2, for an excellent discussion of this problem and suggested solutions.
-
<>
operator - Inequality. Compare numbers, strings, or character pointers, and sets. Returns the logical negation of the
=
operator.
-
>
operator - Greater than; compares numbers, strings, or character pointers. When you compare
PChar
orPWideChar
values, you are comparing pointers. The operator returns True if the left-hand operand points to a later position than the right-hand operand. Both operands must point to locations in the same character array. Strings are compared by comparing their characters' ordinal values. (TheSysUtils
unit contains functions that compare strings taking into account the Windows locale. Most applications should use these functions instead of a simple string comparison. See , for details.) For example:
var Text: array[0..5] of Char; P, Q: PChar; begin Text := 'Hello'; P := Text; Q := P + 3; WriteLn(Q > P); // True because Q points to a later element than P if Text > 'Howdy' then // string comparison is False
-
<
operator - Less than; compares numbers or strings, or character pointers. When you compare
PChar
orPWideChar
values, you are comparing pointers. The operator returns True if the left-hand operand points to an earlier position than the right-hand operand. Both operands must point to locations in the same character array. Strings are compared by comparing their characters' ordinal values. (TheSysUtils
unit contains functions that compare strings taking into account the Windows locale. Most applications should use these functions instead of a simple string comparison. See for details.) For example:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! -
- Chapter 8: Compiler Directives
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterCompiler directives are special comments that control the compiler and its behavior, similar to #pragma directives in C++. This chapter discusses compiler directives and lists all the compiler directives Delphi supports.A directive comment is one whose first character is a dollar sign (
$
). You can use either kind of Pascal-style comment:{$AppType GUI} (*$AppType GUI*)
You cannot use a C++ style comment (//$Apptype
) for a compiler directive.If the first character of a comment is not a dollar sign, the comment is not a compiler directive. A common trick to deactivate or "comment out" a compiler directive is to insert a space or other character just before the dollar sign, for example:{ $AppType GUI} (**$AppType GUI*)
Delphi has three varieties of compiler directives: switches, parameters, and conditional compilation. A switch is a Boolean flag: a feature can be enabled or disabled. A parameter provides information, such as a filename or stack size. Conditional compilation lets you define conditions and selectively compile parts of a source file depending on which conditions are set. Conditions are Boolean (set or not set).The names and parameters of compiler directives are not case-sensitive. You cannot abbreviate the name of a compiler directive. Delphi ignores extra text in the comment after the compiler directive and its parameters (if any). You should refrain from including extra text, though, because future versions of Delphi might introduce additional parameters for compiler directives. Instead of including commentary within the directive's comment, use a separate comment.You can combine multiple directives in a single comment by separating them with commas. Do not allow any spaces before or after the comma. If you use a long directive name, it must be the last directive in the comment. If you have multiple long directives, or any parameter directive, you must use separate comments for each one:{$R+,Q+,C+,Align On} {$O+,M 1024,40980}
Some directives are global for a project. These directives usually appear in the project's source file (Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Appendix A: Command-Line Tools
- Content preview·Buy reprint rights for this chapterInteractive development environments are great, but don't throw away that command line yet. Compiling a big project is often easier using the command-line compiler than compiling the same project in the IDE. This chapter tells you how to use the command-line compiler and other tools effectively.
Reference For: Compiler, dcc32.exe
Reference For: Resource Compiler, brcc32.exe
Reference For: DFM Converter, convert.exe
Reference For: Object File Dumper, tdump.exe
Reference For: IDE, delphi32.exe
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Appendix B: The SysUtils Unit
- Content preview·Buy reprint rights for this chapterThe
SysUtils
unit contains many utility classes, types, variables, and subroutines. They are so central to Delphi programming that many Delphi users forget that theSysUtils
unit is not built in and is optional. Because it is not built into the Delphi Pascal language, theSysUtils
unit is not covered in this book's main text. On the other hand, it plays such a central role in almost every Delphi program, package, and library, omitting it would be a mistake—hence this appendix.Section B.1: Errors and Exceptions
Section B.2: File Management
Section B.3: String Management
Section B.4: Numeric Conversion
Section B.5: Dates and Times
Section B.6: Localization
Section B.7: Modules
Section B.8: Windows
Section B.9: Miscellaneous
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Errors and Exceptions
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThis section lists the classes and subroutines to help you handle errors and exceptions. The most important role
SysUtils
plays is assigning values to the error-handling procedure pointers theSystem
unit declares, namely:AbstractErrorProc
raises anEAbstractError
exception.AssertErrorProc
raises anEAssertionFailed
exception.ErrorProc
maps runtime errors to exceptions.ExceptClsProc
maps Windows and hardware exceptions to Delphi exception classes.ExceptionClass
is set toException
.ExceptObjProc
maps Windows and hardware exceptions to Delphi exception objects.ExceptProc
displays exception information and callsHalt
.
If an error or exception occurs before theSysUtils
unit is initialized or after it is finalized, you don't get the advantage of its error-handling procedures, and must rely on the simple approach of theSystem
unit. This problem occurs most often when an application shuts down: an application corrupts memory by freeing already-freed memory, overrunning a buffer, etc., but the problem is not detected immediately. After theSysUtils
unit is finalized, the heap corruption is detected and another unit reports runtime error 217. Sometimes a unit raises an exception, in which case you get runtime error 216. You can improve this situation by moving theSysUtils
unit to earlier in the project'suses
declaration. Try listing it first in the project's main source file (but afterShareMem
). That forces other units to finalize beforeSysUtils
cleans up its error handlers.The other major roleSysUtils
plays in handling errors is declaring a hierarchy of exception classes. TheException
class is the root of the exception class hierarchy. TheSysUtils
unit sets theExceptionClass
variable toException
, so the IDE stops when any exception that inherits fromException
is raised.Every exception object has an associated string message. When you raise an exception, you provide the message in the exception constructor.Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - File Management
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThe
SysUtils
unit declares a number of useful types, constants, and subroutines for managing files and directories and for parsing filenames. This section describes these utilities.The standard Pascal I/O procedures use the Windows API to perform the actual file operations. You can also call the Windows API directly, but you might find it easier to use theSysUtils
file I/O functions. These functions are thin wrappers around the Windows API functions, providing the convenience of using Delphi strings, for example, instead ofPChar
strings.- FileClose Procedure
procedure FileClose(Handle: Integer);
FileClose
closes an open file. If the file is not open, the error is silently ignored.
- FileCreate Function
function FileCreate(const FileName: string): Integer;
FileCreate
creates a new file and opens it for read and write access, denying all sharing. If the file already exists, it is recreated. The result is the file handle or -1 for an error.
- FileGetDate Function
function FileGetDate(Handle: Integer): Integer;
FileGetDate
returns the modification date of an open file. The modification date is updated when the file is closed, so you might need to close the file first and then callFileAge
to get the most reliable modification date.FileGetDate
returns -1 for an error.
A file date and time are packed into bit fields in a 32-bit integer. The resolution is to the nearest two seconds, and it can represent years in the range 1980 to 2099. Figure 2.1 shows the format of a file date and time. SeeFileDateToDateTime
to convert the file date to aTDateTime
.Figure B.1: Format of a file date and time- FileOpen Function
function FileOpen(const FileName: string; Mode: LongWord): Integer;
FileOpen
opens an existing file. TheMode
combines access and sharing flags, as shown in Table 2.2. Choose one access mode and one sharing mode and combine them with addition or an inclusiveor
. The return value is the file handle or -1 for an error.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - String Management
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThis section lists subroutines related to
AnsiString
andPChar
strings. AnAnsiString
can also store a multibyte string, that is, a string where a single character might occupy more than one byte. The functions listed in the following sections handle multibyte strings correctly. UnlikePChar
strings, anAnsiString
can contain any character, including#0
. However, some string-handling functions assume that the string does not contain any#0
characters (except the#0
that appears after the end of the string). These functions are so noted in their descriptions.For an example of working with strings that might be multibyte strings, see Example 2.4, at the end of this section.The ANSI string functions useAnsiString
arguments and results, but more important, they recognize the Windows locale to handle ANSI characters, such as accented letters. They also handle multibyte strings.- AdjustLineBreaks Function
function AdjustLineBreaks(const S: string): string;
AdjustLineBreaks
converts the stringS
into DOS format by converting single carriage return (#13) and line feed (#10) characters into CR-LF pairs. Existing CR-LF line endings are untouched. Files that are copied from Unix or Macintosh systems use different line break characters, which can confuse some Windows and DOS programs. (Macintosh uses a lone carriage return for a line break; Unix uses a lone line feed character.)
- AnsiCompareFileName Function
function AnsiCompareFileName(const S1, S2: string): Integer;
AnsiCompareFileName
is similar toAnsiCompareText
, but it works around a problem with full-width Japanese (Zenkaku) filenames, where the ASCII characters take up two bytes instead of one. Always callAnsiCompareFileName
to compare two filenames.
- AnsiCompareStr Function
function AnsiCompareStr(const S1, S2: string): Integer;
AnsiCompareStr
compares two strings and returns an integer result, as listed in Table 2.5.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Numeric Conversion
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThis section lists types, constants, and subroutines that help you convert integers, currency, and floating-point numbers to text and to convert text to numbers.
- CurrencyDecimals Variable
var CurrencyDecimals: Byte;
CurrencyDecimals
is the default number of digits to the right of the decimal point when formatting money in a call toFormat
and its related functions. The default value is taken from the Windows locale.
- CurrencyFormat Variable
var CurrencyFormat: Byte;
CurrencyFormat
specifies how to format a positiveCurrency
amount by positioning the currency symbol relative to the numeric currency amount. The default value is taken from the Windows locale. The possible values are listed in Table 2.8.
Table B.8: Values for CurrencyFormat ValueDescriptionExample0<symbol><amount>$1.00
1<amount><symbol>1.00$
2<symbol><space><amount>$ 1.00
3<amount><space><symbol>1.00 $
- CurrencyString Variable
var CurrencyString: string;
CurrencyString
is the symbol that identifies a currency value, such as
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Dates and Times
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThis section lists types, constants, and subroutines related to dates and times. Delphi has five different ways to keep track of a date and time:
- The
TDateTime
type is the most common way to store a date and a time. See its description in for details. - DOS stores the modification date of a file using a simple file date format, which fits in an integer. Convert a file date to a
TDateTime
type or vice versa. - The
TTimeStamp
type is a record that keeps the date and time in separate fields, soTTimeStamp
is harder to use thanTDateTime
. You can convert between these two types. - You can also keep track of the year, month, day, hour, minute, second, and millisecond as separate values. Convert between discrete values and a
TDateTime
value with the encode and decode subroutines. - Windows stores the separate components of a date and time in the
TSystemTime
record. Delphi has convenience functions to convert betweenTSystemTime
andTDateTime
.
- Date Function
function Date: TDateTime;
Date
returns the current date in the local time zone. The time is set to midnight (zero).
- DateDelta Constant
const DateDelta = 693594;
DateDelta
is the number of days between January 1, 0001 and December 31, 1899. The former is the initial date used forTTimeStamp
, and the latter is the initial date used forTDateTime
.
- DateSeparator Variable
var DateSeparator: Char;
DateSeparator
is the character used to separate the month, day, and year in the short format for a date. The default value is taken from the Windows locale.
- DateTimeToFileDate Function
function DateTimeToFileDate(DateTime: TDateTime): Integer;
DateTimeToFileDate
converts aTDateTime
value to a file date, which you can use in a call toFileSetDate
. Figure 2.1 (earlier in this appendix) shows the format of a file date and time. The time is truncated to a two-second time. If the year is out of the range of a file date (1980 to 2099), zero is returned.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Localization
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterEvery Delphi programmer who distributes components must understand localization issues, because you never know who will use your component or where they will use it. To allow for various users,
resourcestring
should be used for all string constants that might be translated to a different language.Format
strings should use index specifiers in case the translated format must rearrange the order of the arguments.Formatting of dates, time, and numbers must heed the local specifications forDecimalSeparator
,CurrencyFormat
, and so on. The user can use the Regional Settings applet in the Windows Control Panel to change these settings. If the user wants to separate the parts of a date with question marks, your program or component should heed that setting (which is stored in theDateSeparator
variable) and not arbitrarily use a hardcoded separator.Strings might contain non-ASCII accented characters or even be multibyte strings. For information about the specific formatting functions, see their descriptions elsewhere in this appendix. This section lists additional types, constants, and subroutines related to localization.For more information about locales and Windows, see the Microsoft Platform SDK documentation. In particular, read about National Language Support.- GetFormatSettings Procedure
procedure GetFormatSettings;
GetFormatSettings
loads the local settings from Windows for the variables listed in Table 2.12. A GUI application automatically calls this procedure when the format settings change (that is, when the application receives aWm_WinIniChange
message).
Table B.12: Variables Set by GetFormatSettings VariableDescriptionCurrencyDecimals
Number of decimal places forCurrency
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Modules
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterEvery module (program, library, or package) exports two procedures named
Initialize
andFinalize
, which invoke the initialization sections and finalization sections for all the units contained in the module. Packages especially rely on this feature because you can easily load a package DLL (thereby initializing its units) and unload the DLL (after finalizing the units). TheSysUtils
unit has a number of constants, types, and subroutines to help you get information about a module and to load and unload packages.Although the names contain the word "Package," most of the subroutines described in this section work for any kind of module.- FinalizePackage Procedure
procedure FinalizePackage(Module: HMODULE);
FinalizePackage
calls the finalization section for all the units in a loaded module.UnloadPackage
callsFinalizePackage
, so you have little reason to call this procedure yourself. If the module does not have a finalization section,FinalizePackage
raises anEPackageError
exception.
- GetPackageDescription Function
function GetPackageDescription(ModuleName: PChar): string;
GetPackageDescription
returns the description string for the module whose path is given byModuleName
. If the file does not exist or cannot be loaded,EPackageError
is raised. If the module does not have a description, an empty string is returned. The description is specified by the$D
or$Description
compiler directive, which the compiler stores as an RCDATA resource namedDESCRIPTION
.
- GetPackageInfo Procedure
type TNameType = (ntContainsUnit, ntRequiresPackage); TPackageInfoProc = procedure (const Name: string; NameType: TNameType; Flags: Byte; Param: Pointer); procedure GetPackageInfo(Module: HMODULE; Param: Pointer; var Flags: Integer; InfoProc: TPackageInfoProc);
GetPackageInfo
callsInfoProc
for every unit in a loaded module (which can be a package, program, or DLL) and for every required package.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Windows
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThis section describes variables and functions for working with the Windows API.
- GetDiskFreeSpaceEx Variable
var GetDiskFreeSpaceEx: function (Directory: PChar; var FreeAvailable, TotalSpace: TLargeInteger; TotalFree: PLargeInteger): Bool stdcall;
The first release of Windows 95 does not support theGetDiskFreeSpaceEx
API function, but the OSR2 release does, as does Windows 98 and Windows NT. Delphi hides this difference by setting theGetDiskFreeSpaceEx
variable to point to the Windows API function if it exists or to a Delphi function on Windows 95a. See theDiskFree
function earlier in this appendix for the easy way to obtain the free space on a drive.
- SafeLoadLibrary Function
function SafeLoadLibrary(const Filename: string; ErrorMode: UINT = SEM_NoOpenFileErrorBox): HMODULE;
Some DLLs change the floating-point control word, which can affect the precision of Delphi'sComp
andCurrency
types. TheSafeLoadLibrary
function is just like the Windows API functionLoadLibrary
, except that it ensures the floating-point control word is not disturbed. The optional second argument is passed toSetErrorMode
, so you can control whether file open failures or similar errors are shown to the user.
- TMultiReadExclusiveWriteSynchronizer Type
type TMultiReadExclusiveWriteSynchronizer = class public constructor Create; destructor Destroy; override; procedure BeginRead; procedure EndRead; procedure BeginWrite; procedure EndWrite; end;
UseTMultiReadExclusiveWriteSynchronizer
when multiple threads must read a shared resource without changing it. A critical section allows only one thread to access a shared resource, even if that thread will not change it.TMultiReadExclusiveWriteSynchronizer
, on the other hand, improves performance by allowing multiple simultaneous readers.
Each reader callsBeginRead
to gain read access andEndRead
when it is finished. When a thread wants to modify the resource, it callsBeginWrite
, which waits until all readers have calledEndRead
, then it gets exclusive write access. CallAdditional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing! - Miscellaneous
- Content preview·Buy PDF of this chapter|Buy reprint rights for this chapterThis section lists everything that doesn't fit in the other categories.
- AllocMem Function
function AllocMem(Size: Cardinal): Pointer;
TheAllocMem
function is just likeGetMem
, except that it initializes the allocated memory to all zero. If you callAllocMem
to allocate a record that contains long strings, interfaces, orVariant
s, you do not need to callInitialize
. Free the memory withFreeMem
.
- CompareMem Function
function CompareMem(P1, P2: Pointer; Length: Integer): Boolean;
CompareMem
comparesLength
bytes pointed to byP1
andP2
for equality. It returns True if the two memory regions contain identical contents, and False if any byte is different.
- FreeAndNil Procedure
procedure FreeAndNil(var Obj);
When an object reference is stored in a variable or field, pass the object reference toFreeAndNil
instead of calling the object'sFree
method. TheFreeAndNil
procedure callsFree
and sets the variable or field tonil
.
FreeAndNil
is particularly useful in a multithreaded or reentrant situation because it guarantees that the variable is set tonil
before the object is freed. In other words, the variable never holds an invalid reference.- Int64Rec Type
type Int64Rec = packed record Lo, Hi: LongWord; end;
Int64Rec
makes it easier to access the high and low long words in anInt64
or other 64-bit type (such asDouble
).
- LoadStr Function
function LoadStr(Ident: Integer): string;
LoadStr
returns a string resource (or an empty string if no resource exists with identifierIdent
). It exists for backward compatibility. New code should useresourcestring
declarations.
- LongRec Type
type LongRec = packed record Lo, Hi: Word; end;
LongRec
makes it easier to access the high and low words in aLongWord
or other 32-bit type (such asSingle
).
- PByteArray Type
type PByteArray = ^TByteArray; type TByteArray = array[0..32767] of Byte;
The most common use of
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Return to Delphi in a Nutshell
About O'Reilly | Contact | Jobs | Press Room | How to Advertise | Privacy Policy
|
© 2008, O'Reilly Media, Inc. | (707) 827-7000 / (800) 998-9938
All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.