Expert guide
Convert Access VBA to 64-bit: PtrSafe, LongPtr and #If VBA7
Fix the compile error properly, so the code is stable on 32-bit and 64-bit Office
Call us at +1 (385) 386-3860
Short answer: in 64-bit Office every Windows API Declare must include PtrSafe, and every parameter, return value or Type member that holds a handle or pointer (hWnd, hDC, hInstance, lpBuffer and so on) must be LongPtr. Wrap declarations in #If VBA7 so they also compile on older versions, replace API calls with built-in VBA where possible, recompile, and rebuild .accde files for each bitness. For a full code review, see Access VBA development.
Microsoft has installed 64-bit Office by default for years, so databases written for 32-bit Office keep meeting new laptops. The symptom is a compile error the moment a module loads: "The code in this project must be updated for use on 64-bit systems. Please review and update Declare statements and then mark them with the PtrSafe attribute." The tempting fix, adding PtrSafe everywhere, makes the error go away and can leave crashes behind.
Why 64-bit Breaks API Calls
Windows API functions pass handles (window, file, device) and memory pointers. In 32-bit Office those are 32 bits, the same size as a VBA Long. In 64-bit Office they are 64 bits. A declaration that passes a handle as Long truncates it, so the API may receive a wrong value: sometimes it works, sometimes Access crashes. LongPtr is a type that is 32 bits in 32-bit Office and 64 bits in 64-bit Office, which is exactly what handles and pointers need.
The Correct Pattern
' BEFORE (32-bit only)
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function ShowWindow Lib "user32" _
(ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
' AFTER (works on 32-bit and 64-bit Office 2010+)
#If VBA7 Then
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Private Declare PtrSafe Function ShowWindow Lib "user32" _
(ByVal hwnd As LongPtr, ByVal nCmdShow As Long) As Long
#Else
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function ShowWindow Lib "user32" _
(ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
#End IfNotice that nCmdShow stays Long: it is a number, not a handle. Changing everything to LongPtr is as wrong as changing nothing. FindWindow returns a window handle, so its return type becomes LongPtr, and any variable that stores that result must be declared LongPtr too.
If every user is on Office 2010 or later, which is effectively everyone today, you can drop the #Else branch and keep only the PtrSafe declarations.
What Becomes LongPtr, and What Stays Long
| Parameter kind | Examples | 64-bit type |
|---|---|---|
| Handles | hWnd, hDC, hInstance, hKey, hFile, hMenu | LongPtr |
| Pointers and addresses | lpBuffer, lParam, pointers to structures | LongPtr |
| wParam / lParam | Message parameters in SendMessage | LongPtr |
| Counts, flags, sizes | nCmdShow, dwFlags, nSize (usually) | Long |
| Booleans and error codes | Return values of many API calls | Long |
| Structure members | Handles inside a Type (e.g. OPENFILENAME.hwndOwner) | LongPtr |
When unsure, check the function's documentation: Windows types such as HWND, HANDLE, LPVOID, LPARAM, WPARAM, ULONG_PTR and SIZE_T map to LongPtr; DWORD, UINT, INT and BOOL map to Long.
Replace API Calls With Built-In VBA Where You Can
| Old API approach | Built-in replacement |
|---|---|
| GetUserName (advapi32) | Environ("USERNAME") or CreateObject("WScript.Network").UserName |
| GetComputerName | Environ("COMPUTERNAME") |
| GetOpenFileName / GetSaveFileName | Application.FileDialog(msoFileDialogFilePicker) |
| ShellExecute to open a file or URL | Application.FollowHyperlink |
| SHBrowseForFolder | Application.FileDialog(msoFileDialogFolderPicker) |
| Sleep | Keep, but declare PtrSafe; or a Timer/DoEvents loop |
Every API call you remove is one fewer declaration to get right and one fewer thing that can break in a future Office update.
Step-by-Step Conversion
Work on a copy. Decompile if the project behaves strangely (open with the /decompile switch), then compact.
Search all modules, including form and report modules, for 'Declare'.
For each declaration: add PtrSafe, change handle and pointer parameters and returns to LongPtr, update any Types.
Search for variables that receive handles from those functions and change them to LongPtr.
Replace API calls that have built-in equivalents.
Debug → Compile until it compiles cleanly, then test every feature that used an API call.
Create separate .accde files from 32-bit and 64-bit Office if you distribute compiled front ends.
ActiveX Controls and References
Older ActiveX controls (some calendar, grid, and third-party controls) may have no 64-bit version; those controls will not load. Replace them with native Access controls or newer components.
Check Tools → References for any reference marked MISSING on the 64-bit machine.
Third-party DLLs called with Declare must have 64-bit versions; a 32-bit DLL cannot be loaded into 64-bit Office.
Should You Standardise on One Bitness?
Code written with #If VBA7 and LongPtr runs on both, but compiled .accde files and some components do not. Many organisations standardise on 64-bit Office for new machines and update their databases once. If you are reviewing an older database anyway, combine the 64-bit work with a broader check, as described in documenting a legacy Access database. AI assistants can help find and draft conversions, following the review approach in using AI for Access VBA, but every declaration should be checked against the API documentation. For upgrades beyond VBA, see migrate and upgrade Access.
FAQ
Access 64-bit VBA FAQ
PtrSafe, LongPtr and compatibility