Loading Assemblies at Runtime

I l @ ve RuBoard

The .NET runtime will load all assemblies referenced by the current assembly as detailed within the manifest of the dependant assembly. In plain English, a Windows Forms application will have the System.Windows.Forms.dll assembly loaded by the runtime on its behalf because the application has an explicit reference to the required assembly. However, sometimes it might be necessary to dynamically load .NET assemblies at runtime. Such uses include application extensions and various tools, such as the VS.NET IDE, which is capable of loading new .NET components for use by developers.

Loading an assembly at runtime is a powerful mechanism to extend an application. Consider an application that allows forms to be added to the application at sometime in the future. As an example, the Microsoft Management Console (MMC) allows for the development of snap-ins, which are basically COM components that are hosted within the shell provided by MMC.

FormHost Project

To illustrate the basic plumbing of loading assemblies at runtime for the purpose of extending an application, a small demo application will be created. The demo application will search in a specific subdirectory for DLL assembly files. Each assembly will be loaded, and the types from that assembly will be inspected. If a type within the assembly is derived from the System.Windows.Forms.Form class and the type supports an extension interface, in this case IFormHostClient , the form will be loaded and a menu item added to the host container.

To create the demo, do the following:

  1. Create a new blank solution within VS.NET with the name of DynamicAsmLoad .

  2. After the blank solution is in place, start by adding a new C# Class library project to the solution. Name this library FormHostSDK . (The FormHostSDK project will do nothing more than provide for a single interface to be used throughout the entire demo.)

  3. Add a new class to the FormHostSDK project named IformHostClient .

Listing 5.1.4 shows the interface definition that will be used by both the FormHost project and a FormOne project.

Listing 5.1.4 IFormHostClient Interface
 1: ///Simple Interface for forms  2: ///  3: using System;  4:  5: namespace FormHostSDK  6: {  7:     public interface IFormHostClient {  8:  9:         string MenuText { 10:             get; 11:         } 12:     } 13: } 

The IFormHostClient interface requires only a single property to be implemented. The MenuText property will be used to add a menu entry to the FormHost shell which will be created next .

With a simple interface defined for client forms to be hosted within a shell application, it's now time to develop the actual container application. Add a new C# Windows Application to the project with the name FormHost . This Windows Application will serve as the container application for hosting MDI child forms, which will be located within assemblies and loaded dynamically by the FormHost application. Listing 5.1.5 shows the code for the FormHost application.

