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

Delphi XE7中TWebBrowser存HTML后文件被占用无法写入Blob字段

Delphi XE7中TWebBrowser保存HTML后文件被占用及LoadFromStream使用问题

问题场景

在Delphi XE7环境下,通过自定义WB_SaveAs_HTML函数将TWebBrowser内容保存为本地HTML文件,随后执行LoadFromFile将文件加载到数据库Blob字段时,触发错误:

EFOpen Error cannot open file ... The process cannot access the file "" because it is being used by another process

原保存函数代码:

function WB_SaveAs_HTML(WB:TWebBrowser; const FileName : string):boolean;
var
  PersistStream: IPersistStreamInit;
  Stream: IStream;
  FileStream: TFileStream;
begin
  result:=true;
  if not Assigned(WB.Document) then
  begin
    result:=false;//ShowMessage('Document not loaded!');
    Exit;
  end;

  PersistStream := WB.Document as IPersistStreamInit;
  FileStream := TFileStream.Create(FileName, fmCreate or fmShareDenyNone);
  try
    Stream := TStreamAdapter.Create(FileStream, soReference) as IStream;
    if Failed(PersistStream.Save(Stream, True)) then
      result:=false //ShowMessage('SaveAs HTML fail!');
  finally
    FileStream.Free;
  end;
  (WebBrowser1.Document as IHTMLDocument2).designMode := 'off';
end;

加载Blob字段代码:

(Query.FindField('content') as TBlobField).LoadFromFile(AFileName);

问题原因及解决方案

原因分析

问题出在TStreamAdapter的引用模式:使用soReference时,COM对象会持有TFileStream的引用,导致FileStream.Free执行后,文件句柄并未立即释放(COM引用计数未降为0),后续LoadFromFile无法打开文件。

解决方式1:修改TStreamAdapter所有权模式

将soReference改为soOwned,让TStreamAdapter接管TFileStream的所有权,COM流释放时会自动清理文件流,确保句柄及时关闭。修改后的函数:

function WB_SaveAs_HTML(WB:TWebBrowser; const FileName : string):boolean;
var
  PersistStream: IPersistStreamInit;
  Stream: IStream;
  FileStream: TFileStream;
begin
  result:=true;
  if not Assigned(WB.Document) then
  begin
    result:=false;
    Exit;
  end;

  PersistStream := WB.Document as IPersistStreamInit;
  FileStream := TFileStream.Create(FileName, fmCreate or fmShareDenyNone);
  try
    // 使用soOwned让TStreamAdapter接管FileStream所有权
    Stream := TStreamAdapter.Create(FileStream, soOwned) as IStream;
    if Failed(PersistStream.Save(Stream, True)) then
      result:=false;
  finally
    // 注释掉手动Free,由TStreamAdapter负责释放FileStream
    // FileStream.Free;
  end;
  (WB.Document as IHTMLDocument2).designMode := 'off';
end;

解决方式2:手动释放COM流对象

保留soReference,但在保存完成后手动将Stream置为nil,强制触发COM引用计数清理,释放文件流的引用:

function WB_SaveAs_HTML(WB:TWebBrowser; const FileName : string):boolean;
var
  PersistStream: IPersistStreamInit;
  Stream: IStream;
  FileStream: TFileStream;
begin
  result:=true;
  if not Assigned(WB.Document) then
  begin
    result:=false;
    Exit;
  end;

  PersistStream := WB.Document as IPersistStreamInit;
  FileStream := TFileStream.Create(FileName, fmCreate or fmShareDenyNone);
  try
    Stream := TStreamAdapter.Create(FileStream, soReference) as IStream;
    if Failed(PersistStream.Save(Stream, True)) then
      result:=false;
    Stream := nil; // 手动释放COM流,触发引用计数清理
  finally
    FileStream.Free;
  end;
  (WB.Document as IHTMLDocument2).designMode := 'off';
end;

Query组件LoadFromStream方法使用示例

更高效的方式是跳过本地文件,直接将TWebBrowser内容写入内存流,再加载到Blob字段,彻底避免文件占用问题:

procedure SaveWBContentToBlob(WB: TWebBrowser; Query: TQuery);
var
  PersistStream: IPersistStreamInit;
  Stream: IStream;
  MemStream: TMemoryStream;
  BlobField: TBlobField;
begin
  if not Assigned(WB.Document) then Exit;

  PersistStream := WB.Document as IPersistStreamInit;
  MemStream := TMemoryStream.Create;
  try
    Stream := TStreamAdapter.Create(MemStream, soOwned) as IStream;
    if Succeeded(PersistStream.Save(Stream, True)) then
    begin
      MemStream.Position := 0; // 重置流指针到起始位置
      BlobField := Query.FindField('content') as TBlobField;
      BlobField.LoadFromStream(MemStream);
    end;
    (WB.Document as IHTMLDocument2).designMode := 'off';
  finally
    // soOwned模式下,Stream释放时会自动清理MemStream
  end;
end;

内容的提问来源于stack exchange,提问作者Jacek U

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 05:25:13