在Internet Explorer内容插件中的可脚本化对象

5
尽管有许多关于浏览器助手对象的指南,但我很难找到关于如何为内容插件(即嵌入在网站中)实现可脚本化对象的资源。
为避免误解:问题是关于插件对象可能返回给网站脚本的可脚本化对象的,例如从方法调用中返回的对象。

虽然我猜想这些对象的一般脚本功能可能通过通常的IDispatch实现,但我不知道如何处理事件(即对attachEvent的处理)。您是否应该手动实现它(例如明确处理对attachEvent的调用),还是只需要实现某些接口?

1个回答

3

更新(从插件中发出事件)

好的,根据gf的补充意见,我认为所需机制是相反的-从DOM之外提供的组件发出事件。

这类似于如何处理MSHTML事件,但插件的对象需要使用不同的机制检查给定给它的对象。

在提供事件(或支持附加对象以进行事件)的对象上,通过IDispatch(如果需要,通过双接口)提供attachEvent和detachEvent方法。如果您希望这与HTML元素无缝配合,请像在IHTMLElement上提供的那样声明它们。DISPIDs不必匹配,但参数和类型的顺序应匹配。

在MSDN上查看attachEvent方法(IHTMLElement2)
在MSDN上查看detachEvent方法(IHTMLElement2)

(来自平台SDK中的MSHTML.IDL)

[id(DISPID_IHTMLELEMENT2_ATTACHEVENT)] HRESULT attachEvent(
  [in] BSTR event,
  [in] IDispatch* pDisp,
  [retval, out] VARIANT_BOOL* pfResult);
[id(DISPID_IHTMLELEMENT2_DETACHEVENT)] HRESULT detachEvent(
  [in] BSTR event,
  [in] IDispatch* pDisp);

当您通过attachEvent接收到电话时,您需要将事件名称与接收到的对象相关联。类似地,当您通过detachEvent接收到电话时,您需要清除对象与事件名称的关联。

当您寻求发出事件时,请检查您存储的所有对象以查找应调用的与您的事件匹配的方法。理论上,您不必使用与事件名称相同的方法名称,但实际上,如果这样做,更容易维护和管理。首先检查IDispatch本身,调用GetIDsOfNames()以查找与您的事件完全匹配的内容。如果没有,请检查IDispatchEx,并通过GetDispID()查找与您的事件匹配的扩展方法。

IDispatch接口 @ MSDN
IDispatch::GetIDsOfNames @ MSDN (定位接收方事件方法)
IDispatchEx接口 @ MSDN
IDispatchEx::GetDispID @ MSDN (定位接收方事件方法)

最后,一旦您从其中一个中定位了处理程序,请调用相关的Invoke()方法。

IDispatch::Invoke @ MSDN
IDispatchEx::InvokeEx @ MSDN

初始(处理预定义的MSHTML事件)

大多数事件处理程序对象是手动创建的,因为这样它们可以通过IDispatch接受MSHTML事件,在通过attachEvent()接收调用或分配给事件属性时。这种机制与COM中普遍存在的ConnectionPointContainer到EventSink设置不同。然而,创建一个处理这些事件的对象更简单。如果您要创建这样的对象来处理事件,则应注意一些关键差异。
  1. 第一个限制条件是接收到的事件的DISPID和方法名称必须与所接收到的事件的DISPID和方法匹配。文档有些稀少,但解决正确的DISPID的最佳位置是查看C++头文件。如果您安装了Microsoft Platform SDK,可以在include子目录中查看文件<mshtmdid.h>(MSHTML Dispatch ID的缩写)。它包含所有相关MSHTML分派ID的列表。

  2. 第二个限制是IE/MSHTML不调用基于vtable的接口中声明的二进制版本的方法,因此调用将通过IDispatch::Invoke()到达。如果您所期望的COM框架不能处理将两种类型的调用路由到代码中的同一处理程序,则可能会出现问题。

  3. 要创建处理程序对象,您需要创建支持IUnknown、IDispatch和IObjectSafety的COM对象。IUnknown对于其他任何接口来说都是隐式的,但不要忘记IObjectSafety。

  4. 虽然不是特别要求,但您的对象应该是单元线程对象,以避免编组头疼。由于调用是直接通过VARIANT包装器到IDispatch的,因此如果您正在执行需要多个单元或者尝试使用自由线程组件的操作,可能会遇到问题。大多数框架都为这种模型创建对象或默认建议使用此类型(VB6、Delphi、MFC、ATL)。

C++头文件中的定义与IHTMLElement上列出的项目完全对应。以下是一个具体的项目供您参考。

首先,HTML DOM元素上的事件。

onclick Property (IHTMLElement) @ MSDN

我们注意到此属性的名称为onclick。现在让我们看看头文件。

MSHTMDID.H @ DDART.NET

我们想要匹配的项是DISPID_EVMETH_ONCLICK(请注意这里的启发式方法:Dispatch ID => Event Method => OnClick)。根据源文件,它通过宏定义重用了现有的定义。

#define DISPID_EVMETH_ONCLICK                DISPID_CLICK

一些定义重叠或循环使用了通用的ActiveX/OLE控件DISPIDs。 DISPID_CLICK在OleCtl.h中定义,因此让我们前往该处以查找最终值。这个头文件也可以在Platform SDK中找到,并且默认情况下包含在VC++安装中,至少回溯到VC++ 6.0,我记得是这样的。

OLECTL.H @ DDART.NET

我们需要的DISPID是-600。

#define DISPID_CLICK                    (-600)

现在,在您组件的IDL中,您需要声明一个名为onclick()的方法,并具有此DISPID值; 或者您需要在IDispatch::Invoke()处理程序中处理此DISPID。如果您使用ATL,则最好声明该方法并提供双重布局。其他实现可能会有所不同。
您开发的其余部分应该与Internet Explorer中的脚本对象一样。还要注意,大多数这些DISPID都在负范围内,以避免与用户定义的DISPID冲突。

感谢您抽出时间。如果我理解正确,您认为在我的代码中将事件下沉到可编写对象中?我可能没有表达清楚 - 我想从我的可编写对象向脚本代码下沉事件,即允许JavaScript在插件控制提供的可编写对象上执行attachEvent。您知道这方面的知识吗? - Georg Fritzsche
现在想再次点赞 - 谢谢。奇怪的是MSDN或其他地方没有关于此的指南。 - Georg Fritzsche
1
我有点困惑如何通过 Invoke() 回调 JavaScript 函数,因为我不知道如何获取正确的 DISPID。有趣的是,在从 attachEvent 获取的 IDispatch 上使用 dispid 0 调用似乎可以工作,但这可靠吗?例如:DISPID id = new DISPID(0); incomingDispatch->Invoke(*id, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, [...]); - Georg Fritzsche
从一些更多的阅读中,似乎DISPID_VALUE会导致调用“默认”方法。这在JavaScript函数上总是有效吗? - Georg Fritzsche
嗯,我认为DISPID 0应该可以正常工作——前提是JavaScript中的函数被传递到了你的方法中。如果传递了一个对象,则应该通过IDispatchEx看到正确的方法。抱歉回复晚了——目前不在国内,直到15号才能回来。 - meklarian
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接