.Net动态加载程序集

  我们先来看.net中与本几个重要的概念:

1.         应用程序域: 由 AppDomain 对象来表示,为执行托管代码提供隔离、卸载和安全边界。多个应用程序域可以在一个进程中运行;但是,在应用程序域和线程之间没有一对一的关联。多个线程可以属于一个应用程序域,尽管给定的线程并不局限于一个应用程序域,但在任何给定时间,线程都在一个应用程序域中执行。应用程序域通过使用 CreateDomain 方法来创建。AppDomain 实例用于加载和执行程序集 (Assembly)。当不再使用 AppDomain 时,可以将它卸载。AppDomain 类实现一组事件,这些事件使应用程序可以在加载程序集、卸载应用程序域或引发未处理的异常时进行响应。
 
2.         程序集是: .NET 框架应用程序的生成块;程序集构成了部署、版本控制、重复使用、激活范围控制和安全权限的基本单元。程序集是为协同工作而生成的类型和资源的集合,这些类型和资源构成了一个逻辑功能单元。程序集为公共语言运行库提供它要用于识别类型实现的信息程序集旨在简化应用程序部署并解决在基于组件的应用程序中可能出现的版本控制问题,程序集可以是静态的或动态的。静态程序集可以包括 .NET 框架类型(接口和类),以及该程序集的资源(位图、JPEG 文件、资源文件等)。静态程序集以 PE 文件格式存储在磁盘上。您还可以使用 .NET 框架来创建动态程序集,动态程序集直接从内存运行并且在执行前不存储到磁盘上。您可以在执行动态程序集后将它们保存在磁盘上。
 
3.         运行库如何定位程序集:若要成功地部署 .NET 框架应用程序,必须了解公共语言运行库如何定位和绑定组成应用程序的程序集。默认情况下,运行库试图与生成应用程序使用的程序集的原版本绑定。这种默认行为可以由配置文件设置覆盖。在试图定位程序集并解析程序集引用时,公共语言运行库将执行若干个步骤。以下几节中对每个步骤进行了解释。当描述运行库如何定位程序集时,通常使用“探测”一词;它是指基于名称和区域性定位程序集时使用的一组试探法。(使用包含在 .NET 框架 SDK 中的程序集绑定日志查看器 (Fuslogvw.exe),来查看日志文件中的绑定信息)。当运行库试图解析对另一个程序集的引用时,就开始进行定位并绑定到程序集的进程。该引用可以是静态的,也可以是动态的。在生成时,编译器在程序集清单的元数据中记录静态引用。动态引用是由于调用各种方法而动态构造的,例如System.Reflection.Assembly.Load 方法。引用程序集的首选方式就是使用完全引用,包括程序集名称、版本、区域性和公钥标记(如果存在)。运行库就会按照本节后面描述的步骤,使用这些信息来定位程序集。无论引用是对静态程序集的引用还是对动态程序集的引用,运行库均使用相同的解析过程。还可通过向调用方法仅提供有关程序集的部分信息的方式(例如仅指定程序集名称),对程序集进行动态引用。在这种情况下,仅在应用程序目录下搜索程序集,不进行其他检查。您可以使用不同加载程序集方法中的任何方法(例如 System.Reflection.Assembly.Load 或 AppDomain.Load)进行部分引用。如果希望运行库在全局程序集缓存和应用程序目录下检查引用的程序集,可以用 System.Reflection.Assembly.LoadWithPartialName 方法指定部分引用。有关部分绑定的更多信息,请参阅部分程序集引用。最后,可以使用诸如 System.Reflection.Assembly.Load 之类的方法进行动态引用并只提供部分信息;然后在应用程序配置文件中用 <qualifyAssembly> 元素限定该引用。该元素使您可以在应用程序配置文件中而不是在代码中提供完全引用信息,包括名称、版本、区域性和公钥标记(如果适用)。如果要在应用程序目录外完全限定对某个程序集的引用,或者如果要引用全局程序集缓存中的程序集,但又希望方便地在配置文件中而不是在代码中指定完全引用,就可以采用这一技术。注意 此类型的部分引用不应用于几个应用程序共享的程序集。因为配置设置是应用于每一应用程序而不是每一个程序集的,所以使用此类型部分引用的共享程序集将要求使用该共享程序集的每一应用程序都要在其配置文件中具有限定信息。
运行库使用以下步骤来解析程序集引用:
1.通过检查适用的配置文件(包括应用程序配置文件、发行者策略文件和计算机配置文件),确定正确的程序集版本。如果配置文件位于远程计算机上,则运行库必须首先定位和下载应用程序配置文件。
2.检查程序集名是否以前已被绑定,如果已绑定,则使用以前加载的程序集。
3.检查全局程序集缓存。如果在其中找到了程序集,则运行库使用该程序集。
4.按以下步骤探测程序集:
a.如果配置策略和发行者策略不影响原始引用,并且绑定请求是使用 Assembly.LoadFrom 方法创建的,则运行库检查位置提示。
b.如果在配置文件中找到了代码基,则运行库只检查该位置。如果该探测失败,则运行库确定绑定请求已失败,并且不再进行其他的探测。
c.使用探测部分中描述的试探法探测程序集。如果探测后没有找到程序集,则运行库请求 Windows 安装程序提供程序集。它用作一个即需即装功能。
注意 不对没有强名称的程序集进行版本检查,并且运行库也不在全局程序集缓存中检查没有强名称的程序集。
 
