DefType Statements in VBA: The Dark Side of Backward Compatibility
Just because you can do something, doesn't mean you should.
I believe deeply in the sanctity of backward compatibility. But it comes with a dark side. Sometimes the old ways of doing things fall out of favor. Their usage becomes so arcane that we have a tendency to forget they even exist.
So it goes with DefType statements.
What You Don't Know Can Hurt You
Several months ago, I wrote an article about Romke Soldaat's Registry Operations class module.
I published the changes I made to Romke's API declarations to make the code run under 64-bit VBA. Every API call was wrapped in #If VBA7
conditional compilation tags and updated with the PtrSafe
keyword.
There was only one problem.
I forgot to include a key change that I had made to one of the module-level declarations in Romke's code. Without this change, Romke's modified code would not compile under 64-bit VBA. The compile error occurred on the following line:
The error message was "ByRef argument type mismatch" and the highlighted variable was hCurKey
.
Here's the offending line of code from Romke's original class module:
Private hCurKey
To fix the compile error, the above line of code may be changed to this:
Private hCurKey As Variant
But wait, you say, aren't those two lines of code doing the same thing?!?! Everybody knows that if you don't declare a variable's type in VBA it is implicitly declared as a Variant. ... Or is it?
Explicit is Better Than Implicit
So what's really going on here?
The problem is that the first line of code above–Private hCurKey
–was defining the hCurKey variable as a Long
datatype.
How could this be?
It was because of this weird line at the top of Romke's class module:
DefLng H-I, L, N
What is that line doing? It's saying that every declared variable in the current module without an explicitly declared type whose variable name begins with H
, I
, L
, or N
, will be treated by the compiler as a Long
data type.
And so, the line Private hCurKey
did implicitly declare a type for the hCurKey variable, but the implicit declaration was as a Long data type instead of a Variant.
Why Does Variant Compile But Long Does Not?
As to why the code compiles when hCurKey
is a Variant but fails when it's a Long, that's a matter of the 32-bit to 64-bit conversion process.
To find the source of the problem, we need to examine the migrated code for the RegCreateKeyEx API declaration:
#If VBA7 Then
Private Declare PtrSafe Function RegCreateKeyEx _
Lib "advapi32.dll" Alias "RegCreateKeyExA" ( _
ByVal hKey As LongPtr, ByVal lpSubKey As String, _
ByVal Reserved As Long, ByVal lpClass As String, _
ByVal dwOptions As Long, ByVal samDesired As Long, _
lpSecurityAttributes As SECURITY_ATTRIBUTES, _
phkResult As LongPtr, lpdwDisposition As Long) As Long
#Else
Private Declare Function RegCreateKeyEx _
Lib "advapi32.dll" Alias "RegCreateKeyExA" ( _
ByVal hKey As Long, ByVal lpSubKey As String, _
ByVal Reserved As Long, ByVal lpClass As String, _
ByVal dwOptions As Long, ByVal samDesired As Long, _
lpSecurityAttributes As SECURITY_ATTRIBUTES, _
phkResult As Long, lpdwDisposition As Long) As Long
#End If
When we call RegCreateKeyEx
from the code, we are passing the hCurKey
variable as the second to last argument in the function. In other words, it's being passed as the phkResult
argument. Notice that in the pre-VBA7 version (Access 2007 and earlier), phkResult
is declared as a Long, but in the VBA7 version it is declared as a LongPtr
.
That's because the phkResult
receives a handle to the created or opened registry key. Whenever you see the word "handle" associated with an API call, you can safely translate that in your head to "memory address." That's why the argument is redefined as a LongPtr
in the VBA7 code: when executing in a 32-bit environment, a LongPtr
is treated as a 32-bit Long
integer, but in a 64-bit environment, a LongPtr
is treated as a 64-bit LongLong
integer.
Declaring hCurKey
as Variant is a bit of a shortcut. The following adaptation would also work (and perform faster, though the speed increase is likely to be imperceptible to the user unless it's called many times inside of a loop):
#If VBA7 Then
Private hCurKey As LongPtr
#Else
Private hCurKey As Long
#End If
As I said, the above approach is more explicit in conveying the developer's intent, performs better, and will raise more compile-time errors than the Private hCurKey As Variant
alternative.
But I'm known to be lazy, and Private hCurKey As Variant
is almost as good with a lot less typing.
Use Thy Knowledge For Good
Now, remember what I said at the beginning of this article?
Just because you can do something, doesn't mean you should.
I wrote this article for two reasons:
- To encourage you to explicitly declare Variant variables
As Variant
- To raise awareness about an arcane aspect of VBA that could trip you up if you are maintaining (or copy-pasting) someone else's code
I DID NOT write this article to inspire you to write DefType statements in your own code. DO NOT DO THAT!!! Remember, just because you can do something doesn't mean you should.