Dropping `#If VBA7 Then` From API Declares
It is time.
From this point forward, I no longer feel obligated to include the #If VBA7 Then
construct in my Windows API calls.
In other words, code on my site that used to look like this...
#If VBA7 Then
Private Declare PtrSafe Function apiGetParent _
Lib "user32" Alias "GetParent" (ByVal hWnd As LongPtr) As LongPtr
#Else
Private Declare Function apiGetParent _
Lib "user32" Alias "GetParent" (ByVal hWnd As Long) As Long
#End If
...will now look like this...
Private Declare PtrSafe Function apiGetParent _
Lib "user32" Alias "GetParent" (ByVal hWnd As LongPtr) As LongPtr
As I wrote in my 32-bit vs. 64-bit VBA Deep Dive article:
Now that we've had two perpetual VBA7-supported versions of Office reach end of life, I feel comfortable dropping support for Office 2007 and earlier in both my applications and my writing.
Here's Why I'm Doing This Now
- VBA7 has been available since Office 2010
- Office 2007–the last version without VBA7–has been out of support since October 10, 2017
- This makes it so I don't have to choose between readability and compile-time type checks in my calling code (e.g., I can simply use
As LongPtr
instead ofAs Variant
or#If VBA7 Then As LongPtr #Else As Long #End If
) - It's simple to adapt
PtrSafe
API declares to run in Office 2007 and earlier
That last point is the most important one for this blog.
I know all of my applications are running on Access 2010 or later. However, I am sure at least some of my readers have Access applications running on Access 2007 or earlier. If that's you and you need to support those older versions of Access, read the next section for step-by-step instructions to convert my PtrSafe
API declare lines to run in your version of Office.
Adapting API Declares to Run in Office 2007 and Earlier
As I lamented in my 32-bit vs. 64-bit VBA Deep Dive article, there is no simple algorithm to follow to convert 32-bit-only code to run under 64-bit VBA.
The main difficulty in moving from 32-bit-only API declares to 64-bit-compatible API declares was in identifying which Long
parameters needed to be switched to LongPtr
(i.e., which parameters represented memory pointers/object handles) and which should stay declared as Long
(i.e., the arguments represented actual 32-bit signed integers). Figuring that out required using references such as the Win32_API.txt file, reading documentation for the API functions, or–in some extreme cases–finding and understanding the underlying C++ header files.
However, moving in the other direction is quite simple.
What's more, if you follow the instructions below, your code will continue to run in 64-bit versions of VBA while also being backward-compatible with VBA6 versions of Office (2007 and earlier).
Step-by-Step Instructions
You may need to make changes in two different locations:
- The API
Declare
line in the code module's header - References to
LongPtr
in the calling code (e.g., theSub
,Function
, orProperty
)
API Declare in Module Header
Here are the steps to migrate a VBA 7-only API declare to run in both 64-bit environments and Office versions 2007 and earlier (i.e., pre-VBA 7):
- Add the following line of code before your existing API declare:
#If VBA7 Then
- Add the following line of code after your existing API declare:
#Else
- Copy and paste your existing API declare to the line following the
#Else
- Delete the keyword
PtrSafe
from the copied line - Replace any instances of
LongPtr
withLong
in the copied line - Add the following line of code after the copied line:
#End If
Here's an example from a recent article:
Before
The code below works in both 32-bit and 64-bit environments in Office 2010 and later:
Private Declare PtrSafe Function apiGetParent _
Lib "user32" Alias "GetParent" (ByVal hWnd As LongPtr) As LongPtr
After
The code below works in both 32-bit and 64-bit environments in Office 97 and later:
#If VBA7 Then
Private Declare PtrSafe Function apiGetParent _
Lib "user32" Alias "GetParent" (ByVal hWnd As LongPtr) As LongPtr
#Else
Private Declare Function apiGetParent _
Lib "user32" Alias "GetParent" (ByVal hWnd As Long) As Long
#End If
LongPtr References in the Calling Code
While you can take the same approach as above by adding #If VBA7 Then...#Else...#End If
lines to your calling code, a simpler alternative (but with less compile-time type-checking safety) is to replace references to LongPtr
with Variant
in your calling code:
- Replace variable declarations of
As LongPtr
withAs Variant
We'll continue using the above example from a previously published article:
Before
Function GetInnerAccessHwnd(Optional ByVal ChildHWnd As LongPtr = 0) As LongPtr
After
Function GetInnerAccessHwnd(Optional ByVal ChildHWnd As Variant = 0) As Variant
How Do You Know If You Need To Do This?
The PtrSafe
keyword was introduced in VBA 7, which debuted in Office 2010.
If you are running Office 2007 or earlier, this keyword does not exist yet and you will get the following error when trying to compile code that includes the keyword:
Compile error:
Expected: Sub or Function
Why Does Adding #If VBA7 Then
Fix My Code?
You may be wondering how adding the #If VBA7 Then
line could make the compile error go away in these earlier versions of VBA.
How can a feature introduced in VBA7 (i.e., the VBA7
compiler constant), be recognized by earlier versions of the language? After all, isn't the original problem the fact that the PtrSafe
keyword is not recognized by earlier versions?
The key difference is that PtrSafe
is a language keyword while VBA7
is a compiler constant.
When VBA encounters a language keyword that it does not recognize, that triggers a compile error (technically, it's a syntax error, since the keyword is not part of the language grammar).
However, when VBA encounters a compiler constant that it does not recognize, the compiler constant simply evaluates to False.
So, by introducing a compiler constant–VBA7
–that evaluates to True in VBA 7 (and later versions, on the unlikely chance that a VBA 8 ever gets released), Microsoft provides a way for developers to "hide" new language features from older versions of the language.
Cover image generated with DALL-E-3
UPDATE [2024-01-02]: Added section "LongPtr References in the Calling Code" based on feedback from Peter Cole in the comments below. Thanks, Peter!