所谓结构化存储方法,实际是把树状文件系统的原理应用到单个的文件中,使得单个文件也能象文件系统一样包含"子目录","子目录"还可以包含更深层次的"子目录",各个"目录"可以含多个文件,把原来需要多个文件存储的内容按树状结构和层次保存到一个文件中去。对清楚磁盘存储的用户来说,很容易理解这种存储方法可以极大程度的提高磁盘空间使用效率。另外便于在单个文件就能明确内容的归属关系和分类关系,再就是在软件分发过程不需要带一大批的分发文件,可以把数据文件归结到一个文件中去。
常见的*.doc文件就是使用结构化存储的。
结构化存储机制也称为永久存储机制,利用此机制,一个组件可以和另外一个组件共享同一个文件。COM提供了结构化存储机制,即复合文档技术。在一个文件内部构造一个树状层次结构,犹如文件系统的层次结构一样,在文件内部的树状结构中,每个节点可以是两种对象之一:存储对象和流对象。存储对象不包含数据信息,只是作为流对象的容器,把这样的文件称为复合文件,这是结构化存储的核心思想。
流对象是一个由COM实现的组件对象,它实现了基本的接口IStream,引用程序通过此接口访问流对象。
class IStream : public IUnknown
{
virtual HRSULT Read();
virtual HRSULT Write();
virtual HRSULT Seek();
virtual HRSULT SetSize();
virtual HRSULT CopyTo();
virtual HRSULT Commit();//把所有的对象提交给包容该流对象的容器
virtual HRSULT Revert();//取消上次提交以来的所有修改操作
virtual HRSULT LockRegion();//锁住流对象的一段字节数据
virtual HRSULT UnlockRegion();
virtual HRSULT Stat();
virtual HRSULT Clone();
}
存储对象暴露IStorage接口:
class IStream : public IUnknown
{
virtual HRSULT CreateStream();
virtual HRSULT OpenStream();
virtual HRSULT CreateStorage();
virtual HRSULT OpenStorage();
virtual HRSULT CopyTo();
virtual HRSULT Commit();//把所有的对象提交给包容该流对象的容器
virtual HRSULT Revert();//取消上次提交以来的所有修改操作
virtual HRSULT EnumElements();//锁住流对象的一段字节数据
virtual HRSULT DestroyElements();
virtual HRSULT RenameElements();
virtual HRSULT SetElementsTimes();
virtual HRSULT SetClass();
virtual HRSULT SetStateBits();
virtual HRSULT Stat();
}
永久对象需要定义几个常用的永久接口:IPersistStorage,IPersistStream,IPersistStreamInit,IPersistFile。所有的永久接口都从IPersist接口派生,IPersist接口实现了GetClassID函数,通过此函数可以得到永久对象的CLSID。
结构化存储有一下方法:
存取和访问复合文档主要使用定义在 Activex 单元的三个 COM 接口:
IStorage (类似于 Windows 的目录, 也就是文件夹);
IStream (类似于目录中的文件, 不过在这里都是"流", 每个流至少要占用 512 字节);
IEnumStatStg (用于列举 IStorage 的层次结构)
IStorage 中的函数:
//创建一个子 IStorage 接口
function CreateStorage(
pwcsName: POleStr; {指定子 IStorage 接口的名称}
grfMode: Longint; {指定访问模式}
dwStgFmt: Longint; {保留, 须是 0}
reserved2: Longint; {保留, 须是 0}
out stg: IStorage {返回子 IStorage 接口}
): HResult; stdcall;
//打开当前 IStorage 的子 IStorage
function OpenStorage(
pwcsName: POleStr; {指定子 IStorage 接口的名称}
const stgPriority: IStorage; {已存在的 IStorage 接口, 一般为 nil}
grfMode: Longint; {指定访问模式}
snbExclude: TSNB; {是个指针, 一般为 nil; 好像是指定要排除的元素}
reserved: Longint; {保留, 须是 0}
out stg: IStorage {返回打开的子 IStorage 接口}
): HResult; stdcall;
//创建一个子 IStream 接口
function CreateStream(
pwcsName: POleStr; {指定子 IStream 接口的名称}
grfMode: Longint; {指定访问模式}
reserved1: Longint; {保留, 须是 0}
reserved2: Longint; {保留, 须是 0}
out stm: IStream {返回子 IStream 接口}
): HResult; stdcall;
//打开当前 IStorage 的子 IStream
function OpenStream(
pwcsName: POleStr; {指定子 IStream 接口的名称}
reserved1: Pointer; {保留, 须为 nil}
grfMode: Longint; {指定访问模式}
reserved2: Longint; {保留, 须是 0}
out stm: IStream {返回子 IStream 接口}
): HResult; stdcall;
//复制 IStorage, 该函数可以实现“整理文件,释放碎片空间”的功能
function CopyTo(
ciidExclude: Longint; {要排除的元素数, 可以是 0}
rgiidExclude: PIID; {好像是以 PIID 的方式指定要排除的元素, 可以是 nil}
snbExclude: TSNB; {指定要被排除的元素, 一般为 nil}
const stgDest: IStorage {目标 IStorage}
): HResult; stdcall;
//复制或移动 "子 IStorage" 或 "子 IStream"
function MoveElementTo(
pwcsName: POleStr; {要复制或移动的 IStorage 或 IStream 的名称}
const stgDest: IStorage; {目标 IStorage 的名称}
pwcsNewName: POleStr; {给复制或移动后的 IStorage 或 IStream 指定新的名称}
grfFlags: Longint {指定是复制还是移动, 可选值: STGMOVE_MOVE、STGMOVE_COPY}
): HResult; stdcall;
//获取当前 IStorage 的 IEnumStatStg 接口变量; IEnumStatStg 列举了 IStorage 的层次结构
function EnumElements(
reserved1: Longint; {保留, 须为 0}
reserved2: Pointer; {保留, 须为 nil}
reserved3: Longint; {保留, 须为 0}
out enm: IEnumStatStg {返回 IEnumStatStg 接口变量}
): HResult; stdcall;
//删除 "子 IStorage" 或 "子 IStream"
function DestroyElement(
pwcsName: POleStr {指定名称}
): HResult; stdcall;
//重命名 "子 IStorage" 或 "子 IStream"
function RenameElement(
pwcsOldName: POleStr; {原名}
pwcsNewName: POleStr {新名}
): HResult; stdcall;
//设置元素的时间信息
function SetElementTimes(
pwcsName: POleStr; {元素名}
const ctime: TFileTime; {创建时间}
const atime: TFileTime; {访问时间}
const mtime: TFileTime {修改时间}
): HResult; stdcall;
//在当前存储中建立一个特殊的流对象,用来保存 CLSID
function SetClass(
const clsid: TCLSID {}
): HResult; stdcall;
IStream 中的函数:
//从 IStream 中读取数据
function Read(
pv: Pointer; {接受数据的变量的指针}
cb: Longint; {要读取的字节数}
pcbRead: PLongint {实际读出的字节数}
): HResult; stdcall;
//向 IStream 写入数据
function Write(
pv: Pointer; {要写入的数据的指针}
cb: Longint; {要写入的字节数}
pcbWritten: PLongint {实际写入的字节数}
): HResult; stdcall;
//移动指针
function Seek(
dlibMove: Largeint; {要移动的字节数}
dwOrigin: Longint; {指定移动的起点, 三种取值分别是: 开始、当前、结尾}
out libNewPosition: Largeint {返回新位置指针}
): HResult; stdcall;
//dwOrigin 可选值:
STREAM_SEEK_SET = 0; {开始}
STREAM_SEEK_CUR = 1; {当前}
STREAM_SEEK_END = 2; {结尾}
//更改流对象的大小
function SetSize(
libNewSize: Largeint {指定新的大小, 以字节为单位}
): HResult; stdcall;
//复制部分数据到另一个 IStream
function CopyTo(
stm: IStream; {目标 IStream}
cb: Largeint; {要复制的字节数}
out cbRead: Largeint; {从源中实际读出的字节数}
out cbWritten: Largeint {向目标实际写入的字节数}
): HResult; stdcall;
IEnumStatStg 中的函数:
//检索枚举序列中指定数目的项
function Next(
celt: Longint; {}
out elt; {}
pceltFetched: PLongint {}
): HResult; stdcall;
//跳过枚举序列中指定数目的项
function Skip(
celt: Longint {枚举中要跳过的元素数目}
): HResult; stdcall;
//将枚举序列重置到开始处
function Reset: HResult; stdcall;
//再制一个相同的 IEnumStatStg
function Clone(
out enm: IEnumStatStg {}
): HResult; stdcall;
//创建一个复合文档, 并通过参数返回 IStorage 接口
function StgCreateDocfile(
pwcsName: POleStr; {指定文件名}
grfMode: Longint; {指定访问模式}
reserved: Longint; {保留, 须是 0}
out stgOpen: IStorage {返回 IStorage 接口}
): HResult; stdcall;
//打开一个复合文档, 并通过参数返回 IStorage 接口
function StgOpenStorage(
pwcsName: POleStr; {指定文件名}
stgPriority: IStorage; {已存在的 IStorage 接口, 一般为 nil}
grfMode: Longint; {指定访问模式}
snbExclude: TSNB; {是一个 POleStr(双字节字符串)类型的指针, 一般为 nil}
reserved: Longint; {保留, 须是 0}
out stgOpen: IStorage {返回 IStorage 接口}
): HResult; stdcall;
//判断指定文件是否是按照结构化方式存储的
function StgIsStorageFile(
pwcsName: POleStr {文件名}
): HResult; stdcall;