Delphi 通过IDispatch调用实现识别COM事件的调用方

Delphi 通过IDispatch调用实现识别COM事件的调用方,delphi,com,Delphi,Com,以MSHTML中的HTMLElementEvents2接口为例,每个 EventMethods被传递一个pEvtObj参数,如中所示 HTMLElementEvents2 = dispinterface [...] function onclick(const pEvtObj: IHTMLEventObj): WordBool; dispid -600; 以确定调用该事件的元素。所以 如果定义一个从TInterfacedObject派生的类,该类实现 HTMLElementEvent

以MSHTML中的HTMLElementEvents2接口为例,每个 EventMethods被传递一个pEvtObj参数,如中所示

HTMLElementEvents2 = dispinterface
  [...]
  function  onclick(const pEvtObj: IHTMLEventObj): WordBool; dispid -600;
以确定调用该事件的元素。所以 如果定义一个从TInterfacedObject派生的类,该类实现 HTMLElementEvents2接口,在实现的事件方法中,您可以识别 哪个特定的HTML元素称为事件处理程序,从而访问其成员

能够识别调用该事件的调用者对象非常简单 在处理程序实例接收来自的事件调用时非常重要 多个调用者(例如,当处理程序附加到MSHTML示例中的多个HTML元素时)

这种实现COM事件处理程序的方法工作得很好,但在源代码中有点冗长 术语,因为它需要定义实现事件接口中每个事件的方法

有一种替代的、更简洁的方法来实现事件处理程序,我假设它是基于 在ocletrls.Pas中的TEventDispatch类上,它允许您将处理程序附加到 单一事件-请参阅我对问题的回答

这个答案中的技术问题是,我看不到在调用实现中识别调用方对象的方法,我的问题是,这可以做到吗?如果可以,如何做到

我试着观察传递给答案调用的非类型参数, 通过这样的代码:

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^(我希望包含pEvtObj)的rgvarg成员 参数)不包含任何元素,其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;
  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));
在这样做的过程中,我忽略了一个事实,即IHTMLElement有一个OnClick属性,我将DocEvent分配给它,而不是与它相关的一些想象中的事件接口

当我使用连接点替换
E.onclick:=DocEvent
时,如下所示

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;
  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));

于是,谜团解开了。我的问题有点像XY问题——我认为我需要识别事件的调用者,而如果我使用FindConnectionPoint为我打算使用的事件接口设置事件处理,则不会出现这种需要。

通常,每个源都有一个单独的事件接收器;当然,那个水槽会知道事件的来源。COM连接点是为这个用例设计的;参数中不以任何方式表示源。HTML事件是规则的例外,因为它们是气泡:通常在触发事件的元素之外的元素上处理事件,但在层次结构的某个位置。因此,这些事件的设计者提供了一种特定于应用程序的机制来获取原始源代码。@IgorTandetnik:谢谢,我想我是这么认为的。但是,在我的示例中,在.Invoke的情况下,为什么HTMLElementEvents2中的方法的pEvtObj不存在于传递给它的vPDispParams中?显示如何连接接收器;特别是,您传递给
查找连接点的内容。确保您使用的界面末尾带有
2
(例如
HTMLElementEvents2
而不是
HTMLElementEvents
)。两者之间的区别恰恰在于前者将参数传递给接收器,而后者则不传递。@IgorTandetnik:谢谢,我想你找到了一些有区别的东西。我会看看的,我可以用FindConnectionPoint解决一个问题,然后也许把这个问题记下来,也许用一个新的问题代替它,这取决于。。。