views:

336

answers:

1

While there are many guides for Browser Helper Objects, i have a hard time finding resources on how to implement scriptable objects (i.e. besides the main control) for content plugins (i.e. embedded in website).
To avoid misunderstandings: the question is about scriptable objects that a plugin object may return to the scripts in a website, e.g. from a method call.

While i guess scriptability in general for these probably works via the usual IDispatch, i don't see how events are to be handled (i.e. for attachEvent). Are you supposed to implement that manually (e.g. handle calls to attachEvent explicitly) or are there only certain Interfaces that you have to implement?

+1  A: 

Update (issuing events from plugins)

Ok, so with gf's additional comments, I see that the desired mechanism is the opposite- issuing events from components provided outside of the DOM.

This is similar in respect to how MSHTML events can be handled, but the plugin's objects will need to inspect objects given to it using a different mechanism.

On the objects that provide events (or support attaching objects for events), provide attachEvent and detachEvent methods through IDispatch (and through dual-interface if desired). If you want this to appear seamless with HTML elements, then you should declare them the same way as they are provided on IHTMLElement. The DISPIDs don't have to match but the order of arguments and types should match.

attachEvent Method (IHTMLElement2) @ MSDN
detachEvent Method (IHTMLElement2) @ MSDN

(from MSHTML.IDL in the platform SDK)

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

When you receive a call via attachEvent, you will need to associate the event name with the object received. Similarly, you will need to clear an association of the object to event name when you receive a call via detachEvent.

When you look to issue an event, examine all the objects you have stored for methods that should be called, matching your event. In theory you don't have to use the same method name as your event name, but in practice it will be easier to maintain and manage if you do. First examine IDispatch itself, calling GetIDsOfNames() to find an exact match for your event. If not, examine IDispatchEx and look for an expando method via GetDispID() that is a match for your event.

IDispatch Interface @ MSDN
IDispatch::GetIDsOfNames @ MSDN (locate recipient event method)
IDispatchEx Interface @ MSDN
IDispatchEx::GetDispID @ MSDN (locate recipient event method)

Last, once you have located the handler from one or the other, call the associated Invoke() method.

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

Initial (handling predefined MSHTML events)

Most event handler objects are created manually, as this allows them to accept MSHTML events via IDispatch when receiving calls through attachEvent() or when assigned to event properties. This mechanism varies from the typical ConnectionPointContainer to EventSink setup that is prevalent in COM. However, creating an object to handle these events is simpler. There are some key differences you should note if you are going to create such an object to handle an event.

  1. The first constraint is that the DISPID and method name of the event received must match the DISPID and method for the event received. Documentation is somewhat sparse on this, but the best place to resolve the correct DISPID is to look at the C++ header files. If you have the Microsoft Platform SDK installed, you can look inside the include subdirectory at the file <mshtmdid.h> (short for MSHTML Dispatch ID). It contains a list of all the relevant MSHTML dispatch IDs.

  2. The second constraint is that IE/MSHTML does not call the binary version of methods declared in vtable-based interfaces, so the call will arrive via IDispatch::Invoke(). This might be an issue for you if your desired framework for COM doesn't take care of routing both types of calls to the same handler in your code.

  3. To create a handler object, you need to create a COM object that supports IUnknown, IDispatch, and IObjectSafety. IUnknown is implicit for either of the other interfaces, but don't forget IObjectSafety.

  4. Not specifically required, but your object should be an apartment-threaded object, to avoid marshaling headaches. Since calls are direct to IDispatch via a VARIANT wrapper, you may encounter issues if you are doing things that require multiple apartments or if you are trying to use free-threaded components. Most frameworks create objects for this model or suggest this type by default (VB6, Delphi, MFC, ATL).

Definitions in the C++ header file mentioned above correspond exactly to items listed on IHTMLElement. Here is one specific item to get you started.

First, the event off of the HTML DOM element.

onclick Property (IHTMLElement) @ MSDN

We note that the name of this property is onclick. Now, to the header file.

MSHTMDID.H @ DDART.NET

The matching item we want is DISPID_EVMETH_ONCLICK (Note the heuristic here: Dispatch ID => Event Method => OnClick). According to the source file, it reuses an existing definition by macro definition.

#define DISPID_EVMETH_ONCLICK                DISPID_CLICK

Some of the definitions overlap or recycle the same DISPIDs defined for general ActiveX/OLE control use. DISPID_CLICK is defined in OleCtl.h, so let's head over there to track down the final value. This header file is also available in the Platform SDK and is also included by default in VC++ installations, at least as far back as VC++ 6.0, as I recall.

OLECTL.H @ DDART.NET

The DISPID we want is -600.

#define DISPID_CLICK                    (-600)

Now, in the IDL for your component, you will either need to declare a method called onclick(), that has this DISPID value; or you will need to handle this DISPID in your handler for IDispatch::Invoke(). If you are using ATL, it doesn't hurt to declare the method and provide dual-layout. Other implementations may vary.

The rest of your development should be typical for scripting objects in Internet Explorer. Also note that most of these DISPIDs fall in the negative range, to avoid collisions with user-defined DISPIDs.

meklarian
Thanks for taking the time. If i get you right here you assume sinking events to scriptable objects in my code? I probably didn't express that clearly enough - i want to go for sinking events from my scriptable objects to script code though, i.e. allow JavaScript to do `attachEvent` on scriptable objects the plugin control provides. Do you know about that?
Georg Fritzsche
Further clarification: the events are custom events.
Georg Fritzsche
Would like to vote up again now - thank you. Its strange that there is no guide for this on MSDN or somewhere else.
Georg Fritzsche
No problem, my pleasure. I went through nearly the same ordeal a long time ago. I ended up piecing the puzzle together little by little.
meklarian
I am a bit confused as to how call the JavaScript functions back via `Invoke()` as i don't know how to get the correct `DISPID`. Interestingly, calling it with dispid 0 on the `IDispatch` from `attachEvent` seems to work, but is that reliable? E.g.: `DISPID id = new DISPID(0); incomingDispatch->Invoke(*id, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, [...]);`.
Georg Fritzsche
I.e. what name to use for `GetIDsOfNames()` or what dispid to use if the `IDispatch` results from this: `myObject.attachEvent("TestEvent", function() { alert("rhubarb"); });`
Georg Fritzsche
From some more reading around it seems that DISPID_VALUE results in the 'default' method being called. Can this always be expected to work on JavaScript functions?
Georg Fritzsche
moved that to another question: http://stackoverflow.com/questions/1681327/is-dispidvalue-reliable-for-invokes-on-idispatchs-from-scripts
Georg Fritzsche
hmmm I think DISPID 0 should work fine- provided that the function from javascript was passed in to your method. if an object was passed in, then you should see the correct method via IDispatchEx. Sorry for the late reply- currently out of the country and not due back until the 15th.
meklarian
i should note that i rarely used that method of callback, though, as the objects that received events under my implementation always had proper member functions matching the event.
meklarian