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

Using Microsoft Libraries

The VCL is not quite ready, but you can use the .NET Framework class library as a basis for experimentation with the Delphi for .NET Preview compiler. It can be educational to build programs with the compiler and then inspect them with Intermediate Language Disassembler (ILDASM), for instance. This will be the aim of this section. If you want to look at a simpler example using XML support, refer to the XmlDemo mentioned earlier in the chapter.

The CLRReflection program opens an assembly and then uses reflection to inspect the modules and types defined within that assembly. This program demonstrates using a common dialog box (the OpenFileDialog), constructing menus, handling events, using Delphi's dynamic arrays, and, of course, reflection. Let's look at the project file first:

program CLRReflection;
   
uses
  System.Windows.Forms,
  ReflectionUnit;
   
var
  reflectForm : ReflectionForm;
begin
  reflectForm := ReflectionForm.Create;
  System.Windows.Forms.Application.Run(reflectForm);
end.

The code looks almost like a good old VCL application. You define a variable for your main form, and then you create the form. Then you use the Run method of the .NET Framework class System.Windows.Forms.Application. Here the code is analogous (at least in concept) to the way it is done in the VCL.

Note that throughout this example I have given the fully qualified name for .NET Framework classes. I did so to make sure you know where these classes are located. Because the uses clause includes System.Windows.Forms, you could shorten the expression

System.Windows.Forms.Application.Run(reflectForm);

to

Application.Run(reflectForm);

Now, look at Listing 25.1, which shows the unit where the main form is defined. Note that this code compiles with the November 2002 update of the Delphi for .NET Preview, but not with the version originally shipping with Delphi 7.

Listing 25.1: The ReflectionUnit Unit of the CLRReflection Example
Start example
unit ReflectionUnit;
   
interface
   
uses
    System.Windows.Forms,
    System.Reflection,
    System.Drawing,
    Borland.Delphi.SysUtils;
   
type
  ReflectionForm = class(System.Windows.Forms.Form)
  private
    mainMenu: System.Windows.Forms.MainMenu;
    fileMenu: System.Windows.Forms.MenuItem;
    separatorItem: System.Windows.Forms.MenuItem;
    openItem: System.Windows.Forms.MenuItem;
    exitItem: System.Windows.Forms.MenuItem;
   
    showFileLabel: System.Windows.Forms.Label;
    typesListBox: System.Windows.Forms.ListBox;
    openFileDialog: System.WIndows.Forms.OpenFileDialog; 
  protected
    procedure InitializeMenu;
    procedure InitializeControls;
    procedure PopulateTypes(fileName: String);
    { Event Handlers }
    procedure exitItemClick(sender: TObject; Args: System.EventArgs);
    procedure openItemClick(sender: TObject; Args: System.EventArgs);
  public
    constructor Create;       
end;
   
implementation
   
