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:

  1. To encourage you to explicitly declare Variant variables As Variant
  2. 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.


External references

Deftype statements (VBA)
Office VBA reference topic
Windows API declarations in VBA for 64-bit
How to convert your API Declarations to 64-bit. - Common myths debunked, key factors explained!

Referenced articles

Reverence for Backwards Compatibility
A feature that was heavily used by a very small percentage of power users has been maintained over the course of five subsequent upgrades (and counting). Now that is showing reverence to backwards compatibility.
RegOp Class for 64-bit VBA
Updating a classic VBA registry reading and writing class module for 64-bit compatibility.

Image by mm91 from Pixabay