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

最近在做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 }
无害获取程序集元数据的方法——不加载且不锁定程序集、程序集可依赖第三方程序集

 



本文转自道法自然博客园博客,原文链接:http://www.cnblogs.com/baihmpgy/archive/2010/07/30/1789066.html,如需转载请自行联系原作者

上一篇:阿里云服务器怎样购买申请


下一篇:浏览器端CORS策略 + 缓存策略 导致的 跨域策略失效 问题