最近在做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释放。这下彻底解决所有问题了。该方法实现代码如下:
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。
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,如需转载请自行联系原作者