Eric's Complete Guide To Type Signatures Of Scriptable Object Models

Eric's Complete Guide To Type Signatures Of Scriptable Object Models

Rate This
  • Comments 9

Moving away from the problems of junior high school

Here's a question I've gotten many times over the years: how do you design an object so that it can be easily called from both VBScript and JScript?

COM defines a fairly complex type system, and the script engines by design only support a subset of that type system.  It is certainly possible to create objects that can easily be called from C++ but which cannot be easily called from other languages such as VB6, VBScript, and especially JScript.

I've blogged about the individual issues many times; I've been asked for a document explaining it all in one place twice in the last month, so let me sum up.  I'll link back to the main articles for more details.

Note that this is fundamentally a list of "don'ts", not "dos".  Note also that there is plenty more to say about designing object models that are intuitive, discoverable, testable, documentable, extensible, etc, etc, etc.  Maybe I'll talk about that some day, but today I'm just concerned about the very basic question of what the implementor of method type signatures should avoid.

Numeric Types

VBScript can do arithmetic on VT_I2 (short signed integer), VT_I4 (long signed integer), VT_R4 (single float), VT_R8 (double float), VT_CY (currency), and VT_UI1 (unsigned byte) data.  Eight-byte integers, unsigned integers, signed bytes and fixed-point decimals are not supported.

JScript, by contrast, immediately converts bytes, shorts, singles, currencies and unsigned integers into either VT_I4 or VT_R8 (as appropriate), and then manipulates them as such.

In general, even if the script engines do not support operations on a particular type, you can still store the value and pass it around.  You can use script as "glue" to take, say, a VT_DECIMAL from one object and pass it to another. 

If your object model uses fixed-point decimals (VT_DECIMAL), it will be hard to use it from script.  Speaking of which, keep in mind that almost all floating point arithmetic accrues errors. If your object model depends on, say, using currencies (which are immune to floating point rounding issues at the cost of only supporting four decimal places) then JScript will likely defeat your purpose. Ideally, stick to doubles and longs. 

For more information see

http://blogs.msdn.com/ericlippert/archive/2003/09/15/53000.aspx

Dates

VBScript and JScript use completely different code behind the scenes to keep track of dates.  Both systems are fraught with "gotchas".  If your code manipulates dates using the standard VT_DATE used by COM, JScript will automatically translate them to its internal date format.  Generally speaking there are ways to make it all work, but the code can be slightly tricky.

For more information see

http://blogs.msdn.com/ericlippert/archive/2003/09/16/53013.aspx
http://blogs.msdn.com/ericlippert/archive/2003/10/06/53150.aspx

Object types

No objects which speak interfaces other than IDispatch can be produced or consumed in VBScript or JScript.  If you pass in a VT_UNKNOWN, we try to turn it into a VT_DISPATCH immediately.  Scriptable object models have to be fully dispatchable.

Non-default dispatch interfaces are badness.  JScript does not support non-default dispatches.  VBScript will use a non-default dispatch if given one, but exposes no way to obtain one from a default dispatch.  Don't write objects that support multiple disjoint dispatch interfaces.

For more information see

http://blogs.msdn.com/ericlippert/archive/2003/10/10/53188.aspx

Missing arguments, missing information

VBScript supports missing arguments.  You can call a method like this:

x = foo.bar(123, , 456)

and VBScript will pass a VT_ERROR for the parameter with the error field set to PARAMNOTFOUND.  The callee is then responsible for handling the problem, filling in a sensible default value, whatever.

JScript does not support missing arguments.  Object models where there are methods that take dozens of arguments where some of them are expected to be missing are hard to use from JScript.  (And they are probably badly designed methods just on first principles!) 

Neither JScript nor VBScript supports named arguments, so that's right out too.

A common alternative to passing a missing argument is to pass Nothing, Null, or Empty in VBScript, null or undefined in JScript.  Null and null pass VT_NULL, Empty and undefined pass VT_EMPTY, and Nothing passes a VT_DISPATCH with no value dispatch object pointer.  There's no easy way in JScript to pass a "null" object.  I consider this to be a design flaw in JScript, but we're stuck with it now.  Designers of good scriptable object models might consider treating Null and Nothing the same to make it easier to use from JScript.

Another related issue is the "empty/null string" problem.  In C, an empty string and a null string may be treated differently.  Not so in VB6, VBScript and JScript; they use the convention that a null string and empty string are equivalent.  But VB can call Win32 APIs which do not use this convention, so VB6 and VBScript allow you to force the runtime to pass an empty string or a null string.  JScript does not have this feature.

A dispatchable COM object model which makes a distinction between null and empty strings is almost certainly buggy.  Don't go there.

For more information, see:

http://blogs.msdn.com/ericlippert/archive/2003/09/12/52976.aspx
http://blogs.msdn.com/ericlippert/archive/2003/09/30/53120.aspx
http://blogs.msdn.com/ericlippert/archive/2003/10/01/53128.aspx

More On Parameter Passing

"Out" parameters are badness -- do not write methods that have out parameters in scriptable object models.  JScript does not support out parameters at all.  There are memory leaking scenarios for VBScript. 

