What is the difference between VBA.Str and VBA.Str$? - vba

What is the difference between these two code lines
Debug.Print VBA.Str(123)
and
Debug.Print VBA.Str$(123)

Str$ and Str are identical except for at least 2 important differences:
If the usage of Str$ returns anything other than a String, you'll get an error:
Debug.Print Str(Null) 'Prints Null
Debug.Print Str$(Null) 'Throws Runtime Error 94 - Invalid use of Null
Str$ returns a String, whereas Str returns a Variant. In general, you should always prefer the strongly typed Str$ over Str
There are many other functions that use $-suffixes, like Left$, Mid$ and Space$.
If we look at Left$ (which is really just an alias for _stdcall _B_str_Left) and Left (which is really just an alias for _stdcall _B_var_Left), in the Type Library MIDL, we see that the input types matter too.
[entry(616), helpcontext(0x000f6ea1)]
BSTR _stdcall _B_str_Left(
[in] BSTR String,
[in] long Length);
[entry(617), helpcontext(0x000f653e)]
VARIANT _stdcall _B_var_Left(
[in] VARIANT* String,
[in] long Length);
You can actually use the underlying functions in code:
Debug.Print VBA.Strings.[_B_var_Left]("abc",1) 'Prints a
Debug.Print VBA.Strings.[_B_str_Left]("abc",1) 'Prints a
And to make matters more confusing, a function like Join (that does return a String, but which doesn't have a corresponding Join$ function, can actually be used with a $ suffix:
Debug.Print Join$(Array(1, 2, 3), ".") 'Prints 1.2.3
For further discussion, see my answer at Exactly what are these _B_var_Xxxxx and _B_str_Xxxxx members in the VBA COM libraries?

Str$ is identical to Str except for the declared type of its return value. i.e. Str returns a Variant, the Str$ returns a String.
Refer the msdn link

Related

VBA: IsEmpty, vbEmpty, "Empty" and Empty

In VBA, if I understand correctly, emptiness means that a variant has not been initialized, i.e., it is the default value of a variant before an assignment.
There appear to be four ways to test if a variant is empty:
IsEmpty(var) = True
VarType(var) = vbEmpty
TypeName(var) = "Empty"
var = Empty
What I want to know is if those methods are completely equivalent, or if there are subtle (or stark) differences.
It seems that they ought to be equivalent, but I was surprised to find that Microsoft's documentation on IsEmpty, on vbEmpty (1, 2), and on TypeName make no reference to each other, which I thought they would if they are equivalent.
I found two references that seem to imply the first three are the same in VBscript (where everything is a variant): CodeWiki, Herong.
It seems that there are situations that are specific to Excel. It appears that emptiness in Excel also refers to a cell not containing anything, which I suppose is equivalent to the variable representing that cell not being initiated. But the website "Decision Models" says that emptiness also refers to whether a cell value is up to date ("a calculated parameter is Empty if it references uncalculated cells"). But that page says in one place to test for that using vbEmpty and in other places says to use IsEmpty.
I found two StackOverflow questions that discuss the relationship of IsEmpty and Empty (1, 2), but not on the other two methods.
It also seems that there might be subtle differences when applied to arrays.
I found the following code snippet on GitHub, which implies that if VarType(Obj) = vbEmpty, the value of IsEmpty(Obj) may still be either true or false:
Select Case VarType(Obj)
Case vbNull
json_toString = "null"
Case vbEmpty
'dkottow check if the cell is empty to evtl. convert to null
If IsEmpty(Obj) Then
json_toString = "null"
Else
json_toString = """"""
End If
So, pretty confusing.
To summarize, my question is, in VBA, are the following equivalent, or what are the differences in their meanings?
IsEmpty(var) = True
VarType(var) = vbEmpty
TypeName(var) = "Empty"
var = Empty
All of the below is applicable to VBA regardless of the host application (Excel, Word, AutoCAD etc.) as well as VB6 and prior VB versions. It just happens that Excel works well with Variants but the below hold true regardless.
Variant
Behind the scene a Variant is a structure (tagged union) and can be used to represent any other data type in VB and a couple of special values.
The layout is:
the first 2 bytes (Integer size) hold the VARTYPE
bytes 3 to 8 are reserved and mainly not used - Decimal uses them though
the following bytes can hold a value, a pointer or a flag and the number of bytes used also varies depending on application bitness (for example a pointer is 4 bytes on x32 and 8 bytes on x64)
When running VarType on a Variant the result is those 2 first bytes although they are returned as Long which is 4 bytes but since VBA's memory layout is little-endian then first 2 bytes in a Long perfectly overlap with the 2 bytes in an Integer.
We can use the CopyMemory API to demonstrate the above:
Option Explicit
#If Mac Then
#If VBA7 Then
Public Declare PtrSafe Function CopyMemory Lib "/usr/lib/libc.dylib" Alias "memmove" (Destination As Any, Source As Any, ByVal Length As LongPtr) As LongPtr
#Else
Public Declare Function CopyMemory Lib "/usr/lib/libc.dylib" Alias "memmove" (Destination As Any, Source As Any, ByVal Length As Long) As Long
#End If
#Else 'Windows
'https://msdn.microsoft.com/en-us/library/mt723419(v=vs.85).aspx
#If VBA7 Then
Public Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
#Else
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
#End If
#End If
Sub TestVariantVT()
Dim v As Variant
Dim vt As Integer
CopyMemory vt, v, 2
Debug.Assert vt = VarType(v) 'vbEmpty
v = CInt(2)
CopyMemory vt, v, 2
Debug.Assert vt = VarType(v) 'vbInteger
v = CLng(2)
CopyMemory vt, v, 2
Debug.Assert vt = VarType(v) 'vbLong
v = CDbl(2)
CopyMemory vt, v, 2
Debug.Assert vt = VarType(v) 'vbDouble
End Sub
The VARTYPE holds the data type but can also have the VT_BYREF flag set which means that the Variant is passed by reference (as an argument) to the current method which helps VB know what memory needs to be deallocated and which does not, when the method exits scope. VarType does not return the VT_BYREF flag but this is outside of the question scope. There is also a separate VT_ARRAY flag (as in vbArray) which can be used in combination with other flags to describe the contents of an array e.g. an array of integers will have the vbArray (VT_ARRAY) and the vbInteger (VT_I2) flags set (as in vbArray + vbInteger).
Unrelated to the question but related with the above, the VT_BYREF flag can be used to manipulate memory as seen in my VBA-MemoryTools repository.
IsEmpty
This is quite easy to understand once you've read the above. The IsEmpty function simply checks if the VARTYPE (first 2 bytes) of the Variant is vbEmpty (which is 0).
So yes, the 2 conditions VarType(var) = vbEmpty and IsEmpty(var) = True are always equivalent.
I need to draw attention that most people don't use the IsEmpty(var) = True syntax because IsEmpty already returns a boolean. I, at least will never write something like If IsEmpty(var) = True Then but instead will always write If IsEmpty(var) Then. The latter is cleaner.
VarType
A few notes. You may be wondering what happens when we pass a non-Variant to the VarType function. Well, the VarName argument is of type Variant and so if you pass a non-Variant it actually gets wrapped in a Variant. Inspecting the VBE7.dll reveals this: VbVarType _stdcall VarType([in] VARIANT* VarName);
Note the remark on the link above:
If an object is passed and has a default property, VarType(object) returns the type of the object's default property.
This means that to check for objects you need to use IsObject which checks if the VARTYPE bytes are set to vbObject. In this particular case (objects) the two VarType(var) = vbObject and IsObject(var) are not always equivalent.
However, the above remark does not influence the equivalence of VarType(var) = vbEmpty and IsEmpty(var) because the latter will also check an object's default member.
Empty
In VB*, Empty is just a keyword but is the equivalent of a Variant with the first 2 bytes set to vbEmpty. It's there for convenience in the same way Null is (Variant with first 2 bytes set to vbNull).
Hence, comparing a Variant with Empty is like comparing 2 Variants. When comparing 2 Variants, there are some special rules that apply. Stated here:
If expression1 and expression2 are both Variant expressions, their underlying type determines how they are compared. The following table shows how the expressions are compared or the result from the comparison, depending on the underlying type of the Variant.
If
Then
Both Variant expressions are numeric
Perform a numeric comparison.
Both Variant expressions are strings
Perform a string comparison.
One Variant expression is numeric and the other is a string
The numeric expression is less than the string expression.
One Variant expression is Empty and the other is numeric
Perform a numeric comparison, using 0 as the Empty expression.
One Variant expression is Empty and the other is a string
Perform a string comparison, using a zero-length string ("") as the Empty expression.
Both Variant expressions are Empty
The expressions are equal.
So, var = Empty is NOT the equivalent of VarType(var) = vbEmpty/IsEmpty(var). Quick example: if var is an empty string ("") or a null string (vbNullString) then var = Empty returns True while VarType(var) = vbEmpty and IsEmpty(var) both return False.
TypeName
TypeName is quite different as it returns a String.
It is quite useful when used with objects. For example if var is a Collection then VarType(var) returns vbObject while TypeName(var) returns Collection. So, TypeName offers some more information. Same with arrays: TypeName(Array()) returns Variant() but depending on the array type it can return Integer() or Double() and so on.
That's why you are seeing Range when your parameter is an Excel.Range wrapped in a Variant. The actual VARTYPE is vbObject but TypeName goes a step further and checks the type of the object.
I think in your Excel example you are actually interested in the Range.Value property. If var is a Range then TypeName(var.Value) = "Empty" is just by coincidence equivalent with IsEmpty(var.Value) but only because the .Value property never returns an object but if it did then they would not be equivalent anymore. However, TypeName(var) will never be equivalent with IsEmpty(var) if var is an object.
Note that TypeName does not look at the default member of an object.
Conclusion
VarType(var) = vbEmpty is always the equivalent of IsEmpty(var).
var = Empty follows the rules of comparing two variants and so is not equivalent with the 2 above.
TypeName(var) = "Empty" is only equivalent with VarType(var) = vbEmpty/IsEmpty(var) if var is NOT an object.
IsMissing
Just to clarify, because you've shown it in your own answer, if a variant has the vbError type (first 2 bytes VT_ERROR) and the SCODE member (bytes 9 to 12) set to DISP_E_PARAMNOTFOUND (0x80020004) then VB* sees it as the special Missing value.
The following code returns the special Missing value:
Public Function Missing() As Variant
Missing = &H80020004 'Sets bytes 9 to 12
CopyMemory Missing, vbError, 2 'Sets first 2 bytes
End Function
Okay, I've done some testing in Excel. I don't intend to accept this answer because I don't think it's a definitive answer to my question because:
It's specific to Excel, so I don't know how these results will carry over to Access and other Office programs.
It's just a test of a variety of cases. A definitive answer would be based on knowledge of the algorithms used to calculate IsEmpty(), VarType, and TypeName(), and to assign Empty.
With that disclaimer, here is the VBA function used for the test:
Function vTestEmptiness(sCellOrVar As String, sTest As String, Optional vCell As Variant) As Variant
Dim vVar As Variant
Select Case sCellOrVar
Case "Cell":
Select Case sTest
Case "IsEmpty": vTestEmptiness = IsEmpty(vCell)
Case "VarType": vTestEmptiness = Choose(VarType(vCell) + 1, _
"vbEmpty", "", "", "", "", "vbDouble", _
"", "", "vbString", "", "vbError")
Case "TypeName": vTestEmptiness = TypeName(vCell)
Case "Empty": vTestEmptiness = (vCell = Empty)
Case "IsNull": vTestEmptiness = IsNull(vCell)
Case "IsMissing": vTestEmptiness = IsMissing(vCell)
End Select
Case "Var":
Select Case sTest
Case "IsEmpty": vTestEmptiness = IsEmpty(vVar)
Case "VarType": vTestEmptiness = Choose(VarType(vVar) + 1, _
"vbEmpty", "", "", "", "", "vbDouble", _
"", "", "vbString", "", "vbError")
Case "TypeName": vTestEmptiness = TypeName(vVar)
Case "Empty": vTestEmptiness = (vVar = Empty)
Case "IsNull": vTestEmptiness = IsNull(vVar)
Case "IsMissing": vTestEmptiness = IsMissing(vVar)
End Select
End Select
End Function ' vTestEmptiness()
Here are the formulas that make up the test using that function:
And here are the results:
From those results, I conclude the following:
IsEmpty() and VarType() = vbEmpty appear to be equivalent in Excel in the sense that whenever IsEmpty() is true or false, VarType() is, respectively, vbEmpty or not vbEmpty.
IsEmpty() and TypeName() = "Empty" are definitely not completely equivalent in Excel because when IsEmpty() is true, TypeName() may or may not be "Empty".
IsEmpty() and Empty are definitely not completely equivalent in Excel because in the four cases tested that did not result in an error, IsEmpty() was true or false, but the tested variable was always equal to Empty.
And so, it seems that in Excel, one can use IsEmpty() and VarType() = vbEmpty interchangeably, but one has to be careful about differences between those and TypeName() = "Empty" and Empty.
From these results, I don't see how the code from GitHub cited in my question works. It seems that in that code, IsEmpty(Obj) would never be false.
I hope someone who knows what's going on under the hood in VBA will speak up about what's really going on here.

passing range on another sheet to vlookup

I can't reference a range with a sheet for my function like I can with =vlookup.
This works: =MVLOOKUP(a2,B:C,2,",",", ")
This isn't: =MVLOOKUP(a2,Sheet3!B:C,2,",",", ")
The code:
Public Function MVLookup(Lookup_Values, Table_Array As Range, Col_Index_Num As Long, Input_Separator As String, Output_Separator As String) As String
Dim in0, out0, i
in0 = Split(Lookup_Values, Input_Separator)
ReDim out0(UBound(in0, 1))
For i = LBound(in0, 1) To UBound(in0, 1)
out0(i) = Application.WorksheetFunction.VLookup(in0(i), Table_Array, Col_Index_Num, False)
Next i
MVLookup = Join(out0, Output_Separator)
End Function
I don't know basic and I'm not planning to learn it, I rarely even use excel, so sorry for the lame question. I guess basic is really "basic" it took me 30 minutes to get to this point from the reference(reading included), but other 60 minutes in frustration because the above problem.
Help me so I can go back to my vba free life!
EDIT: Although the code above worked after an excel restart, Jeeped gave me a safer solution and more universal functionality. Thanks for that.
I was not planning to use it on other than strings but thanks for the addition, I wrongly assumed there is a check for data type every time and type passed along in the background and vlookup acting accordingly. I have also learned how to set default values to function input variables.
See solution.
Thanks again, Jeeped!
You are confusing 1 with "1" and regardless of your personal distaste for VBA, I really don't know of any programming language that treats them as identical values (with the possible exception of a worksheet's COUNTIF function).
Public Function MVLookup(Lookup_Values, table_Array As Range, col_Index_Num As Long, _
Optional Input_Separator As String = ",", _
Optional output_Separator As String = ", ") As String
Dim in0 As Variant, out0 As Variant, i As Long
in0 = Split(Lookup_Values, Input_Separator)
ReDim out0(UBound(in0))
For i = LBound(in0) To UBound(in0)
If IsNumeric(in0(i)) Then
If Not IsError(Application.Match(Val(in0(i)), Application.Index(table_Array, 0, 1), 0)) Then _
out0(i) = Application.VLookup(Val(in0(i)), table_Array, col_Index_Num, False)
Else
If Not IsError(Application.Match(in0(i), Application.Index(table_Array, 0, 1), 0)) Then _
out0(i) = Application.VLookup(in0(i), table_Array, col_Index_Num, False)
End If
Next i
MVLookup = Join(out0, output_Separator)
End Function
When you Split a string into a variant array, you end up with an array of string elements. Granted, they look like numbers but they are not true numbers; merely textual representational facsimiles of true numbers. The VLOOKUP function does not treat them as numbers when the first column in your table_array parameter is filled with true numbers.
The IsNumeric function can reconize a string that looks like a number and then the Val function can convert that text-that-looks-like-a-number into a true number.
I've also added a quick check to ensure what you are looking for is actually there before you attempt to stuff the return value into an array.
Your split strings are one-dimensioned variant arrays. There is no need to supply the rank in the LBound / UBound functions.
                    Sample data on Sheet3                                  Results from MVLOOKUP