正式开始本文:
在 .NET 框架中,将程序集加载至应用程序域的方法有几种。每种方法使用不同的类。
您可以使用下面的重载方法将程序集加载至应用程序域: 
  1. System.AppDomain 类包含几种重载的 Load 方法。尽管这些方法可用于将任何程序集成功地加载至当前的或新的应用程序域,但它们主要还是用于 COM 交互操作。您也可以使用 CreateInstance 方法加载程序集。
  2. System.Reflection.Assembly 类包含两种静态重载方法:Load 和 LoadFrom。这两种方法因加载上下文而异。
简单例题:讲解了在一个.exe文件中调用另一个.exe文件的方法
using System;
namespace dy_loadAsse

{
     
class testclass
     
{
          [STAThread]
         
static void Main(string[] args)
         
{
              OutUse test
=new OutUse();
               test.Output();
              Console.ReadLine();

         }

     }

     
class OutUse
     
{
         
public  OutUse()
         
{
         }

         
public void Output()
         
{
              Console.WriteLine(
"test dy load assembly");
         }

     }

}

 
以上编译成功。为dy_loadAsse.exe,执行显示:
test dy load assembly
放在与下面生成的loadexe.exe于同一目录下。
 
文件二:
using System;
using System.Reflection;
namespace Use_dy_Load_Assembly
{
     
class LoadExe
     
{
          [STAThread]
         
static void Main(string[] args)
         
{
              
// Use the file name to load the assembly into the current application domain.
              Assembly a = Assembly.LoadFrom("dy_loadAsse.exe");
              Type [] types2 
= a.GetTypes();
              
foreach (Type t in types2)
              
{
                   Console.WriteLine (t.FullName);
              }

              
//Get the type to use.
              
//Type myType = a.GetType("OutUse"); 这样写老是出错
              Type myType = a.GetType("dy_loadAsse.OutUse");
             Console.WriteLine (myType.FullName);
              
//Get the method to call.
              MethodInfo mymethod = myType.GetMethod("Output");
//            //Create an instance.
            Object obj = Activator.CreateInstance(myType);
//            //Execute the adnamemethod method.
            mymethod.Invoke(obj,null);
              
//执行结果为test dy load assembly
              
//以下是调用dy_loadAsse.exe中的Main方法,出现错误
//                     Type myType = a.GetType("dy_loadAsse.testclass");
//                    Console.WriteLine (myType.FullName);
//                     //Get the method to call.
//                    MethodInfo mymethod = myType.GetMethod("Main");
//            ////          //Create an instance.
//                        Object obj = Activator.CreateInstance(myType);
//            ////          //Execute the adnamemethod method.
//                         mymethod.Invoke(obj,null);     
              Console.ReadLine();
         }

     }

}

 
实际上不管你是.exe或dll 组成的程序集 都可以被加载
 
