如何在 Delphi 10.3.3 中访问 TWebBrowser.Document 时克服内存泄漏

Posted

技术标签:

【中文标题】如何在 Delphi 10.3.3 中访问 TWebBrowser.Document 时克服内存泄漏【英文标题】:How can I overcome memory leak in accessing TWebBrowser.Document in Delphi 10.3.3 【发布时间】:2021-05-14 08:07:37 【问题描述】:

我正在使用 *** (How can I get html source code from TWebBrowser) 中的这段代码从网页获取完整响应:

function TMain.GetWebBrowserHTML(const WebBrowser: TWebBrowser): String;
var
  LStream: TStringStream;
  Stream: IStream;
  LPersistStreamInit: IPersistStreamInit;
begin
  if not Assigned(WebBrowser.Document) then
    Exit;
  LStream := TStringStream.Create('');
  try
    LPersistStreamInit := WebBrowser.Document as
      IPersistStreamInit;
    Stream := TStreamAdapter.Create(LStream, soReference);
    LPersistStreamInit.Save(Stream, True);
    Result := LStream.DataString;
  finally
    LStream.Free();
  end;
end;

在对一些大型网页的例程调用了几百次之后,我的内存不足。

显然组件的Document 属性存在已知问题,但将WebBrowser.Document 替换为WebBrowser.DefaultInterface.Document 的建议没有帮助。我真的不想尝试修复 VCL,如果我知道在哪里以及如何做的话,调用Release 的其他建议可能会起作用。泄漏可能完全是另一回事。此代码高于我的工资等级。

我不能使用TIdHTTP,因为必须编写一些脚本,而且我还是需要视觉效果。

另见:TWebbrowser massive memory leaks : no solution so far

【问题讨论】:

你知道究竟是什么泄漏吗?如果是,那是什么?如果没有,请使用 madExcept 之类的工具,它会告诉您已分配的内容和位置,但未释放。这有助于了解这些信息。 有趣的是,这些年来我与TWebBrowser 合作过很多次,但从未意识到这个泄漏。 “WebBrowser.DefaultInterface.Document 替换 WebBrowser.Document 的建议没有帮助” - 为什么不呢? @DelphiCoder TStreamAdapter 实现了IStream 接口,并被分配给IStream 变量。所以普通的接口引用计数会处理释放它。 @KevinDavidson 您是否为 10.3 安装了所有更新和补丁?与此问题相关的RSP-19473 在 10.3.3 中已作为“已修复”关闭。但我刚刚为原始问题提交了RSP-32393,以防万一。 @KevinDavidson 您声称遇到的TOleControl 问题实际上已在西雅图 10.0 中修复,因此您不应该在 10.3 中看到它,除非这是回归或全新泄漏。 【参考方案1】:

显然组件的 Document 属性存在已知问题

供看到此内容的任何人参考:

RSP-32393: Reference leak in TOleControl.GetIDispatchProp and TOleControl.GetIUnknownProp

更新:据报道此问题已在西雅图 10.0 中修复,因此不应 10.3 将不再发生。

我真的不想尝试修复 VCL,如果我知道在哪里以及如何修复,调用 Release 的其他建议可能会起作用。

你可以这样称呼它:

function TMain.GetWebBrowserHTML(const WebBrowser: TWebBrowser): String;
var
  Disp: IDispatch;
  LStream: TStringStream;
  Stream: IStream;
  LPersistStreamInit: IPersistStreamInit;
begin
  Disp := WebBrowser.Document;
  if not Assigned(Disp) then
    Exit;
  try
    LStream := TStringStream.Create('');
    try
      LPersistStreamInit := Disp as IPersistStreamInit;
      Stream := TStreamAdapter.Create(LStream, soReference);
      LPersistStreamInit.Save(Stream, True);
      Result := LStream.DataString;
    finally
      LStream.Free;
    end;
  finally
    Disp._Release;
  end;
end;

因此:

TWebBrowser.Document 属性返回一个IDispatch,由于TOleControl 中的错误,它的引用计数错误地增加了+2 而不是+1 对Disp 的赋值增加了引用计数+1 对LPersistStreamInit 的强制转换+赋值增加了引用计数+1。

函数退出时:

显式 _Release() 减少 refcount -1 以解决该错误 当LPersistStreamInit 超出范围时,隐式_Release() 会减少引用计数-1 当Disp 超出范围时,隐式_Release() 会减少引用计数-1 Document 属性返回值上的隐式 _Release() 会减少引用计数 -1。

引用计数已正确平衡。

或者,您也可以这样做:

function TMain.GetWebBrowserHTML(const WebBrowser: TWebBrowser): String;
var
  Disp: IDispatch;
  LStream: TStringStream;
  Stream: IStream;
  LPersistStreamInit: IPersistStreamInit;
begin
  Pointer(Disp) := WebBrowser.Document;
  if not Assigned(Disp) then
    Exit;
  LStream := TStringStream.Create('');
  try
    LPersistStreamInit := Disp as IPersistStreamInit;
    Stream := TStreamAdapter.Create(LStream, soReference);
    LPersistStreamInit.Save(Stream, True);
    Result := LStream.DataString;
  finally
    LStream.Free;
  end;
end;

这样,您不再需要显式的_Release()

TWebBrowser.Document 属性仍然返回一个 IDispatch,其引用计数错误地增加了 +2 而不是 +1 分配给Disp 不会增加引用计数+1 对LPersistStreamInit 的强制转换+赋值增加了引用计数+1。

函数退出时:

LPersistStreamInit 超出范围时,隐式_Release() 会减少引用计数-1 当Disp 超出范围时,隐式_Release() 会减少引用计数-1 Document 属性返回值上的隐式 _Release() 会减少引用计数 -1。

引用计数已正确平衡。

【讨论】:

在 10.4.1 中签入,支持票证中提到的两个函数中的临时变量为 nil 添加了赋值。 是的,我刚刚确认 nil 分配是在西雅图 10.0 中引入的。所以这段代码不应该在 10.3 中泄漏

以上是关于如何在 Delphi 10.3.3 中访问 TWebBrowser.Document 时克服内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

js twe

delphi中如何实现多线程对Canvas的同时访问

如何在Delphi 10.1 Berlin中安全地访问“共享文件夹”?

Delphi如何访问父窗体

delphi如何访问xml文件

从 Delphi 异步访问 MySQL 数据库