Commit后IStorage未解锁报STG_E_SHAREVIOLATION及存储大小配置问题
这个报错的核心原因是文件锁被前一个IStorage实例持有未释放,你的代码存在3个直接触发问题的错误,按以下步骤修正即可:
- 修正存储打开权限:根IStorage不能使用
STGM_WRITE标志,复合文件内部需要读写权限维护元数据表,必须替换为STGM_READWRITE。仅写权限会导致存储内部资源无法正常释放,文件锁残留在内核层面。 - 移除手动删除文件的逻辑:你已经传入了
STGM_CREATE标志,该标志本身就会在创建存储时自动覆盖已存在的同名文件。手动调用DeleteFile时如果前一次存储的句柄还没完全释放,会删除失败,后续创建操作直接触发共享冲突。 - 严格按顺序释放资源:先释放所有子IStream/子IStorage接口,再释放根IStorage接口,释放前无需额外调用特殊方法,将接口赋值为
nil即可触发COM引用计数递减,计数归0时会自动关闭文件句柄释放锁。注意必须检查所有COM API的返回值,若API调用失败返回错误码,不要继续操作无效接口,避免异常导致接口无法正常释放。
修正后的核心代码示例:
procedure storeTextIntoStorageStream( text_ : string ); var documentStorage : IStorage; levelIStream : IStream; i, j : integer; hr : HRESULT; begin // 移除原有手动DeleteFile逻辑 hr := StgCreateDocfile( @fileName[1], STGM_READWRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT or STGM_CREATE, 0, documentStorage ); if FAILED(hr) then OleCheck(hr); // 检查创建结果,失败直接抛出异常 try hr := documentStorage.CreateStream( @streamName[1], STGM_READWRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, 0, levelIStream ); if FAILED(hr) then OleCheck(hr); try i := Length( text_ ); OleCheck(levelIStream.Write( @i, SizeOf(Integer), @j )); OleCheck(levelIStream.Write( @text_[1], i*SizeOf(Char), @j )); finally levelIStream.Commit( STGC_DEFAULT ); levelIStream := nil; // 先释放子流 end; finally documentStorage.Commit( STGC_OVERWRITE ); // 提交时回收冗余空间 documentStorage := nil; // 最后释放根存储 end; end; function readTextFromStorageStream : string; var documentStorage : IStorage; levelIStream : IStream; i, j : integer; hr : HRESULT; begin hr := StgOpenStorage( @fileName[1], nil, STGM_READ or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, nil, 0, documentStorage ); if FAILED(hr) then OleCheck(hr); try hr := documentStorage.OpenStream( @streamName[1], nil, STGM_READ or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, levelIStream ); if FAILED(hr) then OleCheck(hr); try OleCheck(levelIStream.Read( @i, SizeOf(Integer), @j )); SetLength( Result, i ); OleCheck(levelIStream.Read( @Result[1], i*SizeOf(Char), @j )); finally levelIStream := nil; end; finally documentStorage := nil; end; end;
IStorage/IStream空间冗余问题解决方法
你测试中1.6KB内容占用16KB空间是Windows默认复合文件实现的正常现象:默认32位复合文件的初始预留空间为16KB,用来存储文件头、目录元数据,同时预留后续写入的扩容空间,减少文件重分配次数。
可以通过以下方式调整大小、减少冗余:
- 替换旧版
StgCreateDocfile为StgCreateStorageExAPI,传入STGOPTIONS结构自定义扇区大小,可根据场景调整默认512字节/扇区的配置,小文件场景下调扇区大小可以显著降低初始占用。 - 提交存储时使用
STGC_OVERWRITE标志替代默认的STGC_DEFAULT,该标志会在提交时回收所有未使用的预留空间,不会保留扩容用的空闲块,写完后文件大小会更接近实际内容大小。 - 小文件场景下可以加
STGM_SIMPLE标志创建存储,该模式使用简化的元数据结构,大幅降低非事务性单流场景的空间开销,但不支持多流、事务等高级特性。 - 注意:默认复合文件的分配粒度是扇区的整数倍,无法做到和普通文件一样完全无冗余,如果对空间占用要求极高,不需要复合文件的多流、增量访问特性,直接用普通文件API读写即可。
内容的提问来源于stack exchange,提问作者SOLID Developper
相关产品推荐
相关产品推荐

