2507C++,结构化存储与复合文件
原文
结构化存储和复合文件
结构化存储
是一个,抽象了COM
接口背后的文件和目录
主要是IStorage
和IStream
的概念的窗口
技术.它的主要目的
是提供一个在单个物理文件
中有文件系统
层次的方法,这里.
这里
这里
结构化存储
已有多年,其中最著名的用法
是在办公
文件,在使用扩展文件格式
(*.docx,*.pptx
等)之前的办公
文件(*.doc,*.ppt,*.xls
等).
当然,旧格式
仍得到很大的支持.
结构化存储
接口(IStorage
表示目录,IStream
表示文件)只是接口.要实际使用它们
,必须要有一些实现.窗口
提供了一个叫复合文件
的结构化存储
实现,这里.
这些术语有时可互换使用
,但区别很重要:复合文件
只是结构化存储
的一个实现,可能还有其他实现
.复合文件
并不是都基于定义的结构化存储
,但它实现了很多,因此绝对够用.
可下载用图形方式
查看使用复合文件
实现创建的物理文件
的内容的叫SSView
的旧工具(但仍运行良好).
下面是一个更有趣
的示例,使用Sysinternals
的自动运行
工具保留的信息.
尽管它们都在一个文件中,更有趣
的层次清晰可见!
主要接口
IStorage
接口表示一个可包含其他目录
和按IStream
接口实现表示"文件"
的"目录"
.要开始,可用StgCreateStorageEx
创建物理文件
,也可用StgOpenStorageEx
打开现有文件
.
这里
这里
成功时,两者都返回IStorage
指针.从那里,可调用IStorage
上的方法来创建或打开
其他目录(存储)和/或文件(流).
IStorage
上最有用的方法是CreateStorage,CreateStream,OpenStorage
和OpenStream
.使用EnumElements
可枚举存储/流
.
以下是打开复合文件
以读取访问
的示例,文件名
来自命令行
参数:
CComPtr<IStorage> spStg;
auto hr = ::StgOpenStorageEx(argv[1], STGM_READ | STGM_SHARE_EXCLUSIVE,STGFMT_STORAGE, 0, nullptr, nullptr, __uuidof(IStorage), reinterpret_cast<void**>(&spStg));
if (FAILED(hr)) {printf("Failed to open file (0x%X)\n", hr);return hr;
}
下面说明了如何递归枚举给定存储的层次
:
void EnumItems(IStorage* stg, int indent = 0) {CComPtr<IEnumSTATSTG> spEnum;stg->EnumElements(0, nullptr, 0, &spEnum);if (spEnum == nullptr)return;STATSTG stat;while (S_OK == spEnum->Next(1, &stat, nullptr)) {if (indent)printf(std::string(indent, ' ').c_str());printf("%ws", stat.pwcsName);if (stat.type == STGTY_STORAGE) {printf(" [DIR]\n");CComPtr<IStorage> spSubStg;stg->OpenStorage(stat.pwcsName, nullptr,STGM_READ | STGM_SHARE_EXCLUSIVE, 0, 0, &spSubStg);if (spSubStg)EnumItems(spSubStg, indent + 1);}elseprintf(" (%u bytes)\n", stat.cbSize.LowPart);::CoTaskMemFree(stat.pwcsName);}
}
每一项
都有一个名字,但流("文件")
可有数据
.STATSTG
的cbSize
成员返回该大小
.
流只是抽象一堆字节
.要实际读取/写入
流,需要先使用IStorage::OpenStream
打开它,然后再使用IStream::Read,IStream::Write
和类似方法
访问数据
.
流的更多信息
在窗口接口
中到处使用IStream
接口,而不仅是结构化存储
的一部分.它代表了抽象了,理论上可以是任何地方
的缓冲
,这就是抽象
的好处.
给定IStream
指针,你可读取,写入,查找
,复制进另一个流
,克隆,甚至只要实现支持,可提交/恢复事务
.顺便,复合文件
不支持流上的事务
.
在结构化存储
之外,可通过多种方式
取流.
CreateStreamOnHGlobalAPI
在可选HGLOBAL
上创建内存缓冲
(可为无效
以分配新缓冲),并返回该内存缓冲
的IStream
指针.
这里
如,这在处理剪切板
时很有用,因为它需要一个可能不方便使用的HGLOBAL
.
通过取IStream
指针,代码可用它(可能从另一个流
中读取它,或手动填充数据
),然后调用GetHGlobalFromStream
以取底层HGLOBAL
,然后再(如SetClipboardData
)把它传递进剪切板.
只要可方便访问按IStream
抽象的文件数据
,还可调用SHCreateStreamOnFile
直接取文件的流.
这里
活扩
控件持久化中也使用IStream
.
使用IStream
的另一例
是,按COM
对象状态信息"打包",这允许调用CoMarshalInterThreadInterfaceInStream
(可能是名最长的COMAPI
),从不同单元
创建该对象的代理
,该API
(按IStream
)抓要传递给另一个单元(A)
要求的状态,如果需要,A
可调用相应的CoGetInterfaceAndReleaseStream
来生成原对象的代理
.
这里
这里
案例研究:自动运行
早在2021
年,当我在Sysinternals
团队工作时,任务之一
是从GUI
的角度来现代化改造自动运行
.我想我会借机重大重写,这样更容易
维护该工具并按需改进.
这里
自动运行
的功能之一是可保存工具提供的信息
,这样以后可在不同机器
上加载.这很难,因为某些信息
如图标,不容易持久化.
我不记得旧的自动运行
是否保留了它们,但我绝对想这样做.
旧的自动运行
格式是顺序
的,在文件中线性保存数据结构
.要偏移更改需要添加的任何新属性
,这会强制更改格式"版本"
,并在读取各种"旧"格式
的文件时做出正确
的决定.
我想让持久化
更加灵活,所以我决定完全按复合文件
更改格式.使用此方案,添加新属性
不会导致任何问题
,可添加新流,而不干扰其他流.
代码可忽略它不关心的属性
(可能是存储和/或流).这使得该格式
在定义上是可扩展
的,不受任何偏移更改
的影响,且可用(如SSView
)工具,非常容易
的查看.
顺便,持久化图标非常容易
,因为可通过单个调用函数
持久化,自动运行
用来保存图标集合
的图片列表
对象到流中:ImageList_Write
;这非常方便
!
这里
结论
结构化存储
的想法非常强大,窗口
提供的复合文件
实现非常好
且灵活.微软
将Office
移动至新格式
的原因之一是需要使文件更小
,因此新的扩展格式
是ZIP
压缩的.
它们的内部格式
也有变化,且大部分
时间都不使用复合文件
.可压缩结构化存储
文件,从而节省磁盘空间
,同时仍可用存储和流
方便访问.