You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Commit后IStorage未解锁报STG_E_SHAREVIOLATION及存储大小配置问题

STG_E_SHAREVIOLATION 报错的原因与正确关闭IStorage的方法

这个报错的核心原因是文件锁被前一个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 13:24:21