"In-out" parameters are also badness.  JScript does not support in-out parameters at all.  VBScript always passes VT_VARIANT | VT_BYREF, and the default implementation of IDispatch::Invoke will give a type mismatch if the method is expecting a hard-typed byref parameter.  You can either write your own coercion logic, or make the argument a variant, but both are tricky. Best to avoid it entirely -- don't use out or in-out parameters.

"Out-retval" values are not really parameters -- they are return values -- so they are fine.

For more information see

http://blogs.msdn.com/ericlippert/archive/2003/09/15/52996.aspx
http://blogs.msdn.com/ericlippert/archive/2003/09/15/53005.aspx
http://blogs.msdn.com/ericlippert/archive/2003/09/15/53006.aspx
http://blogs.msdn.com/ericlippert/archive/2003/09/29/53117.aspx

Arrays

Arrays are badness.  JScript has very poor support for VB-style arrays.  Multi-dimensional vb-style arrays are particularly hard to deal with in JScript.  Try to not write object models that take arrays as parameters, or which can only return arrays.  (This is also a good idea because it may avoid the perf cost of copying around large arrays.)

VB6 can create arrays with arbitrary index ranges, such as foo(10 to 20, 4 to 6).  VBScript can consume such arrays but cannot produce them -- all VBScript-produced arrays have lower bounds of zero.

The script engines only support arrays of variants.  An array of bytes can be converted to a string by the underlying operating system, but that's pretty hacked up.

For more information see

http://blogs.msdn.com/ericlippert/archive/2003/09/22/53061.aspx
http://blogs.msdn.com/ericlippert/archive/2003/09/22/53069.aspx
http://blogs.msdn.com/ericlippert/archive/2004/05/25/141525.aspx

Collections

Both VBScript and JScript support iterating collections that implement IEnumVARIANTNo other enumerators are supported.

For more information see

http://blogs.msdn.com/ericlippert/archive/2003/09/22/53063.aspx

Method names

Use straight A-Z 0-9 characters in object model names.  Names with underbars are badness, just on general principles.  They're too easily confused with the line continuation character in VB, and just look ugly.  Avoid.

Names which collide with JScript or VBScript reserved words or built-in object model elements are also a bad idea.  Don't go calling your methods InStr or Math.cos, you'll just confuse people!

For more information see

http://weblogs.asp.net/ericlippert/archive/2004/06/10/152831.aspx

I'm sure there's stuff I'm forgetting.  I reserve the right to update this list in the future!

  • "There are memory leaking scenarios for VBScript."

    I've been experimenting with this lately (ten years too late, I guess).

    Is this not just a simple bug? Why not make sure value going into [out] parameters are properly released?

    Come to think of it, probably because you can't know it's [out], since you don't read the typelib - is that it?

    Thanks for any definitive answers.
  • Correct. The script engine has no idea whether it is talking to an out, in, or in-out parameter. The script engine sees

    foo.bar baz

    and thinks "clearly the developer wishes me to pass a reference to baz", so that's what it does.

    The callee on the other hand knows that the contract is "this thing is an out parameter, so I don't have to free the thing that comes in."

    The problem is as you state -- there's no way for the engine to know what the contract is before the call without checking the type info, and that is VERY expensive in some common scenarios.
  • > ... there's no way for the engine to know what the contract is before the call without checking the type info, and that is VERY expensive in some common scenarios.

    Dang. You stole my thunder. I was about to wax eloquent on the beauties of using tlbinf32.dll to assist scripting... :|
  • Eric, as always, interesting stuff. Dude, when you going to blog about VSTO 2? (assuming you're still working with that team, maybe not?)
  • Thanks dude!

    I am still working on VSTO2.

    Eric Carter, Peter Torr, Paul Stubbs and the VSTO User Education team are all blogging about it, and I reserve the right to do so as well, and I probably will at some point.

    However, one of the primary reasons I started this blog is because the COM script engines are still widely used and will be widely used for some time. I have a huge email archive containing trivia about the script engines that exists nowhere else on the web, and still lots of questions to answer in public, so I'm going to stick primarily to scripting for some time yet.
  • >However, one of the primary reasons I started this blog is because the COM script engines are still widely used and will be widely used for some time. I have a huge email archive containing trivia about the script engines that exists nowhere else on the web, and still lots of questions to answer in public, so I'm going to stick primarily to scripting for some time yet

    Keep going! Great stuff so far! Thanks Eric...
  • Version 1.4.1993 of the Shell Extensions for .NET Assemblies has been released.

  • Eric -- your blog rocks. I never thought I would return to COM automation and scripting but it is still the easiest way to reach the rest of the world that is not on the .Net bandwagon. Thanks for putting all the useful details up.

    I have one question that perhaps you could treat as a follow-on to this blog entry: discussion of [in, defaultvalue] vs. [in, optional] in IDL and the various scripting engines and .Net COM interop. I recall you having an entry on optional parameters but now I can't find it; in any case I don't remember it discussing the distinction between [optional] and [defaultvalue]. It is not super clear why both exist and what the differences are between them. For example, older COM books say you can specify [in, optional, defaultvalue] for parameters, but the present-day MIDL compiler does not let you do it. [optional] and [defaultvalue] are mutually-exclusive.

    Thanks again man, this stuff is great!

  • To be able to invoke functions on the instantiated COM function through JScript, we need to add the method to the ITest interface which is derived from IDispatch.

Page 1 of 1 (9 items)