使用带有 TWebBrowser 的 IHTMLEventObj 处理程序的内存泄漏

Posted

技术标签:

【中文标题】使用带有 TWebBrowser 的 IHTMLEventObj 处理程序的内存泄漏【英文标题】:Memory leak using IHTMLEventObj handlers with TWebBrowser 【发布时间】:2021-07-04 02:56:52 【问题描述】:

我正在使用 TWebBrowser 来显示 WYSIWYG html 编辑器,并且我添加了一些处理程序来捕获键盘和鼠标事件,因此我可以将此编辑器集成到我的应用程序流中。此浏览器集成在自定义 TPanel 中,TPanelEditorHTML。

这就是我的做法,遵循this answer 的一些提示:

  //Create the procedure type to assign the event
  THTMLProcEvent = procedure(Sender: TObject; Event: IHTMLEventObj) of object;

  //Create a  new class for manage the event from the twebbrowser
  THTMLBrowserEventLink = class(TInterfacedObject, IDispatch)
  private
    FOnEvent: THTMLProcEvent;
  private
    constructor Create(Handler: THTMLProcEvent);
    function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
    function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
    function GetIDsOfNames(const IID: TGUID; Names: Pointer;
      NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
    function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
      Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
  public
    property OnEvent: THTMLProcEvent read FOnEvent write FOnEvent;
  end;

在我的 TWebBrowser 容器上,我有这个:

FOnKeyDownConnector:  IDispatch; 
FOnClickConnector:  IDispatch;
FOnKeyDownConnectorIFrame:  IDispatch;
FOnClickConnectorIFrame:  IDispatch;    

procedure BrowserIHTMLDocument2OnKeyDown(Sender: TObject; Event: IHTMLEventObj);
procedure BrowserIHTMLDocument2OnClick(Sender: TObject; Event: IHTMLEventObj); 
procedure IframeIHTMLDocument2OnKeyDown(Sender: TObject; Event: IHTMLEventObj);
procedure IframeIHTMLDocument2OnClick(Sender: TObject; Event: IHTMLEventObj);

BrowserIHTMLDocument2OnKeyDown 等是我将 HTML 编辑器数据集成到我的应用程序中的所有工作的过程

我在启动时创建处理程序

constructor TPanelEditorHTML.Create(AOwner: TComponent);
begin
  inherited;
  // ...
  FNavegador := TGENBrowser.Create(self);
  FOnKeyDownConnector := THTMLBrowserEventLink.Create(BrowserIHTMLDocument2OnKeyDown);
  FOnClickConnector := THTMLBrowserEventLink.Create(BrowserIHTMLDocument2OnClick);
  FOnKeyDownConnectorIFrame := THTMLBrowserEventLink.Create(IFrameIHTMLDocument2OnKeyDown);
  FOnClickConnectorIFrame := THTMLBrowserEventLink.Create(IFrameIHTMLDocument2OnClick);
end;    

当我加载 HTML 编辑器时,我将此处理程序分配给 DOM 树中的几个元素:

procedure TPanelEditorHTML.AsignarManejadores;
var
  HTMLDocument2_A, HTMLDocument2_B: IHTMLDocument2;
begin
  HTMLDocument2_A := ExtraerIframeEditor;
  HTMLDocument2_B := (FNavegador.Document AS IHTMLDocument2);
  if (HTMLDocument2_A = nil) or (HTMLDocument2_B = nil) then
    Exit;

  if (FOnKeyDownConnectorIFrame <> nil) then
    HTMLDocument2_A.onkeydown := FOnKeyDownConnectorIFrame; 
  if (FOnClickConnectorIFrame <> nil) then
    HTMLDocument2_A.onclick := FOnClickConnectorIFrame; 

  if (FOnKeyDownConnector <> nil) then
    HTMLDocument2_B.onkeydown := FOnKeyDownConnector; 
  if (FOnClickConnector <> nil) then
    HTMLDocument2_B.onclick := FOnClickConnector;   
end;

当用户结束编辑我删除这个处理程序

procedure TPanelEditorHTML.DesconectarManejadores;
var
  HTMLDocument2      : IHTMLDocument2;
begin
  HTMLDocument2 := ExtraerIframeEditor;
  if (HTMLDocument2 <> nil) then
  begin
    HTMLDocument2.onkeydown := Unassigned; //assign the event handler
    HTMLDocument2.onclick := Unassigned; //assign the event handler
  end;

  HTMLDocument2:=(FNavegador.Document AS IHTMLDocument2);
  if (HTMLDocument2 <> nil) then
  begin
    HTMLDocument2.onkeydown := Unassigned; //assign the event handler
    HTMLDocument2.onclick := Unassigned; //assign the event handler
  end;
end;

我的问题出在 TPanelEditorHTML 析构函数上。这导致了四个 THTMLBrowserEventLink 的内存泄漏。如果我尝试 FreeAndNil 处理程序,我会收到运行时错误。

destructor TPanelEditorHTML.Destroy;
begin
  FDataLink.Free;
  FOnKeyDownConnector := Unassigned;
  FOnClickConnector := Unassigned;
  FOnKeyDownConnectorIFrame := Unassigned;
  FOnClickConnectorIFrame := Unassigned;
  inherited;
end;

我发现 this article 关于内存泄漏,我试图替换这两种复制都无济于事的方法。

我错过了什么吗?

【问题讨论】:

你能举一个尽可能短的小例子来重现你的问题吗?使用完整的源代码(.pas 和 .dfm)编辑您的帖子。如果我们可以重现,我们有望建立一个好的答案。 THTMLEventLink.Create中有多余的_AddRef,会导致内存泄漏。 @DalijaPrasnikar 谢谢,这解决了我的问题! 【参考方案1】:

正如@DalijaPrasnikar 所说,THTMLEventLink.Create 中有一个多余的 _AddRef,我从this answer 中提出的解决方案中复制了它。

将 THTMLBrowserEventLink 的构造函数更改为:

constructor THTMLBrowserEventLink.Create(Handler: THTMLProcEvent);
begin
  inherited Create;
  FOnEvent := Handler;
end;

避免内存泄漏。

【讨论】:

原始答案中的其他(或者我应该说是核心)问题(我在那里添加了另一条评论)是 FOnKeyDownConnector 被声明为 THTMLEventLink 并且由于引用计数被破坏,这是“修复”的多余_AddRef 调用。您将变量声明为 IDispatch 的方法是正确的,并且删除了 _AddRef 您现在从内存管理方面获得了良好的代码:)

以上是关于使用带有 TWebBrowser 的 IHTMLEventObj 处理程序的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

使用 TWebBrowser 时查看 Web 控制台

如何禁用 TWebBrowser 上下文菜单?

TWebBrowser 中的方向键切换控件

使用 Set8087CW、SetMXCSR 和 TWebBrowser 屏蔽浮点异常

TWebBrowser句柄关闭请求

使用delphi在Android下的TWebbrowser中全屏观看youtube或其他视频时出现问题