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

问题描述

我正在使用 StackOverflow (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

解决方法

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

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

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

更新:据报道此问题已在 10.0 Seattle 中修复,因此不应 在 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 中的错误,该 Disp 的引用计数错误地增加了 +2 而不是 +1。
  • LPersistStreamInit 的赋值使引用计数增加 +1
  • _Release() 的强制转换+赋值使引用计数增加 +1。

函数退出时:

  • 显式 _Release() 递减引用计数 -1 以解决该错误
  • LPersistStreamInit 超出范围时的隐式 _Release() 会减少引用计数 -1
  • Disp 超出范围时的隐式 _Release() 会减少引用计数 -1
  • Document 属性的返回值上的隐式 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; 使引用计数递减 -1。

引用计数已正确平衡。

或者,您也可以这样做:

_Release()

这样,您就不再需要显式的 TWebBrowser.Document

  • IDispatch 属性仍然返回一个 Disp,其引用计数错误地增加了 +2 而不是 +1
  • LPersistStreamInit 的赋值不会使引用计数增加 +1
  • _Release() 的强制转换+赋值使引用计数增加 +1。

函数退出时:

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

引用计数已正确平衡。