constructor ReflectionForm.Create;
begin
  inherited Create;
    
  SuspendLayout;
  InitializeMenu;
  InitializeControls;
    
  { Initialize the form and other member variables }
  openFileDialog := System.Windows.Forms.OpenFileDialog.Create;
  openFileDialog.Filter := 'Assemblies (*.dll;*.exe)|*.dll;*.exe';
  openFileDialog.Title := 'Open an assembly';
    
  AutoScaleBaseSize := System.Drawing.Size.Create(5, 13);
  ClientSize := System.Drawing.Size.Create(631, 357);
  Menu := mainMenu;
  Name := 'reflectionForm';
  Text := 'Reflection in Delphi for .NET';
    
  { Add the controls to the form's collection. }
  Controls.Add(showFileLabel);
  Controls.Add(typesListBox);
  ResumeLayout;
end;
   
{ Build the main menu }
procedure ReflectionForm.InitializeMenu;
var
  menuItemArray : array of System.Windows.Forms.MenuItem;
begin
  mainMenu := System.Windows.Forms.MainMenu.Create;
  fileMenu := System.Windows.Forms.MenuItem.Create;
  openItem := System.Windows.Forms.MenuItem.Create;
  separatorItem := System.Windows.Forms.MenuItem.Create;
  exitItem := System.Windows.Forms.MenuItem.Create;
           
  { Initialize mainMenu }
  mainMenu.MenuItems.Add(fileMenu);
           
  { Initialize fileMenu }
  fileMenu.Index := 0;
  SetLength(menuItemArray, 3);
  menuItemArray[0] := openItem;
  menuItemArray[1] := separatorItem;
  menuItemArray[2] := exitItem;
  fileMenu.MenuItems.AddRange(menuItemArray);
  fileMenu.Text := '&File';
                       
  // openItem
  openItem.Index := 0;
  openItem.Text := '&Open...';
  openItem.add_Click(openItemClick);
            
  // separatorItem
  separatorItem.Index := 1;
  separatorItem.Text := '-';
   
  // exitItem
  exitItem.Index := 2;
  exitItem.Text := 'E&xit';
  exitItem.add_Click(exitItemClick);
end;
   
{ Create the controls and populate the form }
procedure ReflectionForm.InitializeControls;
begin
  { Initialize showFileLabel }
  showFileLabel := System.Windows.Forms.Label.Create;
  showFileLabel.Location := System.Drawing.Point.Create(5, 6);
  showFileLabel.Name := 'showFileLabel';
  showFileLabel.Size := System.Drawing.Size.Create(616, 37);
  showFileLabel.TabIndex := 0;
  showFileLabel.Anchor := System.Windows.Forms.AnchorStyles.Top or
    System.Windows.Forms.AnchorStyles.Left or
    System.Windows.Forms.AnchorStyles.Right
  showFileLabel.Text := 'Showing types in: ';
           
  { Initialize typesListBox }
  typesListBox := System.Windows.Forms.ListBox.Create;
  typesListBox.Anchor := System.Windows.Forms.AnchorStyles.Top or
    System.Windows.Forms.AnchorStyles.Bottom or
    System.Windows.Forms.AnchorStyles.Left or
    System.Windows.Forms.AnchorStyles.Right;
  typesListBox.Location := System.Drawing.Point.Create(8, 46);
  typesListBox.Name := 'typesListBox';
  typesListBox.Size := System.Drawing.Size.Create(610, 303);
  typesListBox.Font := System.Drawing.Font.Create('Lucida Console', 8.25,
    System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
  typesListBox.TabIndex  := 1;           
end;
   
{ Event handler for the Exit menu item }
procedure ReflectionForm.exitItemClick(sender: TObject; Args: System.EventArgs);
begin
  System.Windows.Forms.Application.Exit;
end;
   
{ Event handler for the Open menu item }
procedure ReflectionForm.openItemClick(sender: TObject; Args: System.EventArgs);
begin
  if openFileDialog.ShowDialog = DialogResult.OK then
  begin
    showFileLabel.Text := 'Showing types in: ' + openFileDialog.FileName;
    PopulateTypes(openFileDialog.FileName);
  end;
end;
   
{ Open the given assembly, and reflect over its modules }
{ and types.                                            }
procedure ReflectionForm.PopulateTypes(fileName : String);
var
  assy: System.Reflection.Assembly;
  modules: array of System.Reflection.Module;
  module: System.Reflection.Module;
  types: array of System.Type;
  t: System.Type;
  members: array of System.Reflection.MemberInfo;
  m: System.Reflection.MemberInfo;
  i,j,k: Integer;
  s: String;
begin
  try
    { Clear the listbox }
    typesListBox.BeginUpdate;
    typesListBox.Items.Clear;
       
    { Load the assembly and get its modules }
    assy    := System.Reflection.Assembly.LoadFrom(fileName);
    modules := assy.GetModules;       
       
    {For every module, get all types }
    for i := 0 to High(modules) do
    begin
      module := modules[i];
      types  := module.GetTypes;
          
      { For every type, get all of its members }
      for j := 0 to High(types) do
      begin
        t := types[j];
        members := t.GetMembers;
              
        { for every member, get type information and add to list box }
        for k := 0 to High(members) do
        begin
          m := members[k];
          s := module.Name + ':' + t.Name + ': ' + m.Name +
            ' (' + m.MemberType.ToString + ')';
          typesListBox.Items.Add(s);
        end;
      end;
    end;
    typesListBox.EndUpdate;
  except
    System.Windows.Forms.MessageBox.Show('Could not load the assembly.');
  end;
end;
   
end.
End example

The unit begins by declaring its dependency on .NET Framework dcuil files and on the Borland.Delphi.SysUtils unit. From there it goes straight into declaring the class for the main form, which is a descendent of the .NET Framework class, System.Windows.Forms.Form. The form class layout looks familiar: You have member variables for all the controls, and these are declared to be of types found in the .NET Framework class library.

The functions exitItemClick and openItemClick are event handler declarations. The signature of event handler methods is specified by the CLR. All event handlers are procedures that take two parameters: the object that fired the event (a derivative of System.Object) and the event arguments, which are wrapped in the System.EventArgs (or a derived) class. (You will see how to hook up these event handlers in a moment.)

Let's move on to the class constructor. I must call attention to the first statement in the constructor, which calls inherited Create.

Warning 

Here you see a major departure from the .NET Framework way of life, compared to what you are used to with the VCL and with Delphi in general. In Delphi, the constructor initializes member variables, putting the object instance into a known-good state; it does not do any memory allocation. So, it is not uncommon to see a constructor make assignments and then call the inherited constructor. Indeed, you might not call the inherited constructor at all. In Delphi for .NET, you can't get away with this approach. In your constructor, you must call inherited Create, and it must be the method's first executable statement. Currently, if you fail to do so, you will get a compiler error saying that Self is uninitialized and that the inherited constructor must be called prior to accessing any ancestor fields.

After calling the inherited constructor, you are back in familiar territory. Although this code uses a different class hierarchy, it should be clear to any Delphi programmer. You make an instance of System.Windows.Forms.OpenFileDialog by calling the Create constructor—this is how you create an instance of any .NET Framework class.

The next few lines demonstrate setting properties, both of the OpenFileDialog object instance and of the form itself. Finally, you add two controls (a label for the filename and the ListBox that will hold the assembly) to the form's Controls collection, which is a property of type Control.ControlCollection.

The InitializeMenu procedure demonstrates allocation and layout of a System.Windows.Forms
.MainMenu object instance. Where the File menu is initialized, a dynamic array holds each menu item. The dynamic array is then passed to the AddRange method. This code could have been accomplished by calling the Add method separately for each menu item.

The next interesting thing in InitializeMenu is the wiring of the menu item event handlers. In Chapter 24 and earlier in this chapter, I mentioned the behind-the-scenes complexity involved with delegates and multicast events. Here you see some of that complexity coming to the foreground.

You can't do it yet in Delphi for .NET, but in other .NET languages such as C#, you can use the language keyword event to introduce an event handler delegate. The event declaration specifies a delegate to use as a callback mechanism. Because the event is a System.MulticastDelegate derivative (a System.EventHandler delegate in this case), other objects can add and remove event handlers, and these handlers are called when the event fires.

The C# language adds a bit of syntactic sugar to help this pill go down more easily. C# defines += and -= operators for adding and removing event handlers, respectively. Eventually Delphi will get its own spoonful of sugar, with the Include/Exclude mechanism mentioned previously. CTS mandates that all .NET compilers targeting this event model must generate methods named add_<Event> and remove_<Event>. These add_ and remove_ methods wrap the Combine and Remove methods declared in System.Delegate.

For now, to assign an event handler, you must use these add_ and remove_ methods; ordinarily, you would not concern yourself with them, because the compiler would hide this complexity. In the current class declaration, you introduce two methods whose signatures match the System.EventHandler delegate: openItemClick and exitItemClick. You then call the add_Click method on the respective menu item, passing your event handler as the callback method.

Now that the setup is out of the way, let's look at the code that reflects over the types defined within an assembly. You can load any assembly (thus creating an object instance), given its filename, with the static LoadFrom method. Once you have an assembly object, the keys to the kingdom are yours; you can use reflection to look over the assembly from any angle.

The collection of modules contained within an assembly is available with the GetModules method. From there you can drill down to the types defined in the module with GetTypes. As you saw in the InitializeMenu procedure, you can use dynamic arrays for properties that expose a collection with a System.Array.

Finally, each individual member of the module and types arrays contains a Name property, which you can use to build a string to display in the ListBox. The final effect of the code is visible in Figure 25.2.

Click To expand
Figure 25.2:  The CLRReflection example, with an assembly loaded

 
Previous Section Next Section


 


 

Delphi Sources


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