Partager via


In, Out, In-Out, Make Up Your Mind Already

I was talking about reference types vs. by-reference variables a while back (here, here and here). Recall that both JScript and VBScript have reference types (ie, objects) but JScript does not have by-reference variables.

COM supports passing variable references around, but unfortunately the intersection of early-bound COM and late-bound IDispatch is a little bit goofy. There are a few problems that you need to be aware of when you're trying to get VBScript code to talk to COM objects that expect to be passed references to variables. (Obviously attempting to do so from JScript is just a non-starter, as JScript will not pass variable references at all.)

The most common problem happens when you are trying to call a method on a dispinterface where the vtable entry for the method looks something like this

HRESULT MyFunction([in, out] BSTR * pbstrBlah );

Calling this method via VBScript like this produces a type mismatch error:

Dim MyString
MyString = "foo"
MyObj.MyFunction MyString

And calling it like this does not produce an error but also does not actually pass the value by reference:

Dim MyString
MyString = "foo"
MyObj.MyFunction CStr(MyString)

The latter behaviour is completely expected -- what you're passing is the output of a function, not a reference to a variable. There is no reference available, so no value is filled in. But why does the former fail?

Well, VBScript does not know what the callee is expecting as far as types go. That's what "late bound" means -- the callee has to do the work of determining how to suck the relevant data out of the variants passed to Invoke, and the callee has to somehow call the underlying vtable with the correct types. So VBScript sees

MyObj.MyFunction MyString

and passes a reference to a variant. All variables are variants in VBScript.

So why does VBScript produce a type mismatch error here? VBScript doesn't! The object produces the type mismatch error, which VBScript dutifully reports. The object's implementation of Invoke calls the default implementation of Invoke provided for you by the type library implementation. That thing says "I've got a reference to a variant, and that variant is a string. I need a reference to a string. That's a type mismatch."

Now, if I were designing such a system, I'd put some additional smarts into it that would handle this case. Clearly from a reference to a variant that contains a string one can obtain a reference to a string -- just take the address of the string field of the variant! However, for reasons which are lost to the mists of time, the default implementation does not do that.

My advice to you would therefore be

1)      If you want to write an object that can be easily used from script, do not have any [in, out] parameters, because JScript can't pass references.
2)      If you must have in/out parameters, make them variants, because VBScript can't pass any other kind of reference.
3)      If you must have nonvariant in/out parameters, write some fixer-upper code for your IDispatch implementation which transforms byref variants pointing to strings into byref strings. (Or whatever byref type you require.) But if you do that, make sure you get it right. Do not attempt to write your own IDispatch implementation, as there are many pitfalls (which I may discuss at another time).

That's the most common problem I see. The other common problem involves out parameters which are not in/out or out/retval parameters. Just-plain-out parameters cause memory leaks. Consider our earlier example:

Dim MyString
MyString = "foo"
MyObj.MyFunction MyString

Suppose MyFunction takes an in/out variant, and fills in the byref variant with the string "bar". The implementor expects that something will come in and something will go out, and the rule is that the callee frees the coming-in memory before replacing it with the going-out memory. The caller is then responsible for freeing the going-out value.

But if MyFunction takes a just-plain-out variant then the callee does NOT free the incoming memory. It assumes that the incoming memory is garbage because it has specifically been told that nothing is coming in.

How does VBScript know whether the callee is in-out or out? VBScript doesn't! Knowing that requires either compile time knowledge or a very expensive run-time lookup (the results of which are difficult to cache for the same reasons that dispatch ids are difficult to cache.)

The practical result is that if you pass an object or string to a callee expecting an out variant, the string or object pointer will be overwritten without first being freed, and the memory will leak. You should ensure that you always pass empty variants if you must call an object method that takes an out parameter. Yes, this is a violation of the design principle that late-bound calls must have the same semantics as early bound calls, but unfortunately, that's just the way OLE Automation is and there's nothing we can do about it now.

Comments

  • Anonymous
    September 29, 2003
    This the best description of the interaction between script clients and COM objects I've ever seen.Would have been nice if this stuff was documented in MSDN 5 years ago :)

  • Anonymous
    September 29, 2003
    The comment has been removed

  • Anonymous
    March 15, 2004
    hey man, thanks for writing this article. i was stuck with this [in,out] bstr* problem for so long cos i simply can't understand why it works in VB but not VBScript. you're the man!!

  • Anonymous
    May 06, 2004
    It occurs to me that there may be some confusion about what exactly

  • Anonymous
    June 21, 2004
    This is awesome. It's an extremely useful article. It explains what happens and WHY!
    Thanks, dude!

  • Anonymous
    February 09, 2006
    By reading this article i think because of this only i am getting a Type mismatch error for Server.CreateObject method. as i am passing class name like : Cstr(strComponentName) & ".clsMain". But CreateObject is a method of Server object. is it the same case with this object also? Also it works for some time but some time it gives Type mismatch error only when there is less RAM available.

  • Anonymous
    March 27, 2007
    Pff... I DID write my own IDispatch and IDispatchEx implementation, so that we can use class constructors, default parameter values, case sensitive name resolution etc. It was fun ;)

  • Anonymous
    July 25, 2007
    Is there any way to open One Note 2007 application using VB script? I I tried this VB script    dim oneNote,pageStyle,importedPageID,unfiledPath,unfiledID        set oNote = CreateObject("OneNote.Application")        oNote.GetSpecialLocation 1,unfiledPath        oNote.OpenHierarchy unfiledPath, "", unfiledID, 0        oNote.CreateNewPage unfiledID,importedPageID, 0        Dim xml2importBase 'As String        xml2importBase = "<?xml version=""1.0""?><one:Section xmlns:one=""http://schemas.microsoft.com/office/onenote/2007/onenote"" ID="""        xml2importBase = xml2importBase + unfiledID + """>"        xml2importBase = xml2importBase + "<one:Page ID=""" + importedPageID + """>"        xml2importBase = xml2importBase + "<one:Title><one:OE><one:T><![CDATA[Title Comes here-2]]></one:T></one:OE></one:Title>"        Dim tailXml        tailXml = "</one:Page></one:Section>"        Dim xml2import        xml2import = xml2importBase + tailXml        oNote.UpdateHierarchy xml2import        oNote.NavigateTo importedPageID, "", False I know we cant send out parameter in the VB script. So IS there any way to overcome this problem and make this script run?