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.