當我們需要存儲的數據不再是扁平的字節流,而是具有層級嵌套、類型異構、並發訪問特徵時,傳統文件與關係數據庫都力不從心。Windows 通過 COM 提供的結構化存儲技術,在單個物理文件內構建出類似「文件系統」的邏輯結構——這就是復合文檔。
| 文件系統概念 | 結構化存儲對應概念 | COM 接口 |
|---|---|---|
| 目錄/資料夾 | 存儲對象 | IStorage |
| 文件 | 流對象 | IStream |
| 目錄枚舉 | 存儲/流枚舉 | IEnumStatStg |
一個復合文檔內部可以無限層級嵌套:Storage 包含 Sub-Storages 和 Streams,每個 Stream 至少佔用 512 字節(扇區大小),支持事務提交/回滾、多線程/多進程並發訪問不同部分、增量保存。
STATSTG 統計信息。以下為 ActiveX.pas 中定義的核心接口方法簽名,供開發時速查。
{ 創建子存儲 }
function CreateStorage(
pwcsName: POleStr; grfMode: Longint; dwStgFmt: Longint; reserved2: Longint;
out stg: IStorage): HResult; stdcall;
{ 打開現有子存儲 }
function OpenStorage(
pwcsName: POleStr; const stgPriority: IStorage; grfMode: Longint;
snbExclude: TSNB; reserved: Longint; out stg: IStorage): HResult; stdcall;
{ 創建子流 }
function CreateStream(
pwcsName: POleStr; grfMode: Longint; reserved1, reserved2: Longint;
out stm: IStream): HResult; stdcall;
{ 打開現有子流 }
function OpenStream(
pwcsName: POleStr; reserved1: Pointer; grfMode: Longint; reserved2: Longint;
out stm: IStream): HResult; stdcall;
{ 複製整個存儲樹(可用於「整理碎片」) }
function CopyTo(
ciidExclude: Longint; rgiidExclude: PIID; snbExclude: TSNB;
const stgDest: IStorage): HResult; stdcall;
{ 移動/複製單個元素 }
function MoveElementTo(
pwcsName: POleStr; const stgDest: IStorage; pwcsNewName: POleStr;
grfFlags: Longint): HResult; stdcall; { STGMOVE_MOVE / STGMOVE_COPY }
{ 事務提交 }
function Commit(grfCommitFlags: Longint): HResult; stdcall;
{ STGC_DEFAULT=0, STGC_OVERWRITE=1, STGC_ONLYIFCURRENT=2, STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE=4 }
{ 事務回滾 }
function Revert: HResult; stdcall;
{ 獲取枚舉器 }
function EnumElements(
reserved1: Longint; reserved2: Pointer; reserved3: Longint;
out enm: IEnumStatStg): HResult; stdcall;
{ 刪除/重命名/設置時間/設置 CLSID/狀態位/統計信息 }
function DestroyElement(pwcsName: POleStr): HResult; stdcall;
function RenameElement(pwcsOldName, pwcsNewName: POleStr): HResult; stdcall;
function SetElementTimes(pwcsName: POleStr; const ctime, atime, mtime: TFileTime): HResult; stdcall;
function SetClass(const clsid: TCLSID): HResult; stdcall;
function SetStateBits(grfStateBits, grfMask: Longint): HResult; stdcall;
function Stat(out statstg: TStatStg; grfStatFlag: Longint): HResult; stdcall;
function Read(pv: Pointer; cb: Longint; pcbRead: PLongint): HResult; stdcall;
function Write(pv: Pointer; cb: Longint; pcbWritten: PLongint): HResult; stdcall;
function Seek(dlibMove: Largeint; dwOrigin: Longint; out libNewPosition: Largeint): HResult; stdcall;
{ STREAM_SEEK_SET=0, STREAM_SEEK_CUR=1, STREAM_SEEK_END=2 }
function SetSize(libNewSize: Largeint): HResult; stdcall;
function CopyTo(stm: IStream; cb: Largeint; out cbRead, cbWritten: Largeint): HResult; stdcall;
function Commit(grfCommitFlags: Longint): HResult; stdcall;
function Revert: HResult; stdcall;
function LockRegion(libOffset, cb: Largeint; dwLockType: Longint): HResult; stdcall;
{ LOCK_WRITE=1, LOCK_EXCLUSIVE=2, LOCK_ONLYONCE=4 }
function UnlockRegion(libOffset, cb: Largeint; dwLockType: Longint): HResult; stdcall;
function Stat(out statstg: TStatStg; grfStatFlag: Longint): HResult; stdcall;
function Clone(out stm: IStream): HResult; stdcall;
function Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; stdcall;
function Skip(celt: Longint): HResult; stdcall;
function Reset: HResult; stdcall;
function Clone(out enm: IEnumStatStg): HResult; stdcall;
function StgCreateDocfile(pwcsName: POleStr; grfMode: Longint; reserved: Longint; out stgOpen: IStorage): HResult; stdcall;
function StgOpenStorage(pwcsName: POleStr; stgPriority: IStorage; grfMode: Longint; snbExclude: TSNB; reserved: Longint; out stgOpen: IStorage): HResult; stdcall;
function StgIsStorageFile(pwcsName: POleStr): HResult; stdcall; { 判斷文件是否為復合文檔 }
{ ... 更多 LockBytes 相關函數 ... }
function ReadClassStg(stg: IStorage; out clsid: TCLSID): HResult; stdcall;
function WriteClassStg(stg: IStorage; const clsid: TIID): HResult; stdcall;
function ReadClassStm(stm: IStream; out clsid: TCLSID): HResult; stdcall;
function WriteClassStm(stm: IStream; const clsid: TIID): HResult; stdcall;
tagSTATSTG = record
pwcsName: POleStr; { 元素名稱 }
dwType: Longint; { 類型:STGTY_STORAGE=1, STGTY_STREAM=2, STGTY_LOCKBYTES=3 }
cbSize: Largeint; { 流大小 / 存儲大小 }
mtime: TFileTime; { 修改時間 }
ctime: TFileTime; { 創建時間 }
atime: TFileTime; { 訪問時間 }
grfMode: Longint; { 訪問模式標志 }
grfLocksSupported: Longint; { 支持的鎖類型 }
clsid: TCLSID; { 關聯的類 ID }
grfStateBits: Longint; { 用戶狀態位 }
reserved: Longint;
end;
TStatStg = tagSTATSTG;
Commit/Revert 保證寫入原子性,適合關鍵配置文件。uses ActiveX, ComObj;StgCreateDocfile 創建根 IStorage,grfMode 常用 STGM_READWRITE or STGM_SHARE_EXCLUSIVE or STGM_CREATE。HResult,失敗拋出 OleCheck 異常。_Release,無需手動管理。TStreamAdapter 適配為 TStream,復用 TFileStream/TMemoryStream 邏輯。結構化存儲是 Windows 系統級能力的結晶,雖然 API 風格屬於 COM 經典時代,但其解決「單文件多對象、層級結構、事務並發」的架構思想至今不過時。掌握 IStorage/IStream/IEnumStatStg 三大接口與 STATSTG 結構,即可在 Delphi 中輕鬆實現專業級復合文檔讀寫。下一篇將展示具體的讀寫代碼實戰與性能優化技巧。