Delphi Programming Guide
Delphi Programmer 

Menu  Table of contents

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

 
Previous Section Next Section

New Delphi Language Features

The first release of the dccil compiler included new features required by the CLR, and more have been added in subsequent updates.

Unit Namespaces

Namespaces play an important role in the .NET Framework. They allow the class hierarchy to be extended by multiple third parties without fear of conflicting symbol names. Windows and COM use a 16-byte GUID to uniquely identify components, and this magic number must be recorded in the system registry. On the .NET platform, the concept of namespaces—plus metadata and the hard-and-fast rules about locating assemblies—makes GUIDs obsolete.

Ironically, the idea of a Delphi unit is similar to the CLR's namespaces. It's not too far a leap, if you think of a unit as a container of symbols, and a namespace as a container of units. In Delphi for .NET, the namespace to which a unit belongs is declared in the unit clause:

unit NamespaceA.NamespaceB.UnitA;

The dots indicate the containment of one namespace within another, and ultimately of the unit within the namespace. The dots separate the declaration into components, and each component—up to but not including the rightmost one—is a namespace. The entire declaration taken as a whole, dots and all, is the unit name. The dots simply serve as separators; no new symbols are introduced by the declaration. In this example, NamespaceA.NamespaceB is the namespace, and NamespaceA.NamespaceB.UnitA is the name of the unit. NamespaceA.NamespaceB.UnitA.pas would be the name of the source file, and the compiler would produce an output file called NamespaceA.NamespaceB.UnitA.dcuil.

The program statement (and eventually the package and library statements) optionally declares the default namespace for the entire project. Otherwise, the project is called a generic project, and the default namespace is that specified by the –ns compiler option. If no default project namespace is specified with compiler options, then behavior reverts to not using namespaces, like in Delphi 7 (and prior releases).

The unit clause does not have to declare membership in any explicit namespace. It might look like a traditional Delphi statement:

unit UnitA;

A unit that does not declare membership in a namespace is called a generic unit. Generic units automatically become members of the project namespace. Note, however, that this does not affect the source filename.

Warning 

At the time of this writing, namespace support is very limited; this section describes how it should work in the future, rather than how it works now.

In the project file, you can specify a namespaces clause to list a set of namespaces for the compiler to search when it is trying to resolve references to generic units. The namespaces clause must appear immediately after the program (or package or library) statement and before any other clause or block type. The namespaces are separated by commas, and the list is terminated with a semicolon. For example:

program NamespaceA.MyProgram
  namespaces Foo.Bar, Foo.Frob, Foo.Nitz;

This example adds the namespaces Foo.Bar, Foo.Frob, and Foo.Nitz to the generic unit search space.

This discussion leads up to showing you how the compiler searches for generic units when you build your program. When you use a unit and fully qualify its name with the full namespace declaration, there is no problem:

uses Foo.Frob.Gizmos;

The compiler knows the name of the dcuil file (or the .pas file) in this case. But suppose you only said the following:

uses Gizmos;

This is called a generic unit reference, and the compiler must have a way to find its dcuil file.

The compiler searches namespaces in the following order:

  1. The current unit namespace (if any)

  2. The default project namespace (if any)

  3. The namespaces listed in the project's namespaces clause (if any)

  4. The namespaces specified by compiler options

For the first item, if the current unit specifies a namespace, then subsequent generic unit references in the current unit's uses clause are looked for first in the current unit's namespace. Consider this example:

unit Foo.Frob.Gizmos;
uses doodads;

The first search location for the unit doodads would be in the namespace Foo.Frob. So, the compiler would try to open Foo.Frob.Doodads.dcuil. Failing this, the compiler would move on and prefix the unit name doodads with the default project namespace, and so on down the list.

The same symbol name can appear in different namespaces. When such ambiguity occurs, you must refer to the symbol by its full namespace and unit name. If you have a symbol named Hoozitz in unit Foo.Frob.Gizmos, you can refer to the symbol with either