Listing 5.1.5 FormHost
 1: using System;   2: using System.Drawing;   3: using System.Collections;   4: using System.ComponentModel;   5: using System.Windows.Forms;   6: using System.Data;   7: using System.Reflection;   8: using System.IO;   9: using FormHostSDK;  10:  11: namespace FormHost  12: {  13:     /// <summary>  14:     /// Summary description for MainForm.  15:     /// </summary>  16:     public class MainForm : System.Windows.Forms.Form  17:     {  18:         /// <summary>  19:         /// Required designer variable.  20:         /// </summary>  21:         private System.ComponentModel.Container components = null;  22:         private System.Windows.Forms.MdiClient mdiClient1;  23:         private System.Windows.Forms.MainMenu mainMenu1;  24:         private System.Windows.Forms.MenuItem FileMenuItem;  25:         private System.Windows.Forms.MenuItem FormsMenuItem;  26:         private System.Windows.Forms.MenuItem ExitMenuItem;  27:         private Hashtable               forms = new Hashtable( );  28:  29:         public MainForm()  30:         {  31:             //  32:             // Required for Windows Form Designer support  33:             //  34:             InitializeComponent();  35:  36:             //  37:             // TODO: Add any constructor code after  InitializeComponent call  38:             //  39:  40:             LoadForms( );  41:          }  42:  43:          /// <summary>  44:          /// Clean up any resources being used.  45:          /// </summary>  46:          protected override void Dispose( bool disposing )  47:          {  48:                 if( disposing )  49:                 {  50:                    if (components != null)  51:                    {  52:                        components.Dispose();  53:                    }  54:                  }  55:                  base.Dispose( disposing );  56:             }  57:  58:             #region Windows Form Designer generated code  59:             /// <summary>  60:             /// Required method for Designer support - do not modify  61:             /// the contents of this method with the code editor.  62:             /// </summary>  63:             private void InitializeComponent()  64:             {  65:                 this.mdiClient1 = new System.Windows.Forms.MdiClient();  66:                 this.mainMenu1 = new System.Windows.Forms.MainMenu();  67:                 this.FileMenuItem = new System.Windows.Forms.MenuItem();  68:                 this.FormsMenuItem = new System.Windows.Forms.MenuItem( );  69:                 this.ExitMenuItem = new System.Windows.Forms.MenuItem();  70:                 this.SuspendLayout();  71:                 //  72:                 // mdiClient1  73:                 //  74:                 this.mdiClient1.Dock = System.Windows.Forms.DockStyle.Fill;  75:                 this.mdiClient1.Name = "mdiClient1";  76:                 this.mdiClient1.TabIndex = 0;  77:                 //  78:                 // mainMenu1  79:                 //  80:                 this.mainMenu1.MenuItems.AddRange(  81:                            new System.Windows.Forms.MenuItem[] {  82:                                     this.FileMenuItem,  83:                                     this.FormsMenuItem  84:                                                                 }  85:                                                   );  86:                  //  87:                  // FormsMenuItem  88:                  //  89:                  this.FormsMenuItem.Index = 1;  90:                  this.FormsMenuItem.Text = "F&orms";  91:  92:                   //  93:                   // FileMenuItem  94:                   //  95:                   this.FileMenuItem.Index = 0;  96:                   this.FileMenuItem.Text  = "&File";  97:                   this.FileMenuItem.MenuItems.Add( this.ExitMenuItem );  98:  99:                   // 100:                   // menuItemExit 101:                   // 102:                     this.ExitMenuItem.Index = 0; 103:                     this.ExitMenuItem.Text = "E&xit"; 104:                     // 105:                     // MainForm 106:                     // 107:                     this.AutoScaleBaseSize = new 108: System.Drawing.Size(5, 13); 109:                     this.ClientSize = new 110: System.Drawing.Size(576, 429); 111:                     this.Controls.AddRange( 112:                                   new System.Windows.Forms.Control[] { 113:                                                this.mdiClient1 114:                                                                       } 115:                                            ); 116:                     this.IsMdiContainer = true; 117:                     this.Menu = this.mainMenu1; 118:                     this.Name = "MainForm"; 119:                     this.Text = "MainForm"; 120:                     this.ResumeLayout(false); 121: 122:             } 123:             #endregion 124: 125: 126:             /// <summary> 127:             /// load any dynamic forms 128:             /// </summary> 129:             protected void LoadForms( ) { 130:                 string FormsDir = string.Format( 131:                                  "{ 0} \ \ DynamicForms", 132: Application.StartupPath 133:                                                 ); 134: 135:                 //Locate all Assemblies (DLL's only) 136:                 string[] Files = Directory.GetFiles( FormsDir, "*.dll" ); 137: 138:                 foreach( string strFile in Files ) { 139:                  Assembly curAsm = Assembly.LoadFrom( strFile ); 140: 141:                       //Look at the exposed types 142:                       Type[] types = curAsm.GetTypes( ); 143:                       foreach( Type T in types ) { 144:                           if( T.IsSubclassOf( typeof( Form ) ) ) { 145:                             //Create an instance and add to main menu 146:                             Form frm = 147: (Form)curAsm.CreateInstance( T.FullName ); 148:                             forms.Add( 149: ((IFormHostClient)frm).MenuText, T ); 150: 151:                             MenuItem newItem = new MenuItem( 152:                                                ((IFormHostClient)frm) 153:.MenuText, 154:                                                 new EventHandler( 155: this.OnFormSelectMenu ) ); 156:                             FormsMenuItem.MenuItems.Add( newItem ); 157:                           } 158:                        } 159:                  } 160:              } 161: 162:           /// <summary> 163:            /// Create an instance of the requested form 164:            /// </summary> 165:            /// <param name="sender"></param> 166:            /// <param name="e"></param> 167:            protected void OnFormSelectMenu( object sender, EventArgs e ){ 168:                MenuItem mi = (MenuItem)sender; 169:                if( this.forms.ContainsKey( mi.Text ) ) { 170:                    Type T = (Type)this.forms[ mi.Text ]; 171:                    Form frmChild = (Form)Activator.CreateInstance( T ); 172:                    frmChild.MdiParent = this; 173:                    frmChild.Show( ); 174:                } 175:            } 176: 177:             /// <summary> 178:             /// The main entry point for the application. 179:             /// </summary> 180:             [STAThread] 181:             static void Main() 182:             { 183:                  Application.Run(new MainForm()); 184:             } 185:     } 186: } 

Within the FormHost code, there are really only two methods of interest. The first method, LoadForms , which begins on line 127, is responsible for locating assemblies that contain forms that can be hosted within the shell. The LoadForms method begins by retrieving a list of possible assemblies and then iterating through that list. Each assembly then is loaded using the Assembly.LoadFrom static method. The Assembly class is located within the System.Reflection namespace.

After an assembly is loaded, the next step is to extract the various types and then compare those types against two simple tests. The first test is to determine if the loaded type is derived from the base type of System.Windows.Forms.Form . If so, chances are that the type also supports the IFormHostClient interface required by the shell application. If the loaded type is correct, the name is added to the Forms menu and the type information is stored in a hash table.

When the loaded form is selected from the menu, the type is retrieved from the hash table and, using the Activator class, a new instance of the form is created and shown within the MDI container.

To finish the demo, add a new C# class library project to the current DynamicAsmLoad project and name the library FormOne . The FormOne library will contain a single class derived from System.Windows.Forms and also will implement the required IFormHostClient interface. Remember to add a reference to the FormHostSDK project or DLL. In addition, the current FormHost application searches for assemblies located in a subdirectory named DynamicForms , so be sure that the FormOne project will build into this directory. The easiest way to accomplish this is to set the project build properties to point to this directory. Figure 5.1.3 shows the Project Properties dialog and the settings for the Build information.

Figure 5.1.3. FormOne Project Properties.

graphics/0501fig03.gif

The source Listing in 5.1.6 shows the implementation of the FormOne class.

Listing 5.1.6 FormOne
 1: using System;  2: using System.Drawing;  3: using System.Collections;  4: using System.ComponentModel;  5: using System.Windows.Forms;  6:  7: using FormHostSDK;  8:  9: namespace FormOne 10: { 11:     /// <summary> 12:     /// Summary description for FormOne. 13:     /// </summary> 14:     public class FormOne : System.Windows.Forms.Form, FormHostSDK.IFormHostClient 15:     { 16: 17:         /// <summary> 18:         /// Required designer variable. 19:         /// </summary> 20:         private System.ComponentModel.Container components = null; 21: 22: 23:         //Implement the IFormHostClient interface 24:         public string MenuText { 25:             get { 26:               return "Form One"; 27:             } 28:         } 29: 30: 31:         public FormOne() 32:         { 33:             // 34:             // Required for Windows Form Designer support 35:             // 36:             InitializeComponent(); 37: 38:             // 39:             // TODO: Add any constructor code after InitializeComponent call 40:             // 41:          } 42: 43:          /// <summary> 44:          /// Clean up any resources being used. 45:          /// </summary> 46:          protected override void Dispose( bool disposing ) 47:          { 48:              if( disposing ) 49:              { 50:                  if(components != null) 51:           { 52:                     components.Dispose(); 53:                  } 54:               } 55:               base.Dispose( disposing ); 56:           } 57: 58:           #region Windows Form Designer generated code 59:           /// <summary> 60:           /// Required method for Designer support - do not modify 61:           /// the contents of this method with the code editor. 62:           /// </summary> 63:           private void InitializeComponent() 64:           { 65:               this.components = new System.ComponentModel.Container(); 66:               this.Size = new System.Drawing.Size(300,300); 67:               this.Text = "FormOne"; 68:           } 69:           #endregion 70:     } 71: } 

With the completion of the FormOne class, just build the entire solution and launch the FormHost application. If all goes well, a menu item under the Forms menu should appear and, when selected, the FormOne window will appear as shown in Figure 5.1.4.

Figure 5.1.4. FormHost with FormOne showing.

graphics/0501fig04.gif

Although the demo was not very impressive, it should give you enough to enable larger projects with more advanced features. I would suggest developing a container application similar in nature to that of the Microsoft Management Console. Such a project will give you a good understanding of building shell type applications.

Assembly Viewer Light

Throughout this text, the Reflection API has been used to glean information about types and to peer into assemblies. .NET assemblies are so self describing that it is possible to reverse engineer a .NET assembly from IL to C#. The processes of generating C# from IL involves some work, however, generating the basic structure of a .NET class from the description found within an assembly is rather simple.

The Assembly Viewer, shown in Figure 5.1.5, was built in approximately 30 minutes and required no great magic. Although the Assembly Viewer does not import the IL and reverse-engineer it, adding such functionality can be accomplished.

Figure 5.1.5. Assembly Viewer.

graphics/0501fig05.gif

Rather than presenting the entire source here, only the code responsible for generating the C# code from a given System.Type is shown here in Listing 5.1.7.

Listing 5.1.7 Source for TypeSourceEngine
 1: using System;   2: using System.Collections;   3: using System.Reflection;   4:   5: namespace AssemblyViewer.ASMContent   6: {   7:     /// <summary>   8:     /// Summary description for TypeSourceEngine.   9:     /// </summary>  10:     public class TypeSourceEngine  11:     {  12:         public static BindingFlags BF = BindingFlags.Public   13:                                         BindingFlags.NonPublic    14:                                         BindingFlags.Instance   15:                                         BindingFlags.Static   16:                                         BindingFlags.DeclaredOnly;  17:  18:  19:         /// <summary>  20:         /// Generate C# code from a given Type  21:         /// </summary>  22:         /// <param name="T">Type used for code generation</param>  23:         /// <param name="stream">Stream object to write to</param>  24:         public static void GenerateCode( Type T, Stream stream ) {  25:             System.IO.StreamWriter sw = new StreamWriter( stream );  26:  27:             if( T.IsEnum )  28:               GenerateEnum( T, sw );  29:             else  30:               GenerateClassOrInterface( T, sw );  31:  32:         }  33:  34:         /// <summary>  35:         /// If the Type is a class or interface, generate the proper code  36:         /// </summary>  37:         /// <param name="T"></param>  38:         /// <param name="sw"></param>  39:         private static void GenerateClassOrInterface( Type T, StreamWriter sw )  40:         {  41:             ArrayList Fields;  42:             ArrayList Properties;  43:             ArrayList Methods;  44:             ArrayList Events;  45:  46:             GenerateFields( T, out Fields );  47:             GenerateProperties( T, out Properties );  48:             GenerateEvents( T, out Events );  49:             GenerateMethods(T, out Methods );  50:  51:             sw.Write( GenerateTypeStmt( T ) );  52:             sw.Write( "\ r\ n" );  53:             Inject( sw, Fields, "Fields" );  54:             Inject( sw, Properties, "Properties" );  55:               Inject( sw, Events, "Events" );  56:               Inject( sw, Methods, "Methods" );  57:               sw.Write("} ");  58:               sw.Flush( );  59:         }  60:  61:         /// <summary>  62:         /// Generate code for an Enum type  63:         /// </summary>  64:         /// <param name="T"></param>  65:         /// <param name="sw"></param>  66:         private static void GenerateEnum( Type T, StreamWriter sw )  67:         {  68:             ArrayList Fields;  69:             GenerateFields( T, out Fields );  70:             sw.Write( GenerateTypeStmt( T ) );  71:             sw.Write( "\ r\ n" );  72:             Inject( sw, Fields, "Fields" );  73:             sw.Write("} ");  74:             sw.Flush( );  75:         }  76:  77:         /// <summary>  78:         /// Creates a type declaration statement:  79:         /// (public  private) [abstractsealed] [interfaceclassstruct] TypeName graphics/ccc.gif [: [Base Class], [Interfaces] ]  80:         /// </summary>  81:         /// <param name="T"></param>  82:         /// <returns></returns>  83:         private static string GenerateTypeStmt( Type T ) {  84:             string stmt = "";  85:  86:             if( T.IsPublic )  87:                 stmt = "public ";  88:             else if( T.IsNotPublic )  89:                 stmt = "private ";  90:  91:             if( T.IsAbstract )  92:                 stmt += "abstract ";  93:             else if( T.IsEnum )  94:                 stmt += "enum ";  95:             else if( T.IsInterface )  96:                 stmt += "interface ";  97:             else if( T.IsSealed )  98:                 stmt += "sealed ";  99: 100:             if( T.IsClass ) 101:                 stmt += "class "; 102:             else if (T.IsInterface ) 103:                 stmt += "interface "; 104:             else 105:                 stmt += "struct "; 106: 107:             bool bHasBase = false; 108:             stmt += string.Format( "{ 0}  ", T.Name ); 109:             if( T.BaseType != null ) { 110:                 stmt += string.Format(": { 0} ", T.BaseType.Name); 111:                 bHasBase = true; 112:             } 113: 114:             System.Type[] Interfaces  = T.GetInterfaces( ); 115:             if( Interfaces.Length != 0 ) { 116:                 if( !bHasBase ) 117:                     stmt += ": "; 118:                 foreach( Type tt in Interfaces ) { 119:                     stmt += tt.Name; 120:                     stmt += ", "; 121:                  } 122:                  stmt = stmt.Substring(0, stmt.Length - 1); 123:              } 124:              stmt += " { "; 125:              return stmt; 126:         } 127: 128:         /// <summary> 129:         /// Inject source into StreamWriter 130:         /// </summary> 131:         /// <param name="al"></param> 132:         /// <param name="EntryType"></param> 133:         private static void Inject( System.IO.StreamWriter sw, 134:                                     ArrayList al, 135:                                     string EntryType ) { 136:             sw.Write("\ t///////////////////////////////////\ r\ n"); 137:             sw.Write(string.Format("\ t// { 0} \ r\ n", EntryType)); 138:             sw.Write("\ t///////////////////////////////////\ r\ n"); 139:             foreach( string s in al ) { 140:                 sw.Write("\ t"); 141:                 sw.Write( s ); 142:                 sw.Write( "\ r\ n" ); 143:              } 144:              sw.Write("\ r\ n"); 145:          } 146: 147: 148:          /// <summary> 149:          /// Generate Field declarations 150:          /// </summary> 151:          /// <param name="T"></param> 152:          /// <param name="fieldList"></param> 153:          private static void GenerateFields( Type T, out ArrayList fieldList ) 154:          { 155:              fieldList = new ArrayList( ); 156:              FieldInfo[] fields = T.GetFields( BF ); 157:              foreach( FieldInfo fi in fields ) { 158:                  fieldList.Add( GenerateFieldStmt( fi ) ); 159:              } 160:           } 161: 162:         /// <summary> 163:         /// Generate the actual field stmt 164:         /// ie: private int someFieldMember; 165:         /// </summary> 166:         /// <param name="fi"></param> 167:         /// <returns></returns> 168:         private static string GenerateFieldStmt( FieldInfo fi ) { 169: 170:             string stmt; 171: 172:              if( fi.IsPublic ) 173:                  stmt = "public "; 174:              else if( fi.IsPrivate ) 175:                  stmt = "private "; 176:              else 177:                  stmt = "protected "; 178: 179:              if( fi.IsStatic ) 180:                  stmt += "static "; 181: 182:                  stmt += fi.FieldType.Name; 183:                  stmt += " "; 184:                  stmt += fi.Name; 185:                  stmt += ";"; 186:                  return stmt; 187:          } 188: 189:          private static void GenerateProperties( Type T, 190:                                          out ArrayList propertyList ) { 191: 192:              PropertyInfo[] props = T.GetProperties(  ); 193:              propertyList = new ArrayList( ); 194:              foreach( PropertyInfo pi in props ) 195:                   propertyList.Add( GeneratePropStmt( pi ) ); 196: 197:          } 198: 199:          private static string GeneratePropStmt( PropertyInfo pi ) { 200:              string stmt = "public "; 201:              stmt += pi.PropertyType.Name; 202:              stmt += " "; 203:              stmt += pi.Name; 204:              stmt += " {  "; 205:              if( pi.CanRead ) 206:                  stmt += "get; "; 207:              if( pi.CanWrite ) 208:                  stmt += "set; "; 209:              stmt += " } ; "; 210:              return stmt; 211:         } 212: 213: 214:          private static void GenerateMethods( Type T, 215:                                            out ArrayList methodList ) { 216:             MethodInfo[] Methods = T.GetMethods( BF ); 217:             methodList = new ArrayList( ); 218:             foreach( MethodInfo mi in Methods ) 219:                 methodList.Add( GenerateMethodStmt( mi ) ); 220: 221:         } 222: 223:         private static string GenerateMethodStmt( MethodInfo mi ) { 224:            string stmt; 225: 226:            if( mi.IsPublic ) 227:                stmt = "public "; 228:            else if( mi.IsPrivate ) 229:                stmt = "private "; 230:            else if( mi.IsFamily ) 231:                stmt = "protected "; 232:            else 233:                stmt = "protected internal "; 234: 235:            if( mi.IsVirtual ) 236:               stmt += "virtual "; 237:            else if( mi.IsAbstract ) 238:               stmt += "abstract "; 239: 240:            stmt += string.Format("{ 0}  { 1} ( ", mi.ReturnType.Name, mi.Name ); 241:            ParameterInfo[] Params = mi.GetParameters( ); 242:            if( Params.Length > 0 ) { 243:               int i; 244:               for( i = 0; i < Params.Length - 1; i++ ) 245:                   stmt += string.Format("{ 0} , ",GenerateParamStmt( Params[i] ) ); 246:                   stmt += string.Format("{ 0}  ", GenerateParamStmt( Params[ i ] ) ); 247:               } 248:               stmt += ");"; 249:               return stmt; 250:         } 251: 252:          private static string GenerateParamStmt( ParameterInfo pi ) { 253:              string stmt = ""; 254:              if( pi.IsIn ) 255:                  stmt = "[in] "; 256:              else if( pi.IsOut ) 257:                  stmt = "[out] "; 258:              else if ( pi.IsRetval ) 259:                  stmt = "[ref] "; 260:              stmt += string.Format( "{ 0}  { 1} ", pi.ParameterType.Name, pi.Name ); 261:              return stmt; 262:          } 263: 264:          private static void GenerateEvents( Type T, 265:                                              out ArrayList eventList ) { 266:              EventInfo[] Events = T.GetEvents( BF ); 267:              eventList= new ArrayList( ); 268:              foreach( EventInfo ei in Events ) 269:                 eventList.Add( GenerateEventStmt( ei ) ); 270:         } 271: 272:         private static string GenerateEventStmt( EventInfo ei ) { 273:               return string.Format("public { 0}  { 1} ;", 274:                                    ei.EventHandlerType.Name, ei.Name ); 275:         } 276:     } 277: } 

The code generating C# is fairly simple, although Listing 5.1.7 could certainly use some improvements, it does serve as a basis from which to expand. The first improvement to make is the removal of all hand coding of the C# constructs and using the CodeDom class, located in the System.CodeDom namespace, instead. Next, of course, is retrieving the IL from a .NET assembly and generating appropriate statements for the target language.

I l @ ve RuBoard


C# and the .NET Framework. The C++ Perspective
C# and the .NET Framework
ISBN: 067232153X
EAN: 2147483647
Year: 2001
Pages: 204

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net