Fabulous Adventures In Coding
Eric Lippert is a principal developer on the C# compiler team. Learn more about Eric.
"Named items" are what we call the "top level" objects of the host provided object model. WScript in WSH, window in Internet Explorer, Response in ASP, are all named items. A host tells an initialized script engine about named items via the aptly named AddNamedItem method on the IActiveScript interface.
HRESULT ScriptEngine::AddNamedItem(const WCHAR * pszName, DWORD flags)
A few things should immediately seem a little weird about this interface.
The First Four Flags
First off, what are the flags? There are six. The first four are pretty straightforward:
If SCRIPTITEM_ISVISIBLE is set then the name of the named item is added to the global namespace.
Uh, OK… why would you ever NOT want this set? Didn't I just say that named items were specifically for injecting named object model roots into the engine? What good is a named item if you can't see its name?
That brings us to the second flag; if SCRIPTITEM_GLOBALMEMBERS is set then all (immediate) children of the named item are treated as though they are themselves top-level objects/methods. That's how in Internet Explorer you can say
and they do the same thing. IE tells the script engine that window is a visible named item with global members.
Now it makes a little more sense why you might want to have an invisible named item. What if you had an object with lots of methods and properties that you wanted available in the global namespace, but the object itself didn’t have a sensible name? I can't think of any script host offhand that does that, but the capability is there if you need it.
There's a second reason why you might want a named item to be invisible, but we'll get to that in a minute.
The third flag is SCRIPTITEM_ISSOURCE. If that's set then we know that this object is an event source. If the language supports implicit event binding and the host moves the engine into connected state, we're going to need to know which named items to hook up to which events. It can be very expensive to do that hookup, so this provides a simple optimization. If the host knows that a particular named item does not source events, it can choose to not mark the named item as a source, and we therefore never spend any time trying to hook up event sinks to it.
The fourth flag is SCRIPTITEM_ISPERSISTENT. Recall that I said a while back that when the engine goes back to uninitialized state after being initialized, we throw away "some" named items, where "some" was to be defined later. Now you know -- the engine remembers named items marked as persistent. Information about those named items is not thrown away until the engine is closed. Also, cloning an engine is basically making a copy of the uninitialized state of an engine, so persistent named items get cloned when their engine gets cloned. As we'll see, this fact has implications for our implementation.
I'm sure you have a general idea of what I mean by a "module", though if you Google define:module you'll see that everyone has a slightly different definition. Modules in the script engine sense are philosophically fairly straightforward. I often want to have some way to say "this collection of functions can play with each other, but are isolated from this other collection of functions". I want to be able to resolve name collisions by having two methods with the same name coexist in different modules.
Well, I want that stuff in languages designed for "programming in the large". When using script languages, more often than not you simply don't need to chunk stuff into modules. But, bizarrely enough, the script engines support modules, and in a pretty goofy way at that. Of course, any language implementor can implement module semantics however they want, but I see no reason to mess around with the de facto standards. Here's how modules work in VBScript and JScript:
Let me try to make this a little more concrete. Let's consider a hypothetical declarative language that supports embedded imperative script. You might want to have something like this:
<application> <script> function toggle ( x ) if (x == false) return true else return false </script> <form name="fred"> <checkbox name="chuck" checked="false" /> <button name="bob"> <caption>Hello world!</caption> <click> fred.checked = toggle(fred.checked) </click> <script> function foo() // button-specific code here
And so on. Suppose the host defined fred as a visible named item with global members, bob and chuck as named items each with their own module. Then code in bob's event handlers can access anything in the global application namespace and any member of fred. But if we now add script to chuck, chuck's module cannot see bob's code. chuck is free to define its own function foo, which will not collide.
What if bob wants to access code in chuck's module? We'll see in a later entry how chuck can do that -- there are many subtle issues involved and we'll need more infrastructure built into SimpleScript before we can tackle it.
Where's the Object?
One more weird thing -- where's the object? The method takes a name and some flags, but no object.
Let me answer that question with another question: suppose a persistent named item's object is thread affinitized, and you clone the engine and initialize the clone on a different thread? That thing can't use the object from the previous thread!
To solve this problem, we defer getting the actual object until we need it. The engine calls the site back and asks for the object. I haven't implemented that code yet; I'll talk more about that next time.
I've added a named item list object to SimpleScript in nameditemlist.cpp but I haven't quite figured out how I want to implement modules yet.
As you can see, I've rolled my own hash table rather than using hash_map. There was quite a long series of comments the other day about the pros and cons. I chose to roll my own rather than using an off-the-shelf solution for several reasons:
All those things totally outweigh the negligible benefits of re-using an existing generic map, no matter how bulletproof and performant it is.
That reminds me, I wanted to talk more about the script engine threading model. My earlier post on the subject glossed over some important details.
My team hit our Whidbey Beta One Zero Bug Bounce yesterday, so I'm off for a long weekend far, far away from computers. I'll cut it short right now; next week I'll pick it up again and talk a bit more about the threading model, the implications of engine cloning, and I'll kick around some ideas about how to implement modules and code blocks.