This is not a valid range reference ANYWHERE in Excel B:Sheet3!C.
Either use B:C or Sheet3!B:C
Edit. Corrected as per Jeeped's comment.

Passing variants/arrays as arguments for a function

Example code;
Sub functiontester()
Dim testdata As Variant
Dim answer As Long
Dim result1 As Long
testdata = Sheets("worksheet1").Range("E1:E2").Value
result1 = testfunction(testdata)
result2 = testfunction2(testdata(2, 1))
End Sub
Function testfunction(stuff As Variant) As Long
testfunction = stuff(2, 1)
End Function
Function testfunction2(num As Long) As Long
testfunction2 = num
End Function
So from my days in python I'd expect result1 & result2 to both run fine, however this is not the case in VBA and if you try to run this you get
"compile error: Byref argument type mismatch" from result2; which I assume has something to do with limits of calculating values inside arguments of functions
So my question is: is there an easy way to make result2 work so that the variant reference just resolves to the specified element?
testdata(2, 1) likely will be of type Double, not Long.
You can use
CLng(testdata(2, 1))
to cast it to a Long.
So:
result2 = testfunction2(CLng(testdata(2, 1)))
should be fine
"compile error: Byref argument type mismatch" Actually refers to the fact it won't implicitly convert the datatype for you, because ByRef arguments (the default) are expected to be writeable. If converted arguments are written to , it gets lost when you return from the function/subroutine because the converted values are only temporary, they're not in any variable outside of the called function.
You can get around this complaint by making the receiving parameter ByValwhich means that it shouldn't be writable anyway:
Function testfunction2(ByVal num As Long) As Long