自定义绑定
 
除了由编译器隐式地用来进行晚期绑定之外(指virtual方法,接口等相关实现的绑定),反射还可以在代码中显式地用来完成晚期绑定。 公共语言运行库支持多种编程语言,但这些语言的绑定规则各不相同。在早期绑定的情况下,代码生成器可以完全控制此绑定。但是,当通过反射进行晚期绑定时,必须用自定义绑定来控制绑定。Binder 类提供了对成员选择和调用的自定义控制。
 
利用自定义绑定,您可以在运行时加载程序集,获取有关该程序集中类型的信息,然后对该类型调用方法或访问该类型的字段或属性。如果您在编译时(例如当对象类型依赖于用户输入时)不知道对象的类型,就可以使用这种方法。
using System;
namespace dy_loaddll
{
     
public class HelloWorld 
     
{
         
// Constant Hello World string.
          private const String m_helloWorld = "Hello World";
         
// Default public constructor.
         public HelloWorld() 
         
{
         }

         
// Print "Hello World" plus the passed text.
         public void PrintHello(String txt) 
         
{
              
// Output to the Console.
              Console.WriteLine(m_helloWorld + " " + txt);
         }

     }

}


编辑生成dy_loaddll.dll,与 LoadExe.exe在同一的目录下面。

using System;
using System.Reflection;
namespace Use_dy_Load_Assembly
{
     
class LoadExe
     
{
          [STAThread]
         
static void Main(string[] args)
         
{
              
// Use the file name to load the assembly into the current application domain.
              Assembly a = Assembly.LoadFrom("dy_loaddll.dll");
              Type [] types2 
= a.GetTypes();
              
foreach (Type t in types2)
              
{
                   Console.WriteLine (t.FullName);
              }

              
//Get the type to use.
//            Type myType = a.GetType(""); 这样写老是出错因为上面的dll加了命名空间
                       Type myType = a.GetType("dy_loaddll.HelloWorld");
                      
//   Type myType = a.GetType("HelloWorld");
                            Console.WriteLine (myType.FullName);
//                          //Get the method to call.
              MethodInfo printMethod = myType.GetMethod("PrintHello");
              
// Create an instance of the HelloWorld class.
              Object obj = Activator.CreateInstance(myType);
              
// Create the args array.
              Object[] test = new Object[1];
              
// Set the arguments.
              test[0= "From CSharp Late Bound";
              
// Invoke the PrintHello method.
              printMethod.Invoke(obj, test);   
              Console.ReadLine();
         }

     }

}

当然我们不禁要问题,如果方法重载,如果是属性。又该怎么办??我们先看看一般的反射的动态方法查找
下面为ms自带的例子:

public class A
{
   
public virtual int method () {return 0;}
}

public class B
{
   
public virtual int method () {return 1;}
}

class Mymethodinfo
{
   
public static int Main()
   
{
      Console.WriteLine (
" Reflection.MethodInfo");
      A MyA 
= new A();
      B MyB 
= new B();
      
//Get the Type and MethodInfo
      Type MyTypea = Type.GetType("A");
      MethodInfo Mymethodinfoa 
= MyTypea.GetMethod("method");
      Type MyTypeb 
= Type.GetType("B");
      MethodInfo Mymethodinfob 
= MyTypeb.GetMethod("method");
      
//Get and display the Invoke method
      Console.Write(" First method - " + MyTypea.FullName +" returns " + Mymethodinfoa.Invoke(MyA, null));
      Console.Write(
" Second method - " + MyTypeb.FullName +" returns " + Mymethodinfob.Invoke(MyB, null));
      
return 0;
   }

}

下面是用接口查询方法,实例创建对象,再执行实例对象


using System;
public interface  IPoint 
{
      
// return  now     class of  shix    interface
        string  ReturnNowClass();
}
    

上面文件为 ClassSuc.cs 编辑为ClassSuc.dll  。

using System;
namespace ClassLib1
{
     
public class Class1:  IPoint
     {
         
public Class1()
         {
         }
         
public string ReturnNowClass()
         {
            
return  "weclone  Execute ClassLib1   Class1";
         }
     }
}

将ClassSuc.dll的也添加到上面文件,Class1实现了Ipoint接口编辑文件为ClassLib1.dll

using System;
namespace ClassLib1
{
     
public class Class1:  IPoint
     
{
         
public Class1()
         
{
         }

         
public string ReturnNowClass()
         
{
            
return  "weclone  Execute ClassLib1   Class1";
         }

     }

}