Hoozitz; // if the name is unambiguous
Foo.Frob.Gizmos.Hoozitz;

but not with

Gizmos.Hoozitz;      // error!
Frob.Gizmos.Hoozitz; // error!

Unit and namespace names can become quite long and unwieldy. You can create an alias for the fully qualified name with the as keyword in the uses clause:

uses Foo.Frob.DepartmentOfRedundancyDepartment.UIToys as ToyUnit;

Unit aliases introduce new identifiers, so their names cannot conflict with any other identifiers in the same unit (aliases are local to their unit). Even if you declare an alias, you can still use the original, longer name to refer to the unit.

Note 

The case of a namespace declaration is preserved and emitted into assembly metadata as is. However, as far as Delphi is concerned, two namespaces that differ only in case are equivalent.

Extended Identifiers

The cross-language integration of the CTS and CLR brings up some interesting situations for compiler developers. For example, what if the name of an identifier in an assembly is the same as one of your language keywords? Consider the Delphi language keyword type. Type is also the name of a CLR class. Because type is a language keyword, it cannot be used as the name of an identifier. You can avoid this problem two ways in Delphi for .NET (these techniques were not implemented in Delphi 7 and previous versions).

First, you can use the fully qualified name of the identifier:

var
  T: System.Type;

The second, shorter way is to use the new ampersand operator (&) to prefix the identifier. The following has the same effect as the previous example:

var
  T: &Type;

In this statement the ampersand tells the compiler to look for a symbol with the name Type and to not consider it as a keyword. The compiler will look for the Type symbol in the available units, finding it in System (the same mechanism works regardless of the unit defining the symbol).

The final and sealed Keywords

Two more concepts specified by the Common Language Infrastructure (CLI) have been added to the Delphi for .NET compiler: the class attribute sealed and the method attribute final. Putting the sealed attribute on a class effectively ends the class's ability to be used as a base class. Here is a sample code snippet:

type
  TDeriv1 = class (TBase)
    procedure A; override;
  end sealed;

A class cannot derive from a class that has been sealed. Similarly, a virtual method marked with the final attribute cannot be overridden in any descendant class, as in the following sample code.

type
  TDeriv1 = class (TBase)
    procedure A; override; final;
  end;
   
  TDeriv2 = class (TDeriv1)
    procedure A; override; // error: "cannot override a final method"
  end;

Borland added the sealed and final keywords to map an existing feature of .NET, but why did Microsoft introduce these attributes? The final and sealed attributes give users of your code important insights into how you intend your classes to be used. Moreover, these attributes give the compiler hints that allow it to generate more efficient Common Intermediate Language (CIL).

New Visibility and Access Specifiers

Delphi's notion of visibility—public, protected, and private—is a bit different from that of the CLI. In languages like C++ and Java, when you specify a visibility of private or protected on a class member, that class member is only visible to descendants of the class in which it is defined. As you saw in Chapter 2, however, Delphi enforces the idea of private and protected only for classes in different units, because everything is visible within a single unit. To be CTS compliant, the language required new visibility specifiers:

class private  A member declared with class private visibility follows the C++ and Java rules. That is, class private members can be accessed only in methods or properties of the declaring class. Procedures and functions declared at the unit level and methods of other classes do not have access.

class protected  Similarly, class protected members are visible only within the declaring class, and to descendants of the declaring class. Other classes in the same unit have access only if they inherit from this class.

See the ProtectedPrivate example in the LanguageTest folder of the chapter's source code for a trivial test case.

Class Static Members

Delphi has long supported class methods—methods you can apply to a class as a whole and also as a specific instance, even if the methods' code cannot refer to the current object (the Self parameter of a class methods references the current class, not the current object). Delphi for .NET extends this idea by adding the class static specifier, class properties, class static fields, and class constructors:

Class Static Methods  Like Delphi 7 class methods, class static members can be called without an object instance, and no Self parameter refers to an object. Unlike in Delphi 7, however, you cannot refer to the class itself. For example, calling the ClassName method will fail. Also unlike in Delphi 7, you cannot use the virtual keyword with class static methods.

