Spiga

无害获取程序集元数据的方法——不加载且不锁定程序集、程序集可依赖第三方程序集

2010-07-30 23:20 by 道法自然, 1341 visits, 收藏, 编辑

最近在做OSGi.NET清单的VS插件编辑器时遇到了一个问题。该编辑器允许用于通过浏览一个程序集dll文件获取其公共类型,从而使得用户可以直接选择来添加一个服务。该编辑器如下图所示。

 

 

大家都知道要加载一个程序集的元数据,我们需要使用Assembly的几个静态方法,如下:

Assembly.Load(File.ReadAllBytes(AssemblyFile));
Assembly.ReflectionOnlyLoad(File.ReadAllBytes(AssemblyFile));
Assembly.LoadFrom(AssemblyFile);
Assembly.ReflectionOnlyLoadFrom(AssemblyFile);
Assembly.LoadFile(AssemblyFile);

 

在使用这些方法加载程序集时,我碰到了3个问题:(1)程序集文件加载后被锁定;(2)程序集引用的第三方程序集如果不存在则无法获取元数据;(3)元数据会加载到当前应用域。

 

文件被锁定后,我们无法对其进行写或者删除,错误结果如下:

 

这个问题会引起VS 2008在编译程序集时输出一个文件被锁定的错误,可以简单通过Load方法来解决。利用Assembly.Load(File.ReadAllBytes(AssemblyFile))和Assembly.ReflectionOnlyLoad(File.ReadAllBytes(AssemblyFile))可以避免文件被锁定。但是如果文件引用第三方程序集,在加载Assembly后,如果调用Assembly.GetTypes()时,会出现ReflectionTypeLoadException异常:

Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information. 

 

Assembly提供的5个程序集加载方法加载的程序集,在获取元数据时,引用的程序集都必须存在。而我们的OSGi.NET平台并不会确保所有程序集都在同一个目录,每一个插件都有自己的程序集库,插件间通过优雅的类加载器实现类型重用。为了解决这个棘手问题,我们只好使用CciMetadata这个开源组件,你可以通过http://ccimetadata.codeplex.com/来下载。通过老赵博客获悉,这个组件是直接读取PE文件然后获取元数据,它不需要将Assembly加载。因此,我决定试一下。

 

经过尝试,CciMetadata确实可以在不加载程序集且程序集依赖项不存在情况下,读取一个程序集的所有元数据信息,然而不幸的是,这个组件有一个Bug,即读取一个程序集的元数据后,该文件会被锁定,这是该编辑器不允许的。于是,我调式了CciMetadata的代码,发现文件锁定是在CreateFileMapping调用后执行的,搜索后发现必须调用UnmapViewOfFile来释放。因此,我必须重写MetadataReaderHost.OpenBinaryDocument方法来记录所有创建的文件映射,并在Dispose释放。这下彻底解决所有问题了。该方法实现代码如下:

