Scope
In programming Scope essentially means where a variable exists. In VBScript, there are essentially two scopes. Global and Local. This can be complicated through the use of classes, but I'm not going to talk about that here. Global scope means that a variable exists everywhere in the script. Local scope means that the variable exists only in the block of code where it was defined. In VBScript, only Subs and Functions can restrict scope. Again classes complicate this definition a bit, but that is for another post. Code is worth a thousand words, so here are some examples.
Option Explicit
Dim strTest
strTest = "FOO"
WScript.Echo strTest
OUTPUT:
FOO
This is pretty straightforward. We defined the variable outside of any restricting code block, so it has global scope. Since we tried to access the variable at the same level as it was defined, this was sort of trivial. Look at this instead:
Option Explicit
Dim strTest
strTest = "FOO"
RunTest
Sub RunTest()
WScript.Echo strTest
End Sub
OUTPUT:
FOO
This time we are trying to access the variable inside a Sub. Inside the Sub is considered a different level of scope, but since the variable has Global scope, there is no problem accessing it.
Now let's look at an example of scoping a variable locally:
Option Explicit
Dim strTest
strTest = "FOO"
RunTest
WScript.Echo strTest2
Sub RunTest()
Dim strTest2
strTest2 = "BAR"
WScript.Echo strTest
End Sub
OUTPUT:
FOO
Microsoft VBScript runtime error: Variable is undefined: 'strTest2'
Since strTest is still defined Globally, there is no problem accessing it inside or outside the Sub. Since we defined strTest2 inside the Sub though, it is scoped Locally. This means that it can only be accessed inside the Sub. So when we try to access it outside the Sub, we get an error. This scoping is true for Functions also.
A great many people avoid this issue by simply defining all their variables Globally. This does avoid any scoping issues, but personally I find it to be a short sighted solution. Defining variables Globally severely inhibits your ability to reuse your code. Let's say that you create the worlds best function ever. It solves every problem known to man. Since it is so useful, you obviously would want to use it over and over again. If you had created this function using Globally defined variables, that would mean that every time that you wanted to reuse the function, you would have to copy and paste it into your new script, then you would have to be sure to define every single one of the variables use in the function again Globally in the new script. You would have to go through this every time that you wanted to use your uber-spiffy function. Contrast that with properly defining the variables that the function uses Locally inside the function. If you did that, then every time that you wanted to reuse your function, you would copy and paste it into your new script then simply use it. That's all. Much simpler and much better in my opinion.
Data Trasnport - IN Using Local scope for Sub and Function variables does create one bit of extra effort on our part. When we use Global variables in a Sub/Function, we don't need to worry about getting data into the Sub/Function, since the variables we are using already have the data in them. When we use Local variables however, we need to pass all the data we need into the Sub/Function and for functions we need to pass data back out.
Let's talk about passing the data in first since that is the same for both the Sub and the Function. Let's take our Global variable example and quickly tweak it to be local:
Option Explicit
Dim strTest
strTest = "FOO"
RunTest strTest
Sub RunTest(strTest)
WScript.Echo strTest
End Sub
OUTPUT:
FOO
Now you might say, "But EB, you are still using the globally defined strTest". You would be right...sort of. Let's take a short deter here.
ByVal vs. ByRef There are actually two different ways to pass data into a Sub/Function. The default is ByRef. This stands for "By Reference" meaning that you are actually passing a reference pointer into the Sub/Function that points to the original variable outside the Sub/Function. When you do this, any changes that you make inside the Sub/Function will be seen outside the Sub/Function. Here is a demonstration:
Option Explicit
Dim strTest
strTest = "FOO"
RunTest strTest
WScript.Echo "After running the Sub but in Global scope: " & strTest
Sub RunTest(strTest)
WScript.Echo strTest
strTest = "BAR"
WScript.Echo "After change in SUB: " & strTest
End Sub
OUTPUT:
FOO
After change in SUB: BAR
After running the Sub but in Global scope: BAR
Notice that even outside the Sub the value of strTest has been changed. Let's do that again, but change the variable name inside the Sub, just to show that there isn't some weird Global scope hanky panky going on:
Option Explicit
Dim strTest
strTest = "FOO"
RunTest strTest
WScript.Echo "After running the Sub but in Global scope: " & strTest
Sub RunTest(strIn)
WScript.Echo strIn
strIn = "BAR"
WScript.Echo "After change in SUB: " & strIn
End Sub
OUTPUT:
Option Explicit
Dim strTest
strTest = "FOO"
RunTest strTest
WScript.Echo "After running the Sub but in Global scope: " & strTest
Sub RunTest(strIn)
WScript.Echo strIn
strIn = "BAR"
WScript.Echo "After change in SUB: " & strIn
End Sub
So as we can see, when you pass a variable into a Sub/Function, by default whatever you do to it will remain outside the Sub/Function. This is the "What happens in Vegas does
not stay in Vegas" scenario.
The other way that data can be passed into a Sub/Function is ByVal. Here is the same code that we just ran, but we will specify that the dat be passed ByVal ("By Value") instead of ByRef:
Option Explicit
Dim strTest
strTest = "FOO"
RunTest strTest
WScript.Echo "After running the Sub but in Global scope: " & strTest
Sub RunTest(ByVal strIn)
WScript.Echo strIn
strIn = "BAR"
WScript.Echo "After change in SUB: " & strIn
End Sub
OUTPUT:
FOO
After change in SUB: BAR
After running the Sub but in Global scope: FOO
As you can see, this time our changes inside the Sub had no effect on the global variable outside the Sub. I have to be honest with you that this is how I personally wish the default was set up instead of the default method being ByRef. The more loosely coupled your code is, the more easily it is reused.
More Data Transport - IN
Now that we understand the differences between ByVal and ByRef, let's talk about some things that you can pass into Subs/Functions. We have already seen that you can pass in simple strings. As a matter of fact, anything that you can assign to a variable you can pass into a Sub/Function.
Data Transport - Out
So we have seen that getting data into a Sub/Function is relatively easy. Just pass the data in when you call the Sub/Function. Getting data out of a function can be a little tougher. When you return data from a function, you are limited to returning one thing. This sounds like a tough limitation. Let's look at the simple case first. We really do want to return just one thing:
Option Explicit
WScript.Echo qq("Quote me baby!")
Function qq(strIn)
qq = Chr(34) & strIn & Chr(34)
End Function
OUTPUT:
"Quote me baby!"
Anyone that read the
Quote Like Pro tutorial will be familiar with that function. It does demonstrate though that the way you return data from a function is to set the function name equal to the data that you want to return. It can get a little tricky when the data that you are returning is an object instead of a simple data type. Let's say that you were a good coder and any time that you used the same code multiple places, you refactored the code into a function. And let's further say that for some odd reason you were writing a script that needed to instantiate several different dictionaries. You decided to make a function that would return an empty dictionary just to make your life easier:
Option Explicit
Dim dicOne
Dim dicTwo
Dim dicThree
Set dicOne = GetADictionary()
Set dicTwo = GetADictionary()
Set dicThree = GetADictionary()
Function GetADictionary()
Dim dicTemp
Set dicTemp = CreateObject("Scripting.Dictionary")
Set GetADictionary = dicTemp
End Function
This code doesn't actually output anything. There is one thing to remember when the item that you are returning is an object. It is the most common error that people commit when they start returning objects from functions.
Any time that you assign an object, you must use the Set keyword. Notice that inside the function, I use the Set keyword when I assign the temporary dictionary to the function name. Since the function is returning an object and whatever it returns is being assigned to a variable, I also must use the Set keyword when I call the function.
Let's get back to the limitation that a function can only return one thing. The last code sample shows us how we get around this limitation. We just make sure that the one thing we return is a container that holds all the things that we really wanted to return. Here are containers that I will frequently return from functions:
Arrays
ArrayLists
Dictionaries
Folder Collections
File Collections
ADO Recordsets
Basically you can return any data structure that is contained completely in a single object.
As always, let me know if I have missed something or further clarification is required.