在.NET Framework框架中,程序集是重用、安全性以及版本控制的最小單元。程序集的定義為:程序集是一個或多個類型定義文件及資源文件的集合。程序集主要包含:PE/COFF,CLR頭,元數據,清單,CIL代碼,元數據。
PE/COFF文件是由工具生成的,表示文件的邏輯分組。PE文件包含“清單”數據塊,清單是由元數據表構成的另一種集合,這些表描述了構成程序集的文件,由程序集中的文件實現的公開導出的類型,以及與程序集關聯在一起的資源或數據文件。
在托管程序集中包含元數據和IL(微軟的一種中間語言),IL能夠訪問和操作對象類型,並提供了指令來創建和初始化對象、調用對象上的虛方法以及直接操作數組元素。
CLR頭是一個小的信息塊,主要包含模塊在生成是所面向的CLR的major(主)和major(次)版本號;一個標志,一個MethodDef token(指定了模塊的入口方法);一個可選的強名稱數字簽名。
元數據表示一個二進制數據塊,由幾個表構成:定義表,引用表,清單表。
以上是對程序集的構成做了一個簡單的說明,接下來看一下程序集的一些特性:程序集定義了可重用的類型;程序集標記了一個版本號;程序集可以有關聯的安全信息。
在程序運行時,JIT編譯器利用程序集的TypeRef和AssemblyRef元數據表來確定哪一個程序集定義了所引用的類型。JIT編譯器在運行時需要獲取程序集的相關信息,主要包括:名稱、版本、語言文化、公鑰標記等,並將這些連接為一個字符串。JIT編譯器會差查找該標識的程序集,如果查詢到,則將該程序集加載到AppDomain。
接下來介紹一下在CLR中加載程序集的方法:
在System.Refection.Assembly類的靜態方法Load來加載程序集,在加載指定程序集的操作中,會使用LoadFrom()方法,LoadFrom()具有多個重載版本,看一下LoadFrom這個方法的底層實現代碼:
[ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] [MethodImplAttribute(MethodImplOptions.NoInlining)] public static Assembly LoadFrom(String assemblyFile) { Contract.Ensures(Contract.Result<Assembly>() != null); Contract.Ensures(!Contract.Result<Assembly>().ReflectionOnly); StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; return RuntimeAssembly.InternalLoadFrom( assemblyFile, null, // securityEvidence null, // hashValue AssemblyHashAlgorithm.None, false,// forIntrospection false,// suppressSecurityChecks ref stackMark); }
[System.Security.SecurityCritical] // auto-generated [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable internal static RuntimeAssembly InternalLoadFrom(String assemblyFile, Evidence securityEvidence, byte[] hashValue, AssemblyHashAlgorithm hashAlgorithm, bool forIntrospection, bool suppressSecurityChecks, ref StackCrawlMark stackMark) { if (assemblyFile == null) throw new ArgumentNullException("assemblyFile"); Contract.EndContractBlock(); #if FEATURE_CAS_POLICY if (securityEvidence != null && !AppDomain.CurrentDomain.IsLegacyCasPolicyEnabled) { throw new NotSupportedException(Environment.GetResourceString("NotSupported_RequiresCasPolicyImplicit")); } #endif // FEATURE_CAS_POLICY AssemblyName an = new AssemblyName(); an.CodeBase = assemblyFile; an.SetHashControl(hashValue, hashAlgorithm); // The stack mark is used for MDA filtering return InternalLoadAssemblyName(an, securityEvidence, null, ref stackMark, true /*thrownOnFileNotFound*/, forIntrospection, suppressSecurityChecks); }
在加載程序集的操作中,LoadFrom首先會調用Syatem.Reflection.AssemblyName類的靜態方法GetAssemblyName(該方法打開指定文件,查找AssemblyRef元數據表的記錄項,提取程序集標識信息,然後以一個Syatem.Reflection.AssemblyName對象的形式返回這些信息),LoadFrom方法在內部調用Assembly的Load方法,將AssemblyName對象傳給它,CLR會為應用版本綁定重定向策略,並在各個位置查找匹配的程序集。如果Load找到匹配的程序集,就會加載它,並返回代表已加載程序集的一個Assembly對象,LoadFrom方法將返回這個值。
加載程序的另一個方法為LoadFile,這個方法可從任意路徑加載一個程序集,並可將具有相同標識的一個程序集多次加載到一個AppDoamin中。接下來可以看一下LoadFile的底層實現代碼:
[System.Security.SecuritySafeCritical] // auto-generated [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] public static Assembly LoadFile(String path) { Contract.Ensures(Contract.Result<Assembly>() != null); Contract.Ensures(!Contract.Result<Assembly>().ReflectionOnly); AppDomain.CheckLoadFileSupported(); new FileIOPermission(FileIOPermissionAccess.PathDiscovery | FileIOPermissionAccess.Read, path).Demand(); return RuntimeAssembly.nLoadFile(path, null); }
以上對程序集的結構和程序集的加載方法做了一個簡單的說明,需要說明的一點是:程序集不提供卸載的功能。
以下提供幾種較為常用的程序集操作方法:
1.公共屬性和方法:
public static int Minutes = 60; public static int Hour = 60 * 60; public static int Day = 60 * 60 * 24; private readonly int _time; private bool IsCache { get { return _time > 0; } } /// <summary> /// 緩存時間,0為不緩存(默認值:0秒,單位:秒) /// </summary> public ReflectionSugar(int time = 0) { _time = time; } /// <summary> /// 根據程序集路徑和名稱獲取key /// </summary> /// <param name="keyElementArray"></param> /// <returns></returns> private string GetKey(params string[] keyElementArray) { return string.Join("", keyElementArray); } /// <summary> /// key是否存在 /// </summary> /// <param name="key">key</param> /// <returns>存在<c>true</c> 不存在<c>false</c>. </returns> private bool ContainsKey(string key) { return HttpRuntime.Cache[key] != null; } /// <summary> ///獲取Cache根據key /// </summary> private V Get<V>(string key) { return (V)HttpRuntime.Cache[key]; } /// <summary> /// 插入緩存. /// </summary> /// <param name="key">key</param> /// <param name="value">value</param> /// <param name="cacheDurationInSeconds">過期時間單位秒</param> /// <param name="priority">緩存項屬性</param> private void Add<TV>(string key, TV value, int cacheDurationInSeconds, CacheItemPriority priority = CacheItemPriority.Default) { string keyString = key; HttpRuntime.Cache.Insert(keyString, value, null, DateTime.Now.AddSeconds(cacheDurationInSeconds), Cache.NoSlidingExpiration, priority, null); }
2.加載程序集:
/// <summary> /// 加載程序集 /// </summary> /// <param name="path">程序集路徑</param> /// <returns></returns> public Assembly LoadFile(string path) { if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException(path); } try { var key = GetKey("LoadFile", path); if (IsCache) { if (ContainsKey(key)) { return Get<Assembly>(key); } } var asm = Assembly.LoadFile(path); if (IsCache) { Add(key, asm, _time); } return asm; } catch (Exception ex) { throw new Exception(ex.Message); } }
3.根據程序集獲取類型:
/// <summary> /// 根據程序集獲取類型 /// </summary> /// <param name="asm">Assembly對象</param> /// <param name="nameSpace">命名空間</param> /// <param name="className">類名</param> /// <returns>程序集類型</returns> public Type GetTypeByAssembly(Assembly asm, string nameSpace, string className) { try { var key = GetKey("GetTypeByAssembly", nameSpace, className); if (IsCache) { if (ContainsKey(key)) { return Get<Type>(key); } } Type type = asm.GetType(nameSpace + "." + className); if (IsCache) { Add(key, type, _time); } return type; } catch (Exception ex) { throw new Exception(ex.Message); } }
4. 創建對象實例:
/// <summary> /// 創建對象實例 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="fullName">命名空間.類型名</param> /// <param name="assemblyName">程序集(dll名稱)</param> /// <returns></returns> public T CreateInstance<T>(string fullName, string assemblyName) { var key = GetKey("CreateInstance1", fullName, assemblyName); if (IsCache) if (ContainsKey(key)) { return Get<T>(key); } //命名空間.類型名,程序集 var path = fullName + "," + assemblyName; //加載類型 var o = Type.GetType(path); //根據類型創建實例 var obj = Activator.CreateInstance(o, true); var reval = (T)obj; if (IsCache) Add<T>(key, reval, _time); //類型轉換並返回 return reval; }
以上的方法中,根據加載的程序集創建對象後,將獲取的返回值結構加入緩存中。