Char type not defined

Dim myChar As Char
throws Compile Error:
"User-defined type not defined"
What reference should I include to use Char type?
Char type does not exist in VBA, you should use String instead.
Dim myChar As String
Note that VBA is not the same as VB.NET. In VB.NET, you can use Char.
EDIT: Following Michał Krzych's suggestion, I'd use fixed-length string for this specific purpose.
Dim myChar As String * 1
Here is an excerpt from "VBA Developer's Handbook" by Ken Getz and Mike Gilbert:
"Dynamic strings require a bit more processing effort from VBA and are, accordingly, a bit slower to use."
...
"When working with a single character at a time, it makes sense to use
a fixed-length string declared to contain a single character. Because
you know you'll always have only a single character in the string,
you'll never need to trim off excess space. You get the benefits of a
fixed-length string without the extra overhead."
One caveat to this is that fixed string notation is NOT allowed in functions and subroutines;
Sub foo (char as string *1) 'not allowed
...
End Sub
So you would either need to use;
Sub foo (char as string) 'this will allow any string to pass
or
Sub foo (char as byte) 'the string would need to be converted to work
One thing to be careful of when using bytes is that there is no standard
whether a byte is unsigned or not. VBA uses unsigned bytes, which
is convenient in this situation.
I think it depends how you intend to use it:
Sub Test()
Dim strA As String, strB As Byte, strC As Integer, strD As Long
strA = "A"
strB = 65
strC = 75
strD = 90
Debug.Print Asc(strA) '65
Debug.Print Chr(strB) 'A
Debug.Print Chr(strC) 'K
Debug.Print Chr(strD) 'Z
End Sub

