What's difference between
Len(Me.txtStartDate.Value & vbNullString) = 0
and
Len(Me.txtStartDate.Value) = 0
vbNullString is essentially a null string pointer.
Debug.Print StrPtr(vbNullString) 'prints 0
It looks equivalent to a literal "" empty string, but it's not:
Debug.Print StrPtr("") 'prints an address; 6 bytes are allocated for it
There is practically no difference between your two examples... but only because Me.txtStartDate.Value is already a String.
If you were doing this:
Debug.Print Sheet1.Range("A1").Value & vbNullString
Then you would be doing an implicit type conversion between whatever type is returned by Sheet1.Range("A1").Value (say, a Date, or a Double) by means of string concatenation, because the & operator is only used for string concatenations, and the result of that expression will be a String.
In other words, it's a rather convoluted way to do this:
Debug.Print CStr(Sheet1.Range("A1").Value)
Or, as litelite mentioned, a rather convoluted way to do this:
Len(CStr(Me.txtStartDate.Value)) = 0
You typically use vbNullString in place of an empty string "" literal, to spare uselessly allocating 6 bytes (4 for the string pointer, 2 for the null character), and to make your code unambiguously clear about your intent (e.g. "I mean an empty string, this wasn't a typo"), similar to how you would use string.Empty in C#.
What is vbNullString? It is one strange bird, which exists solely for calling functions in a DLL that were written in C (or C++).
Take a look at http://vb.mvps.org/tips/varptr/. It describes a set of (now hidden) functions in VBA (and VB6) that exist solely to allow the passing of pointers to the contents of variables, again mostly to functions in a DLL that were written in C (or any other language that does pointers).
(Technically, VBA does not do pointers, so any pointer that may be returned by a function is - technically speaking - casted to a long integer. ByRef and With do involve pointers, of course, but in hidden-behind-the-scene ways.)
In particular, try out StrPtr(vbNullString); it will return 0. If you tried instead StrPtr(""), you would get some non-zero result. The result is a pointer to where the characters of a string really are in memory. However, a pointer of 0 means something special in C (and C++) - it is often called the NULL pointer (or simply NULL, or maybe null), and it is meant to signify that a pointer points to nowhere. In C programming, sometimes a coder would like to allow a pointer parameter being NULL to mean something special for a function.
If you tried StrPtr("") a few times in a row, you'll likely get a few different non-zero results. That's because VBA is creating a brand new empty string for each try, and as weird as it seems that any memory should be used for a string that is empty, remember that a string in VBA includes a long integer that indicates the number of characters in the string.
Related
When I run the following macro:
Sub try()
Dim num As Integer
num = 123
MsgBox Len(num)
MsgBox VBA.Len(num)
End Sub
The first Msgbox displays 2 and the second Msgbox displays 3.
If I remove the first line which says Dim num As Integer, both MsgBoxes display 3.
Can anyone explain why?
Len and LenB are not just ordinary functions, they are keywords, and as such are found on the list of VBA keywords (although LenB is only mentioned after you click through to Len).
Mid would be another example of such keyword disguised as function, whereas e.g. Left isn't on the list at all, because that is just an ordinary function.
It has to be a keyword because one of its jobs is to perform the compile-time task of determining the storage size of a variable. E.g. with private user-defined types, an ordinary function could not do that at all:
Private Type foo
a As Long
b As String
End Type
Sub TestLens()
Dim f As foo
MsgBox Len(f) ' OK
MsgBox VBA.Len(f) ' Compile time error
End Sub
The fact that the object browser brings you to VBA.Len when you press Shift+F2 on that Len(f) is both correct and misleading.
The Len(f) here does not actually call the VBA.Len function that determines the string size, it simply cannot do that because it would require coercing a private struct into a Variant. Instead it calculates the size of foo at compile time and substitutes the result as a literal constant into the executable.
In your example the Len(num) calculates the compile-time size of variable num (which is 2) and substitutes the constant 2 into the resulting object code, whereas VBA.Len(num) packs the value of num into a Variant and passes that variant to VBA.Len where it's further converted to string "123" and the length of that is returned.
It has to do with the way that VB stores integers and how the Len() function handles arguments that are not strings.
When a datatype that is not a string is passed to the Len() function, it returns the nominal number of bytes used to store the data (VB uses 2 bytes to store an integer). See the documentation for the Len function.
The Len() function will automatically cast the variant variable (which is created by assigning a value to a variable without declaring it first) as a string. The return isn't changing because the storage allocation changes (although it does; variants require 16 bytes of storage space, minimum). Since the implicitly declared variable is actually a variant type, VB will automatically change its type based on the situation. In this case, Len() expects a string so VB makes the variable a string.
You could use Msgbox Len(Cstr(num)) to cast the integer variable as a string before passing it to the Len function if your intent is to return the number of characters in your integer value.
This question already has answers here:
Ternary operator VB vs C#: why resolves Nothing to zero? [duplicate]
(7 answers)
Closed 3 years ago.
I was surprised when I tried to assign null value to a variable using ternary expression in vb.net. When I assign through ternary expression, it doesn't works as expected.
Dim i As Integer? = Nothing
Dim j As Integer? = Nothing
i = If(True, j, 1)
j = If(True, Nothing, 1)
After execution of this code: i is nothing but j becomes 0 (zero). Why?
What is the explanation?
Why I can"t assign directly Nothing (Null) value?
I think the important thing to understand here is Nothing in VB.Net is not the same as null in other languages, because you can still assign Nothing to value types. In many other languages, null is a reference-type construct only. If you're familiar with C#, Nothing is closer to default(T) than to null.
With that in mind, take a fresh look at this expression:
If(True, Nothing, 1)
The compiler evaluates the entire expression on it's own merits, knowing nothing about i or j, even though j is the target of the assignment. The expression has to be able to stand alone.
VB.Net must also determine a type to use for the expression, and it must do this at compile-time. It is not able to infer anything from the use of Nothing about needing an Integer? rather than a basic non-nullable Integer for this type, because VB.Net is perfectly happy to assign Nothing to value types. Therefore the type of the conditional expression can only be inferred from the 1 literal in the final argument, which is a plain Integer, and not Integer?.
Given that resulting type, we now must evaluate Nothing as an integer, where the result is the 0 you observed. In theory, this part is done at runtime rather than compile time, but in practice I suspect the compiler or jitter recognizes the chance to optimize things and rewrites it all down to just j = 0.
If you want to be able to assign an Integer? with a value of Nothing, do it as you did in the first example and keep a variable handy with the correct type you can use for the assignment.
Ok so I was in college and I was talking to my teacher and he said my code isn't good practice. I'm a bit confused as to why so here's the situation. We basically created a for loop however he declared his for loop counter outside of the loop because it's considered good practice (to him) even though we never used the variable later on in the code so to me it looks like a waste of memory. We did more to the code then just use a message box but the idea was to get each character from a string and do something with it. He also used the Mid() function to retrieve the character in the string while I called the variable with the index. Here's an example of how he would write his code:
Dim i As Integer = 0
Dim justastring As String = "test"
For i = 1 To justastring.Length Then
MsgBox( Mid( justastring, i, 1 ) )
End For
And here's an example of how I would write my code:
Dim justastring As String = "test"
For i = 0 To justastring.Length - 1 Then
MsgBox( justastring(i) )
End For
Would anyone be able to provide the advantages and disadvantages of each method and why and whether or not I should continue how I am?
Another approach would be, to just use a For Each on the string.
Like this no index variable is needed.
Dim justastring As String = "test"
For Each c As Char In justastring
MsgBox(c)
Next
I would suggest doing it your way, because you could have variables hanging around consuming(albeit a small amount) of memory, but more importantly, It is better practice to define objects with as little scope as possible. In your teacher's code, the variable i is still accessible when the loop is finished. There are occasions when this is desirable, but normally, if you're only using a variable in a limited amount of code, then you should only declare it within the smallest block that it is needed.
As for your question about the Mid function, individual characters as you know can be access simply by treating the string as an array of characters. After some basic benchmarking, using the Mid function takes a lot longer to process than just accessing the character by the index value. In relatively simple bits of code, this doesn't make much difference, but if you're doing it millions of times in a loop, it makes a huge difference.
There are other factors to consider. Such as code readability and modification of the code, but there are plenty of websites dealing with that sort of thing.
Finally I would suggest changing some compiler options in your visual studio
Option Strict to On
Option Infer to Off
Option Explicit to On
It means writing more code, but the code is safer and you'll make less mistakes. Have a look here for an explanation
In your code, it would mean that you have to write
Dim justastring As String = "test"
For i As Integer = 0 To justastring.Length - 1 Then
MsgBox( justastring(i) )
End For
This way, you know that i is definitely an integer. Consider the following ..
Dim i
Have you any idea what type it is? Me neither.
The compiler doesn't know what so it defines it as an object type which could hold anything. a string, an integer, a list..
Consider this code.
Dim i
Dim x
x = "ab"
For i = x To endcount - 1
t = Mid(s, 999)
Next
The compiler will compile it, but when it is executed you'll get an SystemArgumenException. In this case it's easy to see what is wrong, but often it isn't. And numbers in strings can be a whole new can of worms.
Hope this helps.
While looking for a way to test when a user cancels an InputBox, I stumbled across the StrPtr function. I believe it checks if a variable was ever assigned a value and returns zero if it was never assigned and some cryptic number if it was.
It seems like a useful function! I started with this code:
Dim myVar as string
myVar = InputBox("Enter something.")
MsgBox StrPtr(myVar)
The message box shows a zero if the user cancelled.
Fantastic! But then why do some insist that StrPtr never be used? I read it's unsupported. Why does that matter?
A good answer will explain benefits (beyond my example above) and risks of using the StrPtr function, possibly how you use (or don't use) it without giving an opinion as to whether everyone or no one should use it.
tldr; There's no real risk to using StrPtr like that, but there's not really a benefit either.
While it might look like you get a null pointer back from the InputBox call, you actually don't. Compare the result of StrPtr to VarPtr:
Sub Test()
Dim result As String
result = InputBox("Enter something.") 'Hit cancel
Debug.Print StrPtr(result) '0
Debug.Print VarPtr(result) 'Not 0.
End Sub
That's because InputBox is returning a Variant with a sub-type of VT_BSTR. This code demonstrates (note that I've declared result as a Variant so it doesn't get implicitly cast - more on this below):
Sub OtherTest()
Dim result As Variant
result = InputBox("Enter something.") 'Hit cancel
Debug.Print StrPtr(result) '0
Debug.Print VarPtr(result) 'Not 0.
Debug.Print VarType(result) '8 (VT_BSTR)
Debug.Print TypeName(result) 'String
End Sub
The reason why StrPtr returns 0 is because the return value of InputBox is actually malformed (I consider this a bug in the implementation). A BSTR is an automation type that prefixes the actual character array with the length of the string. This avoids one problem that a C-style null terminated string presents automation - you either have to pass the length of the string as a separate parameter or the caller won't know how large to size a buffer to receive it. The problem with the return value of InputBox is that the Variant that it's wrapped in contains a null pointer in the data area. Normally, this would contain the string pointer - the caller would dereference the pointer in the data area, get the size, create a buffer for it, and then read the N bytes following the length header. By passing a null pointer in the data area, InputBox relies on the calling code to check that the data type (VT_BSTR) actually matches what is in the data area (VT_EMPTY or VT_NULL).
Checking the result as a StrPtr is actually relying on that quirk of the function. When it's called on a Variant, it returns the pointer to the underlying string stored in the data area, and it offsets itself by the length prefix to make it compatible with library functions that require a C-string. That means the StrPtr has to perform a null pointer check on the data area, because it's not returning a pointer to the start of the actual data. Also, like any other VARTYPE that stores a pointer in the data area, it has to dereference twice. The reason VarPtr actually gives you a memory address is that it gives you the raw pointer to whatever variable you pass it (with the exception of arrays, but that's not really in scope here).
So... it's really no different than using Len. Len just returns the value in the header of the BSTR (no, it doesn't count characters at all), and it also needs a null test for the similar reason that StrPtr does. It makes the logical conclusion that a null pointer has zero length - this is because vbNullstring is a null pointer:
Debug.Print StrPtr(vbNullString) '<-- 0
That said, you're relying on buggy behavior in InputBox. If Microsoft were to fix the implementation (they won't), it would break your code (which is why they won't). But in general, it's a better idea to not rely on dodgy behavior like that. Unless you're looking to treat the user hitting "Cancel" differently than the user not typing anything and hitting "Enter", there really isn't much point in using StrPtr(result) = 0 in favor of the much clearer Len(result) = 0 or result = vbNullString. I'd assert that if you need to make that distinction, you should throw together your own UserForm and explicitly handle cancellation and data validation in your own dialog.
I find the accepted answer to be rather misleading, so I was compelled to post another one.
A good answer will explain benefits (beyond my example above) and risks of using the StrPtr function, possibly how you use (or don't use) it without giving an opinion as to whether everyone or no one should use it.
There are three "hidden" functions: VarPtr, StrPtr and ObjPtr.
VarPtr is used when you need to get the address of a variable (that is, the pointer to the variable).
StrPtr is used when you need to get the address of the text data of a string (that is, the BSTR, a pointer to the first Unicode character of the string).
ObjPtr is used when you need to get the address of an object (that is, the pointer to the object).
They are hidden because it may be unsafe to mess around with pointers.
But you cannot go completely without them.
So, when do you use them?
You use them when you need to do what they do.
You use VarPtr when your problem in hand is "I need to know the address of that variable" (e.g. because you want to pass that address to CopyMemory).
You use StrPtr when your problem in hand is "I need to know the address of the first character of my BSTR string" (e.g. because you want to pass it to an API function that accepts wide strings only, but if you simply declare the parameter As String, VB will convert the string into ANSI for you, so you have to pass StrPtr).
You use ObjPtrwhen your problem in hand is "I need to know the address of that object" (e.g. because you want to examine its vtable or manually check if the object address does or does not equal some value you knew previously).
These functions correctly do what they are supposed to do, and you should not be afraid to use them for their intended purpose.
If your task in hand is different, you probably should not be using them, but not out of fear that they will return a wrong value - they will not.
In a perfect world, you would stop at that conclusion. That is not always possible, unfortunately, and the InputBox situation you mention is one of the examples.
From what is outlined above, it would appear that you should not be using StrPtr to determine if Cancel was pressed in an InputBox. Realistically though, you don't have a choice.
VBA.InputBox returns a String. (This fact is incorrectly omitted from the current documentation making it look like it returns a Variant.) It is perfectly okay to pass a string to StrPtr.
However, it is not documented that InputBox returns a null pointer on a cancel. It is merely an observation. Even though realistically that behaviour will never change, theoretically it may in a future version of Office. But that observation is all you have; there is no documented return value for a cancel.
With this in mind, you make a decision on whether or not you are comfortable with using StrPtr on the InputBox result. If you are happy to take the very small risk of this behaviour changing in future and your app therefore breaking, you do use StrPtr, otherwise you switch to Application.InputBox that returns a Variant and is documented to return a False on a cancel.
But that decision will not be based on whether StrPtr is correct in what it tells you. It is. It is always safe to pass the String result of VBA.InputBox to it.
Fantastic! But then why do some insist that StrPtr never be used? I read it's unsupported. Why does that matter?
When someone insists that something should never be used, it's almost always wrong. Even GoTo has its correct uses.
I tired both using StrPtr and without using StrPtr. I tested my Sub with several examples. I got same results except in one occasion - When User inputs null value (nothing) and presses OK.
Precisely I tried these two:
Using StrPtr. "Invalid Number" was the result here
ElseIf StrPtr(Max_hours_string) = 0
MsgBox "Cancelled"
Else
MsgBox "Invalid Number"
Without Using StrPtr. "Cancelled" was the result here
ElseIf Max_hours_string = "" Then
MsgBox "Cancelled"
Else
MsgBox "Invalid Number"
This is my code.
Sub Input_Max_Hours_From_The_User()
'Two Common Error Cases are Covered:
'1. When using InputBox, you of course have no control over whether the user enters valid input.
' You should store user input into a string, and then make sure you have the right value.
'2. If the user clicks Cancel in the inputbox, the empty string is returned.
'Since the empty string can't be implicitly coerced to a double, an error is generated.
'It is the same thing that would happen if the user entered "Haughey" in the InputBox.
Dim Max_hours_string As String, Max_hours_double As Double
Max_hours_string = InputBox("Enter Maximum hours of any Shift")
If IsNumeric(Max_hours_string) Then
Max_hours_double = CDbl(Max_hours_string) 'CDbl converts an expression to double
Range("L6").Value = Max_hours_double
Range("L6").Interior.ColorIndex = 37
ElseIf StrPtr(Max_hours_string) = 0 Then 'ElseIf Max_hours_string = "" Then MsgBox "Cancelled" also works !
MsgBox "Cancelled"
Else
MsgBox "Invalid Number"
End If
End Sub
So I think it depends how important it is to handle the null value for you. All other test cases, including pressing Cancel, non-numerical inputs etc. give the same results. Hope this helps.
Read through this thread and ultimately ended up doing the following... which does exactly what I want.... If the user deletes the previous entry which is the default... and clicks ok.. it moves forward and deletes the back end data ( not shown ). If the user click's cancel, it exists the sub without doing anything. This is the ultimate objective and... this allows it to work as intended... Move forward unless cancel is clicked.
hth,
..bob
Dim str As String
If IsNull(Me.Note) = False Then
str = Me.Note
Else
str = "Enter Note Here"
End If
Dim cd As Integer
cd = Me.ContractDetailsID
str = InputBox("Please Enter Note", "NOTE", str)
If StrPtr(str) = 0 Then
Exit Sub 'user hit cancel
End If
In my opinion: Using StrPtr in order to identify if a value converts to 0 is extra code to write. if you use the following function like your example above
Sub woohoo()
Dim myVar As String
myVar = "hello"
myVar = InputBox("Enter something.")
'if Cancel is hit myVar will = "" instead of hello.
'MsgBox StrPtr(myVar) not needed
MsgBox myVar 'will show ""
End Sub
Now is this the only reason to not use StrPtr no not at all. The other issue you run into with using unsupported functions is that eventually they can break the application. Whether its a library issue or another programmer looking through your code and trying to find that function it just is not a good idea. This may not seem like a big deal if your script is only 100 lines long. But what about when it is thousands of lines long. If you have to look at this code 2 years down the road because something broke it would not be very fun to have to find this magical function that just does not work anymore and try to figure out what it did. Lastly especially in VBA you can get overflow errors. If StrPtr is used and it goes past the allocated space of your data type that you declared it's another unnecessary error.
Just my 2 cents but due to being able to use less code and the function being more stable without it I would not use it.
10+ years Excel Programmer.
When I run the following macro:
Sub try()
Dim num As Integer
num = 123
MsgBox Len(num)
MsgBox VBA.Len(num)
End Sub
The first Msgbox displays 2 and the second Msgbox displays 3.
If I remove the first line which says Dim num As Integer, both MsgBoxes display 3.
Can anyone explain why?
Len and LenB are not just ordinary functions, they are keywords, and as such are found on the list of VBA keywords (although LenB is only mentioned after you click through to Len).
Mid would be another example of such keyword disguised as function, whereas e.g. Left isn't on the list at all, because that is just an ordinary function.
It has to be a keyword because one of its jobs is to perform the compile-time task of determining the storage size of a variable. E.g. with private user-defined types, an ordinary function could not do that at all:
Private Type foo
a As Long
b As String
End Type
Sub TestLens()
Dim f As foo
MsgBox Len(f) ' OK
MsgBox VBA.Len(f) ' Compile time error
End Sub
The fact that the object browser brings you to VBA.Len when you press Shift+F2 on that Len(f) is both correct and misleading.
The Len(f) here does not actually call the VBA.Len function that determines the string size, it simply cannot do that because it would require coercing a private struct into a Variant. Instead it calculates the size of foo at compile time and substitutes the result as a literal constant into the executable.
In your example the Len(num) calculates the compile-time size of variable num (which is 2) and substitutes the constant 2 into the resulting object code, whereas VBA.Len(num) packs the value of num into a Variant and passes that variant to VBA.Len where it's further converted to string "123" and the length of that is returned.
It has to do with the way that VB stores integers and how the Len() function handles arguments that are not strings.
When a datatype that is not a string is passed to the Len() function, it returns the nominal number of bytes used to store the data (VB uses 2 bytes to store an integer). See the documentation for the Len function.
The Len() function will automatically cast the variant variable (which is created by assigning a value to a variable without declaring it first) as a string. The return isn't changing because the storage allocation changes (although it does; variants require 16 bytes of storage space, minimum). Since the implicitly declared variable is actually a variant type, VB will automatically change its type based on the situation. In this case, Len() expects a string so VB makes the variable a string.
You could use Msgbox Len(Cstr(num)) to cast the integer variable as a string before passing it to the Len function if your intent is to return the number of characters in your integer value.