|
Using Microsoft LibrariesThe 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
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. 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.
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
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. |
|
Copyright © 2004-2024 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide |
|