Wherefore IDispatchEx?

Wherefore IDispatchEx?

  • Comments 17

Quite a while back I said that I'd write a post describing why we invented IDispatchEx. I already covered the part about using it to probe the stack for security reasons, but what other reasons did we implement that thing? The documentation is pretty much accurate, but sparse in places. (According to the doc, GetNameSpaceParent, uh, gets the namespace parent.) I won't go into the flag-for-flag level of detail that the doc does well, but I thought I might describe what features motivated the various methods on IDispatchEx. The features are:

Expando objects

In most objects, the methods you've got are the ones you get. But JScript objects and some IE DOM objects can have arbitrary, user-defined properties and methods added to them at will. We call such objects "expando" objects.

Strictly speaking, IDispatchEx is not required to implement an expando object. You could have an implementation of IDispatch which added a new named field every time GetIdsOfNames was called, and everything would work just fine. What IDispatchEx gives you is the ability to pass in a flag (fdexNameEnsure) that specifically requests expando semantics. An object model could choose to, say, only create new fields when the flag was passed in and error out if an unknown name was passed in without the flag. This would then allow the caller to probe for the existence of a property without creating it as a side effect. (Those semantics are up to the callee though -- callers cannot assume that not passing the flag means no expando semantics!)

Use the GetDispID method to access this feature.

Delete members

JScript has a delete operator which does not work at all like C++'s delete operator. Rather, JScript's delete operator removes an expando field from an object. It's pretty useless actually, because doing so does not free up any memory beyond simply clearing the field. Why not? Because implementers must ensure that if the property is re-added that it gets the same dispatch identifier the second time; someone might be caching the dispid. That means that the property bucket and its name has to be kept around, and worse, that every property bucket needs a flag that marks whether it's deleted or not. (The implementer must also ensure that property enumeration continues to work even if the enumerator -- see below -- is presently sitting on a property that was just deleted.) Use DeleteMemberByName or DeleteMemberByDispID in the unlikely event that you want to use this feature.

Case sensitivity

Visual Basic is case-insensitive, and IDispatch was built by the VBA team back in the day, so they never implemented support for case-sensitive languages. JScript is case sensitive, so IDispatchEx lets you pass in flags (fdexNameCaseSensitive, fdexNameCaseInsensitive) on GetDispID that control the case-sensitivity of the lookup.

Property enumeration

In the non-expando world, the property set of an object is stable, so there's little need to enumerate it. If you did need to enumerate it, you could always just look at the static typeinfo. But constructing dynamic typeinfos in a world with expando objects is difficult and expensive, so instead we added the ability to enumerate the valid dispatch identifiers (GetNextDispID) and turn the identifier back into a name (GetMemberName). This is how JScript's for-in loop is implemented.

Constructors

JScript functions may be called as constructors, which is a little weird and has different semantics from calling a regular function. IDispatchEx adds a DISPATCH_CONSTRUCT flag that can be passed to InvokeEx to let the callee know that it is being invoked as a constructor.

Namespace chaining

JScript supports some pretty intense scope resolution semantics, well beyond the local/class/global scoping rules in VBScript. Think closures, or with blocks, which allow construction of scope chains of arbitrary length. But that's not all -- it gets even more fun.

To make the this object semantics that you guys were arguing about the other day work, if you call a function that has a this argument, and it was invoked with the fdexImplicitParents flag, then we run up the GetNameSpaceParent chain to get all the parent scope objects and stuff them into the scope chain for the function invocation. Don't try this at home, kids; I barely remember how any of this stuff works.

Debugger support

Finally IDispatchEx has a GetMemberProperties method which is never used by JScript. Rather, it's used by the script debugger so that the debugger can tell you whether a given field is a method, property, etc.

So there you go -- a whole lot of new features that were difficult to shoehorn into IDispatch, so we implemented a new interface.

 

  • Thanks for your post, it was very helpful. I implemented IDispatchEx in my C++ objects, and now I'm able to see dynamic properties in MSVC during script debugging. The only problem is:

    I have my own C++ objects that acts like a JScript arrays. I want to see them in debugger in the same form I see JScript array, but I don't know how to implement this. As I get, MSVC debugger shows JScript arrays in special way, don't using usual GenNextDISPID scheme. How can add the same behavior to my C++ classes? I implemented _NewEnum and returned IEnumVARIANT from it but this didn't helped.

  • It seems I got it :) I should simply return numbers as a members names! So, it seems MSVC script debugger didn't make a difference between:

    a = [1, 2];

    and

    a = {"0":1, "1":2};

Page 2 of 2 (17 items) 12