千里之行,始于足下

酌贪泉而觉爽,处涸辙而犹欢

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

  开发二组正在进行一个基于.net 的文档管理应用项目,需要大量的文件生成和存储操作。组长找到我谈了谈构想,感觉基本类似于 OLE 结构化存储。于是这两天认真琢磨了一下相关的内容。

  一、基本知识

  一个结构化存储文件又称 OLE 复合文档。它可以在一个文件内包含一个文件系统。COM 提供了一个 IStorage 接口来实现相关的功能,无奈 .net 没有提供相对应的接口,只好自己来写。不过后来在一个微软的例子(DVR-MS)中也发现了对应的接口:

 1using System;
 2using System.Runtime.InteropServices;
 3
 4namespace TestStorage.Storage
 5{
 6    [ComImport]
 7    [Guid("0000000B-0000-0000-C000-000000000046")]
 8    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 9    internal interface IStorage
10    {
11        UCOMIStream CreateStream(
12            [In, MarshalAs(UnmanagedType.BStr)] string pwcsName, 
13            [In] uint grfMode, 
14            [In] uint reserved1, 
15            [In] uint reserved2
16            );
17
18        UCOMIStream OpenStream(
19            [In, MarshalAs(UnmanagedType.BStr)] string pwcsName, 
20            [In] IntPtr reserved1, 
21            [In] uint grfMode, 
22            [In] uint reserved2
23            );
24
25        IStorage CreateStorage(
26            [In, MarshalAs(UnmanagedType.BStr)] string pwcsName, 
27            [In] uint grfMode, 
28            [In] uint reserved1, 
29            [In] uint reserved2
30            );
31
32        IStorage OpenStorage(
33            [In, MarshalAs(UnmanagedType.BStr)] string pwcsName, 
34            [In] IntPtr pstgPriority, 
35            [In] uint grfMode, 
36            [In] IntPtr snbExclude, 
37            [In] uint reserved
38            );
39
40        void Commit(
41            [In] uint grfCommitFlags
42            );
43
44        void Revert();
45    }

46}

47

  上面列出了一些最常用的接口方法,具体的内容可以参见微软的例子。关于 CreateStream 和 OpenStream 方法返回的 UCOMIStream 接口,是在 .net framework 1.1 版实现的,在名空间 System.Runtime.InteropServices 里。到 2.0 版则已被废除,改为使用 System.Runtime.InteropServices.ComTypes 里的 IStream 接口,愈加像是 COM 里的 IStream 了。

  实际应用还必须要声明几个重要函数。比如:

1[DllImport("ole32.dll", PreserveSig=false)]
2internal static extern IStorage StgCreateDocfile(
3    [MarshalAs(UnmanagedType.LPWStr)]string pwcsName, 
4    [In] int grfMode, 
5    [In] int reserved
6    );
7

  随后,就可以象在 win32 下一样的使用结构化存储的所有功能了。其中还需要注意的几点是:

  1、C++ 中的 NULL 在 C# 中应当使用 IntPtr.Zero;
  2、IStream 或是 UCOMIStream 的 一些方法需要使用原生指针作为参数,此时应当使用 GCHandle 类将指针“定住”。

  二、存储模式:Direct  vs. Transact
  
  创建或打开一个复合文档,可以使用 Direct 模式,也可以使用 Transact 模式。两者的区别从用词上就看得出来。Direct 是直接写的,Transact 则是事务模式。

  Direct 模式中,数据直接写入文档中去。但经过我的测试发现,在 Direct 模式下,调用 Write 方法写入一个流文件后,还需要调用一次 Commit 方法,才能使用 DocFile Viewer 看到正确的存储结构。因此可以猜想,Direct 模式下虽是直接写入数据,但也要等到 Commit 方法后,索引信息才被写入,这样也为 Rever 方法提供了弹性。

  Transact 模式中,就象数据库中事务一样,直到 Commit 时才真正写入。使用 Transact 模式时要注意,Transact 是不可嵌套的。也就是说,在用 Transact 模式打开一个复合文档后,不可以再使用 Transact 模式打开或创建子存储。否则会出现 COMException。

  Direct 模式和 Transact 模式性能上孰优孰劣?以下是我的两次测试结果,应该能说明一定问题:

        第一次测试结果:
                非事务模式写100兆,4个文件。共耗时 00:00:40.8988096
                事务模式写100兆,4个文件。共耗时 00:00:23.1532928
        第二次测试结果:
                事务模式写78兆,450个文件。共耗时 00:00:12.6982592
                非事务模式写78兆,450个文件。共耗时 00:02:50.2047424

  大体上可以看出,在主要包含较大文件的时候,两者的性能差异不是很大。但是当所包含文件主要都是一些小文件的时候,事务模式的性能显然要优越得多。

  三、进一步应用的设想

  我们所进行的项目,需要写到复合文档中的文件,都是采用一个 GUID 作为文件名。然而在复合文档中,每个元素的名字不能超过 32 位,去掉末尾的 0 字符,也就剩下 31 个,这无论如何是不够的。此外,复合文档的枚举和查找也是一件既麻烦又缺乏效率的事。那么有没有办法解决呢?

  如同数据库一样,也可以使用索引文件。对每一个复合文档,都加入一个索引,比如我们可以用 XML 来描述一个复合文档的内部结构,基本包含每一个元素的相关属性。如此一来,不但可以提供高速的查找和取属性,也非常漂亮地解决了元素名长度的问题。而这个XML文件,甚至可以直接放在复合文档内部。再配合使用 Transact 模式,也基本上可以确保文档的完整性。
posted on 2006-03-20 21:30  sunwaywei  阅读(2887)  评论(4编辑  收藏  举报