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.
32-bit vs. 64-bit VBA: Deep Dive
A deep dive into the past, present, and future of the transition from 32-bit VBA to 64-bit VBA.

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 of As 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., the Sub, Function, or Property)

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):

  1. Add the following line of code before your existing API declare: #If VBA7 Then
  2. Add the following line of code after your existing API declare: #Else
  3. Copy and paste your existing API declare to the line following the #Else
  4. Delete the keyword PtrSafe from the copied line
  5. Replace any instances of LongPtr with Long in the copied line
  6. 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:

  1. Replace variable declarations of As LongPtr with As 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!