VBA Option Explicit fails to detect undeclared variables in Function - vba

I have always used Option Explicit in each module. I never gave it much thought, until now.
Code:
Option Explicit
Function ParseJSON(ByVal strJSON As String) As String
strJSON = "New String"
'MsgBox (strJSON)
ParseJSON = strJSON
End Function
Sub Test()
Dim strJSON As String
strJSON = "Old String"
MsgBox (ParseJSON(strJSON))
MsgBox (strJSON)
End Sub
This piece of code is just a test and has nothing to do with JSON. When I run the code, I expected it to throw an error as strJSON is never declared in ParseJSON, and it should be a new variable as the original one is passed ByVal and thus cannot be changed, the last MsgBox() confirms this.
Is there anything that I didn't get? My hunch points to the ByVal part., or maybe Option Explicit only checks Sub?

the point of Option Explicit is to have you explicitly declare all variables you're using, and that's what actually happens in Function ParseJSON(ByVal strJSON As String) As String: that strJSON As String is declaring variable strJSON you're going to use inside the function (and it's also declaring it as of String type)
then, you're also giving it a value passed by the calling sub, and that ByVal simply means that whatever value the function strJSON variable is going to assume it won't affect the calling sub variable (if any, and that may be incidentally named after strJSON, but it's distinct from function strJSON) you passed the value of
that's why if you try
Function ParseJSON(ByVal strJSON As String) As String
strJSON = Range("A1:A3")
'MsgBox (strJSON)
ParseJSON = strJSON
End Function
you'll get a run time type mismatch error as soon as the strJSON = Range("A1:A3") line is processed
and that's why if you try
Sub Test()
Dim strJSON As String
strJSON = "Old String"
MsgBox (ParseJSON(Range("A1:A3")))
MsgBox (strJSON)
End Sub
you'll get the same run time type mismatch error as soon as the MsgBox (ParseJSON(Range("A1:A3"))) line is processed

You ARE declaring properly.
In the Test function you declare it as a string with the DIM statement.
And by making it an argument in the function you are also declaring it as a string, for use in the function.
Because the function uses it byVal, and changes you make IN the function, will not affect the value of the string.
It you want to change the value of it, it has to be passed byRef.
ByRef effectively passed the actual variable. - It could be known by a different name even, but any changes will also change the original variable.
byVal is effectively passing a copy of the variable, which you can play about with but not change the original.
Imagine as a piece of paper.
byRef you hand over the actual piece of paper to be marked up
byVal you hand over a photocopy, but keep the original to yourself.

Related

why is a string variable strVar not the same as cStr(strVar)?

Why does a string variable need to be enclosed in a cStr() conversion to be used as a key string for the CreateObject("WScript.Shell").SpecialFolders collection?
Demonstration of this absurdity(?):
Sub sdfgdfsg()
Const strCon_SpecialFolderName As String = "MyDocuments"
Dim strVar_SpecialFolderName As String
strVar_SpecialFolderName = "MyDocuments"
Debug.Print CreateObject("WScript.Shell").SpecialFolders(strCon_SpecialFolderName) ' CORRECT
Debug.Print CreateObject("WScript.Shell").SpecialFolders(strVar_SpecialFolderName) ' WRONG! (it returns the Desktop path instead)
Debug.Print CreateObject("WScript.Shell").SpecialFolders(CStr(strVar_SpecialFolderName)) ' CORRECT
Debug.Print CreateObject("WScript.Shell").SpecialFolders("MyDocuments") ' CORRECT
End Sub
I read the documentation about the index argument for a collection without finding an answer.
The WScript library was designed to be used from scripting languages such as VBScript which use Variants. The SpecialFolders.Item that you are calling expects a Variant containing a string, too.
The result you are seeing appears to come from the fact that the library is not able to read the Variant-wrapped string value VB passes, and does something wrong instead. You can achieve the same result with
Dim strVar_SpecialFolderName As String
strVar_SpecialFolderName = vbNullString
'Returns C:\Users\Public\Desktop
Debug.Print CreateObject("WScript.Shell").SpecialFolders(strVar_SpecialFolderName)
For reasons I don't fully understand, there sometimes is a problem with passing Variant-wrapped data from VBA to an external object. I speculate that it may have something to do with the presence or absence of the VT_BYREF flag in the Variant that VB(A) produces, and it produces it differently for constants, local variables and temporaries.
I believe this to be a problem on the receiving side, not in VBA.
Workarounds include:
Declaring the variable as Variant to begin with:
Dim strVar_SpecialFolderName As Variant
strVar_SpecialFolderName = "MyDocuments"
Debug.Print CreateObject("WScript.Shell").SpecialFolders(strVar_SpecialFolderName)
Forcing an evaluation which turns the local variable into a temporary (that is what your CStr does, too) which apparently changes how VB packs it into a Variant:
Dim strVar_SpecialFolderName As String
strVar_SpecialFolderName = "MyDocuments"
Debug.Print CreateObject("WScript.Shell").SpecialFolders((strVar_SpecialFolderName))

Calling a function of a classmodul

probably just a stupid syntax error but when I try to call a function i created in a class module I get the error message that my "objectvarable or withblock is not declarde".
Here the minimal code example from both modules:
'calling
Dim AllZyklen1 As New ArrayList
For Each Wartungsplan In ArrayWartungsplan
Set AllZyklen1 = Wartungsplan.GetAllZyklen 'added set
next Wartungsplan
'function itself
Public Function GetAllZyklen() As ArrayList
Dim AllZyklen2 As New ArrayList
'allZyklen2 gets calculated, no other functions are called just local varaibles of the class are used
If Not AllZyklen2.Contains(Zyklus) Then
AllZyklen2.Add Zyklus
end if
Set GetAllZyklen = AllZyklen2 'added set
End Function
(numbers are added to "allzyklen" just for easier reading, they are actually both called "allzyklen" without number)
Shouldnt that work? I just cant see the error.
EDIT: As for the Solution, what the answer states is absolutley correct and was necessary for my code to work. Unfortunatley I also had an spelling error for a attribute in the classmodule. In which case vba just highlights the call of this function, but no errors within the function... I ended up moving the function from the classmodule to the main module where the correct line with the spelling error got highlighted and the mistake was easier to spot.
You need to use Set for Objects (ArrayList is an object).
So it should be:
'calling
Dim AllZyklen1 As New ArrayList
For Each Wartungsplan In ArrayWartungsplan
Set AllZyklen1 = Wartungsplan.GetAllZyklen
Next Wartungsplan
and
'function itself
Public Function GetAllZyklen() As ArrayList
Dim AllZyklen2 As New ArrayList
'allZyklen2 gets calculated, no other unctions are called just local varaibles of the class are used
Set GetAllZyklen = AllZyklen2
End Function
Full example that works:
Class Module ClassWartungsplan:
Option Explicit
Public Function GetAllZyklen() As ArrayList
Dim AllZyklen2 As New ArrayList
'allZyklen2 gets calculated, no other unctions are called just local varaibles of the class are used
AllZyklen2.Add "abc"
Set GetAllZyklen = AllZyklen2
End Function
Standard Module:
Option Explicit
Sub Example()
Dim AllZyklen1 As New ArrayList
Dim Wartungsplan As New ClassWartungsplan
Set AllZyklen1 = Wartungsplan.GetAllZyklen
Debug.Print AllZyklen1(0) ' prints ABC in the immediate window
End Sub

How ByVal works for Objects in vb.net?

My question is regarding the concept for working of ByVal in vb.net.
Here is the code:
Private Sub ManipulateDetails()
Dim tObject1 as New customdatatype
tObject1.sName = "Stack"
tObject1.sLastName = "Over"
GetManipulateDetails(tObject1)
End Sub
Private Function GetManipulateDetails(ByVal tObject1 as customdatatype)
tObject1.sName = "Stack-Over-Flow"
tObject1.sLastName = "Flow-Over-Stack"
Return tObject1
End Function
In the above code snippet I am sending tObject1 as ByVal in the GetManipulateDetails function, when the values are changed in this subroutine the object which is returned back, manipulates the actual object which was passed.
i.e. if I quickwatch the object in the method ManipulateDetails I can see the manipulated details.
Also if I am returning the object in the subroutine function the value is getting reflected in the original object which was passed.
as the value is getting changed even without returning the object from the function GetManipulateDetails, I am confused whether this is happening because of ByRef?? or there is some other mechanism which is making this work.
It may be clearer if we use different names:
Private Sub ManipulateDetails()
Dim tObject1 as New customdatatype
tObject1.sName = "Stack"
tObject1.sLastName = "Over"
GetManipulateDetails(tObject1)
End Sub
Private Function GetManipulateDetails(ByVal tother as customdatatype) as customdatatype
tother.sName = "Stack-Over-Flow"
tother.sLastName = "Flow-Over-Stack"
Return tother
End Function
Before you call GetManipulateDetails, tObject1 is a reference to an object of type customdatatype. When you call GetManipulateDetails, tother gets a copy of tObject1. Importantly, what this means is that now, tObject1 and tother are both references to the same object. What was copied was the reference, not the object. Within GetManipulateDetails, it can use its copy of the reference to access the object and make changes to it.
ByVal parameters are always copied - but parameters are either value types or references. They're never reference types (aka objects) themselves.

Is this a thread safe code?

i have three functions in a module that several threads would be using. all of the functions access local variable except the main doWork sub.
Sub DoWork(byval i as integer)
synclock (ListTasks)
dim strItem as string =ListTasks(CInt(i)).ToString
end SyncLock
dim strHtml as string = GetHtml(strItem )
dim strParsed as string = ParseHtml(strHtml)
dim strResult as string = Report(strParsed )
End sub
Function GetHtml(byval url as string) as string
'code to get website
ens sub
Function ParseHtml(Byval html as string) as string
'code to parse HtmlString
end function
Function Report(Byval html as string) as string
'do the work
end function
Is this a thread safe code, so that no thread will overwrite data?
If each function are all using local variables (within the functions, each function is stateless and do not access any shared resources) and all the parameters are passed by value, so that its a separate copy in the stack ( not a reference to another object), it should be threadsafe and you really do not need any locking.
It looks like pretty much everything you're using is a local variable. Your also passing variables by value, not by reference, which helps for thread safety. So you appear to be pretty safe! Just make sure the one lock your using doesn't get you into a race condition.

What does the Call keyword do in VB6?

There's some code in our project that looks a bit like this:
Private Sub Method1()
Call InnerMethod
End Sub
Private Sub Method2()
InnerMethod
End Sub
Private Sub InnerMethod()
'' stuff
End Sub
What's the advantage of doing Method1 over Method2?
From the MSDN:
You are not required to use the Call
keyword when calling a procedure.
However, if you use the Call keyword
to call a procedure that requires
arguments, argumentlist must be
enclosed in parentheses. If you omit
the Call keyword, you also must omit
the parentheses around argumentlist.
If you use either Call syntax to call
any intrinsic or user-defined
function, the function's return value
is discarded.
For example:
Sub Proc1()
Debug.Print "Hello World"
End Sub
Sub Proc2(text As String)
Debug.Print "Hello " & text
End Sub
In the immediate window, if you enter
Proc1
then "Hello World" prints. If you enter
Call Proc1
then "Hello World" prints. If you enter
Proc2 "World"
then "Hello World" prints. If you enter
Call Proc2 "World"
you get a compile error. You would have to enter
Call Proc2("World")
Call does nothing special other than call the method. It is a hang over from the old days of Basic when all lines had to start with a keyword. "Let" is another of these keywords, which was always put before an assignment, but is no longer required.
Method1 and Method2 do the exact same thing.
I have found a major difference about 'call' keyword with functions that having, ByRef Arguments (I have found this in MS-Access VBA editor). If you are calling the function without 'Call' keyword, ByRef aruments will not set for the calle. For Ex:
Private Function Test(Optional ByRef refArg As String) As Boolean
refArg = "Test"
Test = True
End Function
If you call the function without the Call keyword like
Dim a As String
Test(a)
a will be an empty string, after the call returns
If you call the function with the Call keyword like
Dim a As String
Call Test(a)
a will contain the string Test
The detailed explanation provided in the following link:
Cannot use parentheses when calling a Sub
There's no difference.
Here's a post which describes when you need to use call vs not using it and when to parentheses around your parameters.
You can also read more about call from MSDN. Essentially the main difference is that when you use call to call a function you can't access the return value.