Class Static Properties  Like class methods, class static properties can be accessed without an object instance. The access methods or backing fields for class static properties must be declared class static themselves. Class static properties cannot be published, nor can they have stored or default value definitions.

Class Static Fields  A class static field can be accessed without an object instance. Class static fields and properties are typically used as design tools; they allow you to declare variables and constants within the meaningful context of a class declaration.

Class Constructor  A class constructor is a private constructor (it must be declared with class private visibility) that runs prior to the first use of the declaring class. The CLR offers no guarantee of when this will happen, except to say it will happen before the first use of the class. In CLR terms, this can get a bit tricky, because code is not considered "used" unless (and until) it is executed. A class can declare only one class constructor. Descendants can declare their own class constructors, but only one can be declared in any class.

You can't call a class constructor from source code; it is called automatically as a way to initialize class static fields and properties. Even the inherited keyword is prohibited, because the compiler takes care of this for you.

The following example class declaration illustrates the syntax for these new specifiers:

TMyClass = class
class private // can only be accessed within TMyClass
  // Class constructor must have class private visibility
  class constructor Create;
class protected // can be accessed in TMyClass and in descendants
  // Class static accessors for class static property P1, below
  class static function getP1 : Integer;
  class static procedure setP1(val : Integer);
public
  // fx can be called without an object instance
  class static function fx(p : Integer) : Integer;
  // Class static property P1 must have class static accessors
  class static property P1 : Integer read getP1 write setP1;
end;

Nested Types

Nested types are similar to class fields, in that they can be accessed through a class reference; an object instance is not needed. Declared within the scope of a class, nested types give you a way to use the enclosing class as a kind of namespace for the type.

Multicast Events

Delphi has always had the ability to set an event listener—a function that is called when an event is fired. The CLR supports the use of multiple event listeners so that more than one function can respond when an event is fired. These are called multicast events. Delphi for .NET introduces two new property access methods, add and remove, to support multicast events. The add and remove methods can be used only on properties that are events.

To support multicast events, you must have a way to store all the functions that register themselves as listeners. As stated in Chapter 24, multicast events are implemented using the CLR MulticastDelegate class. And, as discussed there, the compiler hides a lot of complexity behind the scenes. The add and remove keywords handle the storage and removal of event listeners, but the containment mechanism is an implementation detail you aren't expected to deal with. The compiler automatically generates add and remove methods for you, and these methods implement storage of event listeners in an efficient way.

In the final release of Delphi for .NET, the add and remove methods should work hand in hand with an overloaded version of the standard functions Include and Exclude. In your source code, when you'd want to register a method as an event listener, you call Include. To remove a method, call Exclude. For example:

Include(EventProp, eventHandler);
Exclude(EventProp, eventHandler);

Behind the scenes, Include and Exclude will call the methods assigned to the add and remove access functions, respectively. At the time of this writing, this technology wasn't working, so the book examples don't use it.

To support legacy code, the Delphi assignment operator (:=) still works as a way to assign a single event handler. The compiler generates code to go back and replace the last event handler (and only that event handler) that was set with the assignment operator. The assignment operator works separately and independently from the add/remove (or Include/Exclude) mechanism. In other words, the use of the assignment operator does not affect the list of event handlers that have been added to the MulticastDelegate.

As an example, you can refer to the XmlDemo program. The following code snippet (the working code at the time of this writing) creates a button at run time and installs two event handlers for its Click event:

MyButton := Button.Create;
MyButton.Location := Point.Create (
  Width div 2 - MyButton.Width div 2, 2);
MyButton.Text := 'Load';
MyButton.add_Click (OnButtonClick);
MyButton.add_click (OnButtonClick2);
Controls.Add (MyButton);

Custom Attributes

