使用带有 TWebBrowser 的 IHTMLEventObj 处理程序的内存泄漏
Memory leak using IHTMLEventObj handlers with TWebBrowser
我正在使用 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 关于内存泄漏的问题,我尝试替换这两种复制方法都无济于事。
我是不是漏掉了什么?
如@DalijaPrasnikar said there is a superfluous _AddRef in THTMLEventLink.Create, wich I copied from the solution proposed in this answer.
将 THTMLBrowserEventLink 的构造函数更改为:
constructor THTMLBrowserEventLink.Create(Handler: THTMLProcEvent);
begin
inherited Create;
FOnEvent := Handler;
end;
避免内存泄漏。
我正在使用 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 关于内存泄漏的问题,我尝试替换这两种复制方法都无济于事。
我是不是漏掉了什么?
如@DalijaPrasnikar said there is a superfluous _AddRef in THTMLEventLink.Create, wich I copied from the solution proposed in this answer.
将 THTMLBrowserEventLink 的构造函数更改为:
constructor THTMLBrowserEventLink.Create(Handler: THTMLProcEvent);
begin
inherited Create;
FOnEvent := Handler;
end;
避免内存泄漏。