Fabulous Adventures In Coding
Eric Lippert is a principal developer on the C# compiler team. Learn more about Eric.
Every now and then someone will ask me what the VBScript error message "Cannot use parentheses when calling a Sub" means. I always smile when I hear that question. I tell people that the error means that you CANNOT use PARENTHESES when CALLING a SUB -- which word didn't you understand?
Of course, there is a reason why people ask, even though the error message is perfectly straightforward. Usually what happens is someone writes code like this:
Result = MyFunc(MyArg) MySub(MyArg)
and it works just fine, so they then write MyOtherSub(MyArg1, MyArg2) only to get the above error.
Here's the deal: parentheses mean several different things in VB and hence in VBScript. They mean:
1) Evaluate a subexpression before the rest of the expression: Average = (First + Last) / 2 2) Dereference the index of an array: Item = MyArray(Index) 3) Call a function or subroutine: Limit = UBound(MyArray) 4) Pass an argument which would normally be byref as byval: Result = MyFunction(Arg1, (Arg2)) ' Arg1 is passed byref, arg2 is passed byval
That's confusing enough already. Unfortunately, VB and hence VBScript has some weird rules about when #3 applies. The rules are
3.1) An argument list for a function call with an assignment to the returned value must be surrounded by parens: Result = MyFunc(MyArg) 3.2) An argument list for a subroutine call (or a function call with no assignment) that uses the Call keyword must be surrounded by parens: Call MySub(MyArg) 3.3) If 3.1 and 3.2 do not apply then the list must NOT be surrounded by parens.
And finally there is the byref rule: arguments are passed byref when possible but if there are “extra” parens around a variable then the variable is passed byval, not byref.
Now it should be clear why the statement MySub(MyArg) is legal but MyOtherSub(MyArg1, MyArg2) is not. The first case appears to be a subroutine call with parens around the argument list, but that would violate rule 3.3. Then why is it legal? In fact it is a subroutine call with no parens around the arg list, but parens around the first argument! This passes the argument by value. The second case is a clear violation of rule 3.3, and there is no way to make it legal, so we give an error.
These rules are confusing and silly, as the designers of Visual Basic .NET realized. VB.NET does away with this rule, and insists that all function and subroutine calls be surrounded by parens. This means that in VB.NET, the statement MySub(MyArg) has different semantics than it does in VBScript and VB6 -- this will pass MyArg byref in VB.NET, byval in VBScript/VB6. This was one of those cases where strict backwards compatibility and usability were in conflict, and usability won.
Here's a handy reference guide to what's legal and what isn't in VBScript: Suppose x and y are vars, f is a one-arg procedure and g is a two-arg procedure.
to pass x byref, y byref: f x call f(x) z = f(x) g x, y call g(x, y) z = g(x, y)
to pass x byval, y byref: f(x) call f((x)) z = f((x)) g (x), y g ((x)), y call g((x), y) z = g((x), y)
The following are syntax errors: call f x z = f x g(x, y) call g x, y z = g x, y
Ah, VBScript. It just wouldn't be the same without these quirky gotchas.
This has been always confusing for me. Here, I find a perfect answer.
Thank you so much, Eric.
>how to write this?
>Set WshShell = WScript.CreateObject("WScript.Shell")
>WshShell.Run(iexplore -k http://support.microsoft.com/kb/154780, 1, true)
You should use quotation marks around the command, first off. That wasn't the cause of your problem but it would likely just cause another error after you fixed the parenthesis error.
There are two ways to correctly call the function, you can either preface the function with "Call" like this:
Call WshShell.Run("iexplore -k http://support.microsoft.com/kb/154780", 1, true)
Or you can simply remove the parenthises:
WshShell.Run "iexplore -k http://support.microsoft.com/kb/154780", 1, true
This is because, as Eric explained in his article, VBSCRIPT sees the parenthesis in function(myarg) as telling it to pass myarg by reference instead of by value. It does NOT see the parenthesis as describing where the list of arguments start and end. Since the comma tells VBSCRIPT where the next argument is, and it is expecting a parenthesis before the comma, it puts two and two together and tells you you can't use parenthesis (around the list of arguments) when calling a sub.
If it's still confusing to you, then just remember to allways use "Call" before calling a subroutine and it will behave the way you expect it to, because Call does require all arguments to be within a set of parenthesis. You can wrap individual arguments in the parenthesis to set them to by reference instead of by value.
6 1/2 years later and this is still the clearest explanation of the cause behind this on the Internet. Even with the many other language options out there, somehow VBScript still gets it's hooks into the developer's world. Known how to fix this for some time, but never bothered to learn why. Now I know. Thanks, Eric (if you're even still around!)
The article is great, but it primarily focuses on calls rather than declarations. Consider this gem...
Sub MySub(ByRef Parameter)
Parameter is passed by value.
Whitespace matters when there is only one parameter.
Sub MySub(Parameter) passes by value, but Sub MySub( Parameter ) and Sub MySub(Parameter, AnotherParameter) pass by value.
Oops... I meant...
Sub MySub(Parameter) passes by value, but Sub MySub( Parameter ) and Sub MySub(Parameter, AnotherParameter) pass by reference.
Well, my bad, apparently... It only seems like it was the declaration... Somewhere in chasing it down, the whole confusing thing got me: turns out the declaration of Sub MySub(Parameter) does pass by reference if the call is written well...
Still, the whole thing with Sub MySub(ByRef Parameter) passing by value when called MySub (Parameter) is just plain wrong. The presence of ByRef didn't kick an error, and was not treated as a parameter, so its as though the parser just disappeared it silently.
I'll slither back into my corner now.
This documentation is very explanatory.
interestingLY, I got this same error for not saving the return value:
Replace( name, "findStr", "replaceStr" ) ' error
name = Replace( name, "findStr", "replaceStr" ) ' ok
Change: MyOtherSub(MyArg1, MyArg2)
To: Call MyOtherSub(MyArg1, MyArg2)
Or: MyOtherSub MyArg1, MyArg2
Great explaination...it was confusing to me why passing 1 argument with parentheses works but 2 arguments failed...and you had the answer for it. Thx
thanks for this post.
simple answer is when using Sub call like:
MySub arg1, arg2, arg3