8 namespace ccipereader
 9 {
10     internal class HostEnvironment : MetadataReaderHost, IDisposable
11     {
12         /// <summary>
13         /// 释放映射文件句柄,否则文件将一直被block。
14         /// </summary>
15         /// <param name="lpBaseAddress">映射对象句柄。</param>
16         /// <returns>是否释放成功。</returns>
17         [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
18         [return: MarshalAs(UnmanagedType.Bool)]
19         unsafe private  static extern bool UnmapViewOfFile(
20           void* lpBaseAddress // starting address
21         );
34         /// <summary>
35         /// 实现Assembly元数据装载。
36         /// </summary>
37         /// <param name="location"></param>
38         /// <returns></returns>
39         public override IUnit LoadUnitFrom(string location)
40         {
41             var document = BinaryDocument.GetBinaryDocumentForFile(location, this);
42             var unit = peReader.OpenModule(document);
43             this.RegisterAsLatest(unit);
44             return unit;
45         }
46 
47         public override IBinaryDocumentMemoryBlock OpenBinaryDocument(IBinaryDocument sourceDocument)
48         {
49             // 该操作将会调用CreateFileMapping创建映射,但并没有释放,因此在这里需要
50             // 重写该函数并记录下所有映射文件对应的Block。
51             IBinaryDocumentMemoryBlock block = base.OpenBinaryDocument(sourceDocument);
52             memoryBlocks.Add(block);
53             return block;
54         }
64         private void Dispose(bool disposing)
65         {
66             if (disposed)
67                 return;
68             if(disposing)
69             {
70                 unsafe
71                 {
72                     // 释放文件映射,否则该文件将一直被lock直到程序退出。
73                     foreach (var block in memoryBlocks)
74                     {
75                         UnmapViewOfFile((void*)block.Pointer);
76                     }
77                 }
78             }
79             disposed = true;
80         }
           ......


此时使用扩展的MetadataReaderHost读取程序集元数据的代码如下,或者查看附件/Files/baihmpgy/ccipereader.rar

9 namespace ccipereader
10 {
11     class Program
12     {
13         static string AssemblyFile
14         {
15             get
16             {
17                 return Path.Combine(
18                         AppDomain.CurrentDomain.BaseDirectory, 
19                         "DependencyModule.dll");
20             }
21         }
22 
23         static void Main(string[] args)
24         {
25             // 使用CciMetadata项目加载类型元数据
26             LoadTypesByCCI(); //不会锁定文件,且可以在依赖程序集无法找到下获取类型
27 
28             //Assembly.Load(File.ReadAllBytes(AssemblyFile)).GetTypes(); //不会锁定文件,但必须在以来程序集存在情况下才能加载到元数据
29             //Assembly.ReflectionOnlyLoad(File.ReadAllBytes(AssemblyFile)).GetTypes(); //不会锁定文件,但必须在以来程序集存在情况下才能加载到元数据,仅用于读取元数据不能使用
30 
31             //Assembly.LoadFrom(AssemblyFile).GetTypes(); //不会重复加载同名称程序集,锁定文件,但必须在以来程序集存在情况下才能加载到元数据
32             //Assembly.ReflectionOnlyLoadFrom(AssemblyFile).GetTypes(); //不会重复加载同名称程序集,锁定文件,但必须在以来程序集存在情况下才能加载到元数据,仅用于读取元数据不能使用
33 
34             //Assembly.LoadFile(AssemblyFile).GetTypes(); //每次都加载程序集,可以加载多版本,会锁定文件,但必须在以来程序集存在情况下才能加载到元数据
35             Console.ReadLine();
36         }
37 
38         static void LoadTypesByCCI()
39         {
40             using(HostEnvironment host = new HostEnvironment())
41             {
42                 if (!File.Exists(AssemblyFile))
43                 {
44                     return;
45                 }
46                 var assembly = host.LoadUnitFrom(AssemblyFile) as IAssembly;
47                 if (assembly == null)
48                 {
49                     return;
50                 }
51                 var types = assembly.GetAllTypes();
52                 if (types != null)
53                 {
54                     foreach (var type in types)
55                     {
56                         Console.WriteLine(type.ToString());
57                     }
58                 }
59             }
60         }
61     }
62 }

 

Creative Commons License本文基于Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名道法自然(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。

 

Add your comment

4 条回复

  1. #1楼 夜闻香      2010-07-31 05:26
    这个第三方组件能读取全部的元数据信息么?
    如果能那就太霸气了。
     回复 引用 查看   
  2. #2楼 蓝蓝的天      2010-07-31 07:04
    多谢分享
     回复 引用 查看   
  3. #3楼[楼主] 道法自然      2010-07-31 09:52
    @夜闻香
    可以的。“The CCI Metadata API allows applications to efficiently analyze or modify .NET assemblies, modules, and debugging (PDB) files. CCI Metadata supports the functionality of the .NET System.Reflection and System.Reflection.Emit APIs, but with much better performance. It also provides additional functionality that is not available in either .NET API.”
     回复 引用 查看   
  4. #4楼[楼主] 道法自然      2010-07-31 10:13
    刚发现已经有人遇到这个问题了,DLL locked after reading,顺便开了一个Issue并回复一下。
     回复 引用 查看