 将ClassSuc.dll的也添加到上面文件,Class1实现了Ipoint接口编辑文件为ClassLib1.dll

using System;
namespace ClassLib2
{
     
public class Class2:IPoint
     
{
        
public Class2()
         
{
         }

         
public string ReturnNowClass()
         
{
              
return  "ClassLib2"+"Class2"+"weclone";
         }

     }

}

也许你已经看和做的不厌烦了,你可能要要问,你到底想讲什么????注意,将上面的三个dll  copy 在 同一路径下 这里为“C:/test”

using System;
using System.Reflection;
class LoadExe
     
{
         [STAThread]
         
static void Main(string[] args)
         
{
         
// Use the file name to load the assembly into the current application domain.
              Assembly b;
              b 
= Assembly.LoadFrom(@"C:/test/ClassSuc.dll");
              Type[] mytypes 
= b.GetTypes();
              
// show b中的接口IPoint
              foreach (Type t in mytypes)
              
{
                   Console.WriteLine (t.FullName);
              }

              MethodInfo Method 
= mytypes[0].GetMethod("ReturnNowClass");
              
//Get the method to call.
                            Assembly a ;
                            
string  k=Console.ReadLine();
              
//输入大于10时,调用ClassLib1.dll的方法 否则调用ClassLib2的方法
                            if(Convert.ToInt32(k)>10)
                                 a 
= Assembly.LoadFrom(@"C:/test/ClassLib1.dll");
                            
else
                                 a 
= Assembly.LoadFrom(@"C:/test/ClassLib2.dll");
              Type[] types 
= a.GetTypes();
              
// show b中的ClassLib1.Class1或 ClassLib2.Class2
              foreach (Type t in  types)
              
{
                   Console.WriteLine (t.FullName);
              }

         
// Create an instance of the HelloWorld class.
        Object obj = Activator.CreateInstance(types[0]);
              
// Invoke the  method.
              Console.WriteLine(Method.Invoke(obj, null));   
              Console.ReadLine();
         }

     }

执行效果为:
Ipoint
这时要求输入 输入
13
继续执行显示
ClassLib1.Class1
weclone  Execute ClassLib1   Class1
要求输入时 输入
5
继续执行显示
ClassLib2.Class2
weclone  Execute ClassLib2   Class2
实现了什么,通过接口动态加载程序集。
意义:反射机制实现动态插拔,只需更改配置文件和XCOPY相应的组件,
可以无须编译就直接定制出一个特定系统

缺点: 性能冲击 速度慢
有的人还要问,既然能够动态加载程序集
那如何显示卸载程序集  CLR不支持卸载程序集  但可以卸载AppDomain包含的所有的程序集。AppDomain.Unload 方法 卸载指定的应用程序域。
本还想写一文章讲述动态程序集 但自己理解不够,又觉得意义不大所以把那些东西,自己也不想学那些东西(太浮躁的表现),所以提到这里来。
动态程序集是编译器或工具在运行时发出元数据和 MSIL,并可在磁盘上生成可移植可执行 (PE) 文件 (不同于上面的那个动态加载程序集)
在运行时定义程序集,然后运行这些程序集并/或将它们保存到磁盘。
在运行时定义新程序集中的模块,然后运行这些模块并/或将它们保存到磁盘。
在运行时定义类型,创建这些类型的实例,并调用这些类型的方法。
程序集---》 模块---》类型—》实例的方法

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章