家喻户晓,用 Assembly.LoadFile() 办法对一个程序集文件进行剖析存在肯定的局限性,如果只想分析程序集,然而并不需要执行程序集,应该怎么办呢?明天,通过一个简略的试验来教给大家。
在编写.NET 程序的时候,如果须要对一个程序集文件进行剖析,咱们能够应用 Assembly.LoadFile() 来加载这个程序集,而后对 LoadFile() 办法返回的 Assembly 对象进行进一步的剖析。然而 Assembly.LoadFile() 办法会以执行为目标把程序集加载到程序中,因而它对于被加载的程序集文件有严格的要求,比方,如果被程序集所依赖的程序集不存在,那么 LoadFile() 会抛出异样,再比方,在 .NET Core 中加载 .NET Framework 的程序集,LoadFile() 也会抛出异样。如果咱们只想分析程序集,然而并不需要执行程序集,那么咱们就须要一种单纯地分析程序集文件的形式。
.NET Framework 提供了 Assembly.ReflectionOnlyLoad() 来实现相似的成果,然而这个办法因为依赖于 AppDomain,因而在.NET Core 中不被反对。微软已经在实验室我的项目中提出过一个在.NET Core 中实现这个性能的 System.Reflection.TypeLoader,但不晓得什么起因,没有在 .NET Core 的正式版中提供这个类。
咱们晓得,.NET 程序集是 PE 格局的文件,.NET 中提供了用来剖析 PE 文件的类 PEReader(位于 System.Reflection.Metadata 这个 NuGet 包中),因而咱们能够用 PEReader 来分析程序集文件。
在 PEReader 中,咱们能够通过 TypeDefinitions 获取到程序集中的所有类,咱们能够用 GetMethods() 获取某个类中定义的所有办法。为了晋升效率,TypeDefinitions、GetMethods() 等成员取得到的对象都是 TypeDefinitionHandle、MethodDefinitionHandle 等句柄类型的,这些对象只蕴含地址信息,并不蕴含类型的名字、办法的名字、办法的参数等详细信息,要获取这些信息,咱们须要调用 MetadataReader 的 GetTypeDefinition()、GetMethodDefinition() 等办法来获取。如下的代码用来加载一个程序集,并且输入程序集中所有的类型信息以及类型中定义的办法:
//Install-PackageSystem.Reflection.Metadata
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
string file =@"E:\Microsoft.AspNetCore.Components.Web.dll";
using FileStream fileStream =File.OpenRead(file);
using PEReader peReader = newPEReader(fileStream);
if(!peReader.HasMetadata)
{Console.WriteLine($"{file} doesn't contain CLI metadata.");
return;
}
var mdReader =peReader.GetMetadataReader();
if (!mdReader.IsAssembly)
{Console.WriteLine($"{file} is not an assembly.");
return;
}
foreach (var typeHandler inmdReader.TypeDefinitions)
{var typeDef = mdReader.GetTypeDefinition(typeHandler);
string name = mdReader.GetString(typeDef.Name);
string nameSpace = mdReader.GetString(typeDef.Namespace);
Console.WriteLine($"***********{nameSpace}.{name}***********");
foreach (var methodHandler in typeDef.GetMethods())
{var methodDef = mdReader.GetMethodDefinition(methodHandler);
Console.WriteLine(mdReader.GetString(methodDef.Name));
}
}
应用 PEReader 的时候,咱们须要先取得 XXXHandler,而后再调用 MetadataReader 获取句柄的详细信息,这样做只管性能比拟高,然而代码比拟繁琐,而且在实现某些高级操作的时候比拟麻烦。比方,如果咱们要获取一个程序集的 CustomAttribute 信息,PEReader 并没有提供比较简单的办法,须要咱们对 PE 格局十分精通,能力编写进去对应的代码。
咱们能够应用 AsmResolver.DotNet 这个第三方 Nuget 包来简化程序集文件的读取剖析,它是对 PEReader 的一个高级封装。如下的代码用来加载一个程序集,输入程序集的公司信息,并且输入程序集中所有的类型信息以及类型中定义的办法:
string file =@"E:\Microsoft.AspNetCore.Components.Web.dll";
var moduleDef =AsmResolver.DotNet.ModuleDefinition.FromFile(file);// 用的不是 System.Reflection.Metadata 命名空间下的 ModuleDefinition 类
var asmCompanyAttr =moduleDef.Assembly.CustomAttributes.FirstOrDefault(c =>c.Constructor.DeclaringType.FullName =="System.Reflection.AssemblyCompanyAttribute");
var utf8Value =(Utf8String?)asmCompanyAttr.Signature.FixedArguments[0].Element;
var strValue = (string?)utf8Value;
Console.WriteLine($"companyname:{strValue}");
foreach(var typeDef inmoduleDef.GetAllTypes())
{
string name = typeDef.Name;
string nameSpace = typeDef.Namespace;
Console.WriteLine($"***********{nameSpace}.{name}***********");
foreach (var methodDef in typeDef.Methods)
{Console.WriteLine(methodDef.Name);
}
}
总之,如果咱们须要剖析一个程序集并且要运行其中的代码,咱们能够应用 Assembly.LoadFile();如果咱们不须要运行程序集,只是想分析程序集,那么应用 PEReader 是更好的抉择,当然咱们也能够抉择对 PEReader 进行封装的 AsmResolver.DotNet 这个 NuGet 包。本文作者杨中科在 Zack.Commons 这个开源我的项目中实现“判断一个程序集是否是微软开发的”这个性能的时候就用到了 AsmResolver.DotNet,大家能够查看这个我的项目的 GitHub 代码仓库来查看源代码。