• DotNet程序集解析


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

         以上的方法中,根据加载的程序集创建对象后,将获取的返回值结构加入缓存中。

       

  • 相关阅读:
    方法
    逻辑运算符/三元运算符/Scanner
    多线程线程状态和案例演示
    实现多线程的两种方式
    初识多线程
    IO流一些问题的总结
    IO流—其他流
    厦门Android开发三年,工资不到1w,敢问路在何方?
    二本渣渣考研失败,幸得知乎内推,成功拿下Android开发offer!
    2020Android面试心得,已拿到offer
  • 原文地址:https://www.cnblogs.com/pengze0902/p/6043525.html
Copyright © 2020-2023  润新知