在.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);
}
}
/// <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);
}
}
/// <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;
}