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

最近在做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  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值