Using Microsoft Libraries

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;
  reflectForm : ReflectionForm;
  reflectForm := ReflectionForm.Create;

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




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;
  ReflectionForm = class(System.Windows.Forms.Form)
    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; 
    procedure InitializeMenu;
    procedure InitializeControls;
    procedure PopulateTypes(fileName: String);
    { Event Handlers }
    procedure exitItemClick(sender: TObject; Args: System.EventArgs);
    procedure openItemClick(sender: TObject; Args: System.EventArgs);
    constructor Create;       
constructor ReflectionForm.Create;
  inherited Create;
  { 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. }
{ Build the main menu }
procedure ReflectionForm.InitializeMenu;
  menuItemArray : array of System.Windows.Forms.MenuItem;
  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 }
  { Initialize fileMenu }
  fileMenu.Index := 0;
  SetLength(menuItemArray, 3);
  menuItemArray[0] := openItem;
  menuItemArray[1] := separatorItem;
  menuItemArray[2] := exitItem;
  fileMenu.Text := '&File';
  // openItem
  openItem.Index := 0;
  openItem.Text := '&Open...';
  // separatorItem
  separatorItem.Index := 1;
  separatorItem.Text := '-';
  // exitItem
  exitItem.Index := 2;
  exitItem.Text := 'E&xit';
{ Create the controls and populate the form }
procedure ReflectionForm.InitializeControls;
  { 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
  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
  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;           
{ Event handler for the Exit menu item }
procedure ReflectionForm.exitItemClick(sender: TObject; Args: System.EventArgs);
{ Event handler for the Open menu item }
procedure ReflectionForm.openItemClick(sender: TObject; Args: System.EventArgs);
  if openFileDialog.ShowDialog = DialogResult.OK then
    showFileLabel.Text := 'Showing types in: ' + openFileDialog.FileName;
{ Open the given assembly, and reflect over its modules }
{ and types.                                            }
procedure ReflectionForm.PopulateTypes(fileName : String);
  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;
    { Clear the listbox }
    { 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
      module := modules[i];
      types  := module.GetTypes;
      { For every type, get all of its members }
      for j := 0 to High(types) do
        t := types[j];
        members := t.GetMembers;
        { for every member, get type information and add to list box }
        for k := 0 to High(members) do
          m := members[k];
          s := module.Name + ':' + t.Name + ': ' + m.Name +
            ' (' + m.MemberType.ToString + ')';
    System.Windows.Forms.MessageBox.Show('Could not load the assembly.');
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.


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

Part I: Foundations