Recall from Chapter 24 that one of the requirements of the CLI is an extensible metadata system. All .NET language compilers are required to emit metadata for the types defined within an assembly. The extensible part of extensible metadata means that programmers can define their own attributes and apply them to just about anything: assemblies, classes, methods, and more. The compiler emits these into the assembly's metadata. At run time, you can query for the attributes that were applied to an entity (assembly, class, method, and so on) using the methods of the CLR class System.Type.

Custom attributes are reference types derived from the CLR class System.Attribute. Declaring a custom attribute class is just like declaring any other class (this code snippet is extracted from the trivial NetAttributes project part of the LanguageTest folder):

type
  TMyCustomAttribute = class(TCustomAttribute)
  private
    FAttr : Integer;
  public
    constructor Create(val: Integer);
    property customAttribute : Integer read FAttr write FAttr;
  end;
  ...
  constructor TMyCustomAttribute.Create(val: Integer)
  begin
    inherited Create;
    customAttribute := val;
  end;

The syntax for applying the custom attribute is similar to that of C#:

type
  [TMyCustomAttribute(17)]
  TFoo = class
  public
    function P1(X : Integer) : Integer;
  end;

The custom attribute is applied to the construct immediately following it. In the example, it is applied to the class TFoo. No doubt you noticed that the custom attribute syntax is nearly identical to that of Delphi's GUID syntax. Here we have a problem: GUIDs are applied to interfaces; they must immediately follow the interface declaration. Custom attributes, on the other hand, must immediately precede the declaration to which they apply. How can the compiler determine whether the thing in the square brackets is a traditional Delphi-style GUID (which should be applied to the preceding interface declaration) or a .NET-style custom attribute (which should be applied to the first member of the interface)?

There is no way to tell, so you have to punt—make a special case for custom attributes and interfaces. If you apply a GUID to an interface, it must immediately follow the declaration of the interface, and it must follow the established Delphi syntax:

type
  interface IMyInterface
  ['(12345678-1234-1234-1234-1234567890ab)']

CLR's GuidAttribute custom attribute is used to apply GUIDs; it is part of the System.Runtime.InteropServices namespace. If you use this custom attribute to apply a GUID, then you must follow the CLR standard and put the attribute declaration before the interface.

Class Helpers

Class helpers are an intriguing new language feature added to Delphi for .NET. The main reason for supporting class helpers is the way Borland maps .NET core classes with its own RTL classes, as covered later in the section "Class Helpers for the RTL." Here I will focus on this feature from a language perspective.

A class helper gives you a way to extend a class without using derivation, by adding new methods (but not new data). The odd fact, compared to inheritance, is that you can create objects of the original class, which is extended maintaining the same name. This means you can plug-in methods to an existing object of an existing class. A simple example will help clarify the idea.

Suppose you have a class (probably one you haven't written yourself—otherwise you could have extended it right away) like this:

type
  TMyObject = class
  private
    Value: Integer;
    Text: string;
  public
    procedure Increase;
  end;

Now you can add a Show method to objects of this class by writing a class helper to extend it:

type
  TMyObjectHelper = class helper for TMyObject
  public
    procedure Show;
  end;
   
procedure TMyObjectHelper.Show;
begin
  WriteLn (Text + ' ' + IntToStr (Value) + ' -- ' +
    Self.ClassType.ClassName + ' -- ' + ToString);
end;

Notice that Self in the class helper method is the object of the class that the helper is for. You can use it like this:

Obj := TMyObject.Create;
...
Obj.Show;

You'll end up seeing the name of the TMyObject class in the output. If you inherit from the class, however, the class helper will also be usable on the derived class (so you end up adding a method to an entire hierarchy), and everything will work properly. For your experiments, refer to the ClassHelperDemo example in the LanguageTest folder.


 
Previous Section Next Section


 


 

Delphi Sources


Copyright © 2004-2024 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide
ร๐๓๏๏เ ยส๎ํ๒เ๊๒ๅ   Facebook   ั๑๛๋๊เ ํเ Twitter