The TPersistent Class

The TPersistent Class

The first core class of the Delphi library we'll look at is TPersistent, which is quite a strange class: It has very little code and almost no direct use, but it provides a foundation for the entire idea of visual programming. You can see the definition of the class in Listing 4.1.

Listing 4.1: The Definition of the TPersistent Class, from the Classes Unit
Start example
TPersistent = class(TObject)
  procedure AssignError(Source: TPersistent);
  procedure AssignTo(Dest: TPersistent); virtual;
  procedure DefineProperties(Filer: TFiler); virtual;
  function  GetOwner: TPersistent; dynamic;
  destructor Destroy; override;
  procedure Assign(Source: TPersistent); virtual;
  function  GetNamePath: string; dynamic;
End example

As the name implies, this class handles persistency—that is, saving the value of an object to a file to be used later to re-create the object in the same state and with the same data. Persistency is a key element of visual programming. In fact (as you saw in Chapter 1, "Delphi 7 and Its IDE"), at design time in Delphi you manipulate actual objects, which are saved to DFM files and re-created at run time when the specific component container— form or data module—is created.


Everything I say about DFM files also applies to XFM files, the file format used by CLX applications. The format is identical. The extension difference is relevant because Delphi uses it to determine whether the form is based on CLX/Qt or on VCL/Windows. In Kylix, every form is a CLX/Qt form, regardless of which extension is used; so, the XFM/ DFM file extension in Kylix really doesn't matter.

Streaming support is not embedded in the TPersistent class but is provided by other classes, which target TPersistent and its descendants. In other words, you can "persist" with Delphi default streaming-only objects of classes inheriting from TPersistent. One of the reasons for this behavior lies in the fact that the class is compiled with a special option turned on, {$M+}. This flag activates the generation of extended RTTI information for the published portion of the class.

Delphi's streaming system doesn't try to save the in-memory data of an object, which would be complex because of the many pointers to other memory locations and totally meaningless when the object was reloaded. Instead, Delphi saves objects by listing the values of all properties in the published section of the class. When a property refers to another object, Delphi saves the name of the object or the entire object (with the same mechanism) depending on its type and relationship with the main object. For a comparison with other approaches, see the sidebar "Object Streaming versus Code Generation."

The only method of the TPersistent class that you'll generally use is the Assign procedure, which can be used to copy the actual value of an object. In the library, this method is implemented by many noncomponent classes but by very few components. Most subclasses reimplement the virtual protected AssignTo method, called by the default implementation of Assign.

Other methods include DefineProperties, used for customizing the streaming system and adding extra information (pseudo-properties); and the GetOwner and GetNamePath methods, used by collections and other special classes to identify themselves to the Object Inspector.

The published Keyword

Delphi has four directives specifying data access: public, protected, private, and published. I've covered the first three in Chapter 2, "The Delphi Programming Language," so now it's time to look at what published means. For any published field, property, or method, the compiler generates extended RTTI information, so that Delphi's run-time environment or a program can query a class for its published interface. For example, every Delphi component has a published interface that is used by the IDE, in particular the Object Inspector. A proper use of published items is important when you write components. Usually, the published part of a component contains no fields or methods, just properties and events.

When Delphi generates a form or data module, it places the definitions of its components and methods (the event handlers) in the first portion of its definition, before the public and private keywords. These fields and methods in the initial portion of the class are published. The default is published when no special keyword is added before an element of a component class.

To be more precise, published is the default keyword only if the class was compiled with the $M+ compiler directive or is descended from a class compiled with $M+. This directive is used in the TPersistent class, so most classes of the VCL and all the component classes default to published. However, noncomponent classes in Delphi (such as TStream and TList) are compiled with $M- and default to public visibility.

The methods used to handle events in the IDE (and in DFM files) should be published, and the fields corresponding to your components in the form should be published to be automatically connected with the objects described in the DFM file and created along with the form. (Later in this chapter I'll discuss the details of this situation and the problems it generates.)

Accessing Properties by Name

The Object Inspector displays a list of an object's published properties, even for components you've written. To do this, it relies on the RTTI information generated for published properties. Using some advanced techniques, an application can retrieve a list of an object's published properties and use them.

Although this capability is not very well known, in Delphi it is possible to access properties by name simply by using the string with the name of the property and then retrieving its value. Access to the RTTI information of properties is provided through a group of undocumented subroutines, part of the TypInfo unit.


These subroutines have always been undocumented in past versions of Delphi, so that Borland remained free to change them. However, from Delphi 1 to Delphi 7, changes were very limited and related only to supporting new features, with a high level of backward compatibility. In Delphi 5, Borland added many more goodies and a few "helper" routines that are officially promoted (even if still not fully documented in the Help file but explained only with comments provided in the unit).

Rather than explore the entire TypInfo unit here, we will look at only the minimal code required to access properties by name. Prior to Delphi 5, it was necessary to use the GetPropInfo function to retrieve a pointer to some internal property information and then apply one of the access functions, such as GetStrProp, to this pointer. You also had to check for the existence and the type of the property.

Now you can use a new set of TypInfo routines, including the handy GetPropValue, which returns a variant with the value of the property and raises an exception if the property doesn't exist. To avoid the exception, you can call the IsPublishedProp function first. You simply pass to these functions the object and a string with the property name. A further optional parameter of GetPropValue allows you to choose the format for returning values of properties of any set type (either a string or the numeric value for the set). For example, you can call

ShowMessage (GetPropValue (Button1, 'Caption'));

This call has the same effect as calling ShowMessage, passing as parameter Button1.Caption. The only real difference is that this version of the code is much slower, because the compiler generally resolves normal access to properties in a more efficient way. The advantage of the run-time access is that you can make it very flexible, as in the following RunProp example.

This program displays in a list box the value of a property of any type for each component of a form. The name of the property you are looking for is provided in an edit box. Being able to type the property name in the edit box makes the program very flexible. In addition to the edit box and the list box, the form has a button to generate the output and some other components added only to test their properties. When you click the button, the following code is executed:

procedure TForm1.Button1Click(Sender: TObject);
  I: Integer;
  Value: Variant;
  for I := 0 to ComponentCount -1 do
    if IsPublishedProp (Components[I], Edit1.Text) then
      Value := GetPropValue (Components[I], Edit1.Text);
      ListBox1.Items.Add (Components[I].Name + '.' +
        Edit1.Text + ' = ' + string (Value));
      ListBox1.Items.Add ('No ' + Components[I].Name + '.' +

Figure 4.2 shows the effect of clicking the Fill List button while using the default Caption value in the edit box. You can try it with any other property name. Numbers will be converted to strings by the variant conversion. Objects (such as the value of the Font property) will be displayed as memory addresses.

Click To expand Figure 4.2: The output of the RunProp example, which accesses properties by name at run time

Do not use regularly the TypInfo unit instead of polymorphism and other property-access techniques. Use base-class property access first, or use the safe as typecast when required, and reserve RTTI access to properties as a last resort. Using TypInfo techniques makes your code slower, more complex, and more prone to human error; in fact, it skips the compile-time type-checking.

Part I: Foundations