The Curious Case of the Return Type of the hWndAccessApp Method in 64-bit VBA
Is everything I thought I knew about 64-bit vs. 32-bit VBA wrong? Or is there a problem with the Access object model? Or is it something else?
This is not the article I planned to write today.
As I was writing my intended article, though, I took the time to update some of my existing code to be more reliable in both 32-bit and 64-bit VBA environments. This is especially important since Office installs in 64-bit mode by default since the release of Office 2019.
Additionally, I decided I would start removing the #If VBA7 Then
conditional compile statements from my code, as those only apply to Access versions 2007 and earlier, which have been out of support now for more than six years.
As part of doing that, I was updating my GetInnerAccessHwnd
function, which I've included as a supporting function in many articles.
That function begins by fetching the window handle to the main Access application, via the Application.hWndAccessApp method.
In the context of upper-case-W Windows, "handles" refer to memory addresses that hold information about Windows objects, including lower-case-w windows such as the Access application window. Since window handles represent memory addresses, they should be 32-bits long in a 32-bit environment and 64-bits long in a 64-bit environment.
Imagine my surprise, then, when I checked the TypeName of the hWndAccessApp in my 64-bit VBA environment and VBA returned Long
:
I would have expected it to return LongLong
.
NOTE: The keyword LongPtr
is an alias that the compiler translates to Long
in 32-bit environments and LongLong
in 64-bit environments. Because it's not a datatype per se, the TypeName() function will never return LongPtr
as a result.
What Say You, Documentation?
In the hopes of finding an explanation, I naïvely headed to the documentation for the hWndAccessApp method.
The "Return value" section was quite explicit but short on details:
Return value
Long
Undeterred, I continued reading to the "Remarks" section. I hoped Microsoft would have found this behavior literally remarkable, as I had already found it figuratively remarkable. Alas, no such luck:
Remarks
The hWndAccessApp method returns a Long Integer value set by Access and is read-only.
Use this method by using Visual Basic when making calls to Windows application programming interface (API) functions or other external procedures that require a window handle as an argument.
To get the handle to a window containing an Access object such as a Form or Report, use the hWnd property.
Et tu, Access MVPs and Team?
Finding nothing of note in the documentation, I turned to the current/former Access MVP mailing list.
I've gotten no immediate response (it's only been about an hour). The mailing list is quite active, which leads me to believe I'm at least not missing anything obvious. (That in itself is a bit of a relief.)
Possible Explanation(s)
The first (and, so far, only) explanation that has come to my mind is that the Access developers could not update the Access object model to change the return type of the hWndAccessApp method because it would have broken binary backward compatibility.
This is the same consideration that forces us to refer to an Image control's ControlSource property via the generic .Properties
collection rather than a dedicated .ControlSource
property:
Sub TestImageControlSource()
Dim ImgCtl As Image
Set ImgCtl = Forms!Form1.Image0
'The obvious approach raises a compile error:
'Debug.Print ImgCtl.ControlSource
'We're forced to resort to "stringly-typed" programming:
Debug.Print ImgCtl.Properties("ControlSource")
End Sub
Wisdom of the Crowd
So here I am, empathizing deeply with Keith Richards and Mick Jagger, wondering whether a satisfactory answer exists at all.
Which brings me to you, dear reader. Do you know why the Access.Application.hWndAccessApp
method returns a Long
and not a LongLong
in 64-bit VBA environments?
I welcome theories, relevant forum posts or blog articles, and (especially) official documentation in the comments below.
UPDATE [2023-12-12]: Shortly after publishing this article, I did receive a response from Ben Clothier on the Access MVP mailing list. The gist of his response was twofold:
- Window handles are technically pointers, but for backward-compatibility reasons Windows guarantees that they will fit in a 32-bit integer even when running in 64-bit mode
- Changing
hWndAccessApp
would indeed break the Access library's interface and therefore was not something the Access team could do
A member of the Access engineering team confirmed the accuracy of Ben's response. He mentioned that the Access team could have added a new method to the API (e.g., hWndAccessApp64
) but there was no need because of the backward-compatibility guaranteed at the Windows level. He also posted a link to the relevant documentation: Interprocess Communication Between 32-bit and 64-bit Applications.
In the comments below, Ben also included a relevant link to a StackOverflow answer that addresses this issue in general: What is the range of a Windows HANDLE on a 64 bits application? Notably, the top-rated answer to that question points to the same "Interprocess Communication" documentation the Access engineering team pointed me to.
Here's the key text from the linked documentation:
64-bit versions of Windows use 32-bit handles for interoperability. When sharing a handle between 32-bit and 64-bit applications, only the lower 32 bits are significant, so it is safe to truncate the handle (when passing it from 64-bit to 32-bit) or sign-extend the handle (when passing it from 32-bit to 64-bit). Handles that can be shared include handles to user objects such as windows (HWND), handles to GDI objects such as pens and brushes (HBRUSH and HPEN), and handles to named objects such as mutexes, semaphores, and file handles.
Special thanks to Ben and the Access engineering team for solving the mystery.
I guess you could say I got what I needed.
UPDATE [2023-12-15]: Access MVP Tom van Stiphout chimed in on the MVP mailing list with an additional important observation:
None of the above applies to Declare statements for the Windows API or other libraries. Those declarations still must use LongPtr for window handles to be compatible with both 32-bit and 64-bit versions of those libraries.
Thanks for the insight and added context, Tom!
Image generated by DALL-E-3