mid()=foo in VBS?

In VBA and VB6 I can assign something to mid for Example mid(str,1,1)="A" in VBS this doesn't work.
I need this because String concatenation is freakin' slow
Here is the actual code i hacked together real quick
Function fastXMLencode(str)
Dim strlen
strlen = Len(str)
Dim buf
Dim varptr
Dim i
Dim j
Dim charlen
varptr = 1
buf = Space(strlen * 7)
Dim char
For i = 1 To strlen
char = CStr(Asc(Mid(str, i, 1)))
charlen = Len(char)
Mid(buf, varptr, 2) = "&#"
varptr = varptr + 2
Mid(buf, varptr, charlen) = char
varptr = varptr + charlen
Mid(buf, varptr, 1) = ";"
varptr = varptr + 1
Next
fastXMLencode = Trim(buf)
End Function
How can i get this to work in VBS?
Authoritative source explicitly stating it's not available in VBScript:
Visual Basic for Applications Features Not In VBScript:
Strings: Fixed-length strings LSet, RSet Mid Statement StrConv
VBA has both a Mid Statement and a Mid Function. VBScript only has the Mid Function.
The one other option you have if you are stuck doing this in VBScript is to make API calls. Since you are already comfortable working directly with the string buffer this might not be too big a jump for you. This page should get you started: String Functions
Sorry, it looks like API calls are out, too: Rube Goldberg Memorial Scripting Page: Direct API Calls Unless you want to write an ActiveX wrapper for your calls, but we're starting to get into an awful lot of work (and additional maintenance requirements) now.
You can't do that. You will have to rebuild the string from scratch.
This is not possible, mid(str,1,1) is a function which just returns a number (str is not passed 'by reference' and is not altered).
It's never too late to suggest an answer, even to a decade-old question...
One great thing about VBScript is that even though it's a subset of VB/VBA it still lets you declare (and use) classes. And having classes implies them having properties, with getters and/or setters... if you can guess where I'm going...
So if you wrap a class around a VBA-compatible implementation of Mid(), it's actually possible to emulate Mid() statements with a Let property. Consider the following code:
Class CVBACompat
' VB6/VBA-Like Mid() Statement
Public Property Let LetMid(ByRef Expression, ByVal StartPos, ByVal Length, ByRef NewValue) ' ( As String, As Long, As Long, As String)
Dim sInsert ' As String
sInsert = Mid(NewValue, 1, Length)
Expression = Mid(Expression, 1, StartPos - 1) & sInsert & Mid(Expression, StartPos + Len(sInsert))
End Property
' VB6/VBA-Like IIf() Function
Public Function IIf(Expression, TruePart, FalsePart)
If CBool(Expression) Then
IIf = TruePart
Else
IIf = FalsePart
End If
End Function
End Class: Public VBACompat: Set VBACompat = New CVBACompat: Public Function IIf(X, Y, Z): IIf = VBACompat.IIf(X, Y, Z): End Function
VBA's language specification says that when Mid( <string-expression>, <start> [, <length> ] ) is used as a statement, what's to be replaced in the source string <string-expression> is the lowest number of characters given the entire RHS expression or as limited by the <lenght> argument, when provided. Now, you can't have optional arguments in VBScript, so if you want to be able to also use statements of the sort Mid( <string-expression>, <start> ) = <expression> you'll have to have two separate implementations.
The implementation proposed above can be checked with the following code (not including checks for error-throwing conditions, which are the same as for the Mid() function anyway) :
Dim f ' As String
Dim g ' As String
f = "1234567890"
g = f
' VB6 / VBA: Mid(f, 5, 2) = "abc"
VBACompat.LetMid(f, 5, 2) = "abc"
' VB6 / VBA: Mid(g, 5, 4) = "abc"
VBACompat.LetMid(g, 5, 4) = "abc"
WScript.Echo "First call " & IIf(f = "1234ab7890", "passes", "fails")
WScript.Echo "Second call " & IIf(g = "1234abc890", "passes", "fails")
In any case, since the proposed solution is just an emulation of otherwise native code implementations (in VB6, VBA), and still implies string concatenation anyway with the overhead of class instantiation + VTable translations, the OP'd be better off creating and making available an ActiveX component (OCX) to do concatenations in series.