通过 IDispatch Invoke 实现识别 COM 事件的调用者
Identifying the caller of a COM event via an IDispatch Invoke implementation
以MSHTML中的HTMLElementEvents2接口为例,每个
EventMethods 传递了一个 pEvtObj 参数,如
HTMLElementEvents2 = dispinterface
[...]
function onclick(const pEvtObj: IHTMLEventObj): WordBool; dispid -600;
确定调用事件的元素。所以,
如果你定义一个 class 继承自 TInterfacedObject 实现
HTMLElementEvents2接口,在实现的事件方法中,可以识别
哪个特定的 HTML 元素调用事件处理程序,因此访问其成员。
能够识别调用事件的调用者对象非常棒
您的处理程序实例从哪里接收事件调用非常重要
多个调用者(例如,当处理程序附加到 MSHTML 示例中的多个 HTML 元素时)。
这种实现 COM 事件处理程序的方法工作正常,但在源代码中有点冗长
术语,因为它需要定义实现事件接口中每个事件的方法。
我假设有一个更简洁的替代方法来实现事件处理程序
在 OleCtrls.Pas 中的 TEventDispatch class 上,它允许您将处理程序附加到
单个事件 - 请参阅我对 q 的回答
Detect when the active element in a TWebBrowser document changes.
该答案中的技术问题是我在 Invoke 实现中看不到识别调用者对象的方法,我的问题是,这可以做到吗?如果可以,怎么做?
我尝试观察传递给答案调用的无类型参数参数,
通过这样的代码:
function TEventObject.Invoke(DispID: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult;
var
vPDispParams : PDispParams;
begin
vPDispParams := PDispParams(@Params);
[...]
但是 vPDispParams 的 rgvarg 成员^(我希望它包含 pEvtObj
parameter) 不包含任何元素,其 cArgs 为零。
我对链接 q 的回答中的代码是我所问内容的 MCVE。
在@IgorTandetnik 的评论帮助下,我非常感谢他,我找到了解决这个问题的方法。
在链接的答案中,我使用了一个有缺陷的 EventObject 分配给 HTML 输入元素,就像这样
var
V : OleVariant;
E : IHtmlElement;
V := Doc.getElementById('input1');
E := IDispatch(V) as IHtmlElement;
// Create an EventObject as per the linked answer
DocEvent := TEventObject.Create(Self.AnEvent, True) as IDispatch;
E.onclick := DocEvent;
在这样做的过程中,我忽略了一个事实,即 IHTMLElement 有一个 OnClick 属性,我将 DocEvent 分配给它,而不是一些想象中的 Events 接口与之相关。
当我使用 ConnectionPoint 替换 E.onclick := DocEvent
时,像这样
ITE := IDispatch(V) as IHtmlInputTextElement;
Assert(ITE <> Nil);
CPC := ITE as IConnectionPointContainer;
Assert(CPC <> Nil);
OleCheck(CPC.FindConnectionPoint(HTMLInputTextElementEvents2, CP));
OleCheck((CP as IConnectionPoint).Advise(DocEvent, Cookie));
,那么 DocEvent 的 .Invoke 方法工作正常。特别是,我可以访问 HTMLInputTextElementEvents2.OnClick 方法的 IEvtObj
使用如下代码:
function TEventObject.Invoke(DispID: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult;
[...]
begin
vPDispParams := PDispParams(@Params);
V := OleVariant(vPDispParams^.rgvArg^[0]);
IDisp := IDispatch(V);
if Supports(IDisp, IHTMLEventObj, IEvtObj) then begin
IHE := IEvtObj.srcElement;
IHE.QueryInterface(IHtmlInputTextElement, ITE);
end;
所以,谜团解开了。我的问题有点 XY 问题 - 我认为我需要识别事件的调用者,而如果我使用 FindConnectionPoint 为我打算使用的事件接口设置事件处理,那不需要出现了。
以MSHTML中的HTMLElementEvents2接口为例,每个 EventMethods 传递了一个 pEvtObj 参数,如
HTMLElementEvents2 = dispinterface
[...]
function onclick(const pEvtObj: IHTMLEventObj): WordBool; dispid -600;
确定调用事件的元素。所以, 如果你定义一个 class 继承自 TInterfacedObject 实现 HTMLElementEvents2接口,在实现的事件方法中,可以识别 哪个特定的 HTML 元素调用事件处理程序,因此访问其成员。
能够识别调用事件的调用者对象非常棒 您的处理程序实例从哪里接收事件调用非常重要 多个调用者(例如,当处理程序附加到 MSHTML 示例中的多个 HTML 元素时)。
这种实现 COM 事件处理程序的方法工作正常,但在源代码中有点冗长 术语,因为它需要定义实现事件接口中每个事件的方法。
我假设有一个更简洁的替代方法来实现事件处理程序 在 OleCtrls.Pas 中的 TEventDispatch class 上,它允许您将处理程序附加到 单个事件 - 请参阅我对 q 的回答 Detect when the active element in a TWebBrowser document changes.
该答案中的技术问题是我在 Invoke 实现中看不到识别调用者对象的方法,我的问题是,这可以做到吗?如果可以,怎么做?
我尝试观察传递给答案调用的无类型参数参数, 通过这样的代码:
function TEventObject.Invoke(DispID: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult;
var
vPDispParams : PDispParams;
begin
vPDispParams := PDispParams(@Params);
[...]
但是 vPDispParams 的 rgvarg 成员^(我希望它包含 pEvtObj parameter) 不包含任何元素,其 cArgs 为零。
我对链接 q 的回答中的代码是我所问内容的 MCVE。
在@IgorTandetnik 的评论帮助下,我非常感谢他,我找到了解决这个问题的方法。
在链接的答案中,我使用了一个有缺陷的 EventObject 分配给 HTML 输入元素,就像这样
var
V : OleVariant;
E : IHtmlElement;
V := Doc.getElementById('input1');
E := IDispatch(V) as IHtmlElement;
// Create an EventObject as per the linked answer
DocEvent := TEventObject.Create(Self.AnEvent, True) as IDispatch;
E.onclick := DocEvent;
在这样做的过程中,我忽略了一个事实,即 IHTMLElement 有一个 OnClick 属性,我将 DocEvent 分配给它,而不是一些想象中的 Events 接口与之相关。
当我使用 ConnectionPoint 替换 E.onclick := DocEvent
时,像这样
ITE := IDispatch(V) as IHtmlInputTextElement;
Assert(ITE <> Nil);
CPC := ITE as IConnectionPointContainer;
Assert(CPC <> Nil);
OleCheck(CPC.FindConnectionPoint(HTMLInputTextElementEvents2, CP));
OleCheck((CP as IConnectionPoint).Advise(DocEvent, Cookie));
,那么 DocEvent 的 .Invoke 方法工作正常。特别是,我可以访问 HTMLInputTextElementEvents2.OnClick 方法的 IEvtObj 使用如下代码:
function TEventObject.Invoke(DispID: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult;
[...]
begin
vPDispParams := PDispParams(@Params);
V := OleVariant(vPDispParams^.rgvArg^[0]);
IDisp := IDispatch(V);
if Supports(IDisp, IHTMLEventObj, IEvtObj) then begin
IHE := IEvtObj.srcElement;
IHE.QueryInterface(IHtmlInputTextElement, ITE);
end;
所以,谜团解开了。我的问题有点 XY 问题 - 我认为我需要识别事件的调用者,而如果我使用 FindConnectionPoint 为我打算使用的事件接口设置事件处理,那不需要出现了。