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

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 If

Notice 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 kindExamples64-bit type
HandleshWnd, hDC, hInstance, hKey, hFile, hMenuLongPtr
Pointers and addresseslpBuffer, lParam, pointers to structuresLongPtr
wParam / lParamMessage parameters in SendMessageLongPtr
Counts, flags, sizesnCmdShow, dwFlags, nSize (usually)Long
Booleans and error codesReturn values of many API callsLong
Structure membersHandles 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 approachBuilt-in replacement
GetUserName (advapi32)Environ("USERNAME") or CreateObject("WScript.Network").UserName
GetComputerNameEnviron("COMPUTERNAME")
GetOpenFileName / GetSaveFileNameApplication.FileDialog(msoFileDialogFilePicker)
ShellExecute to open a file or URLApplication.FollowHyperlink
SHBrowseForFolderApplication.FileDialog(msoFileDialogFolderPicker)
SleepKeep, 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

Your VBA contains Declare statements for Windows API functions written for 32-bit Office. In 64-bit Office, every Declare must include the PtrSafe keyword, and parameters that hold pointers or handles must use LongPtr instead of Long.

No. PtrSafe removes the compile error, but if handle and pointer parameters stay as Long, the code can crash Access or behave unpredictably on 64-bit. You must also change those parameters and related variables to LongPtr, and any structures (Types) that contain pointers.

Yes, if API declarations use conditional compilation with #If VBA7 and LongPtr, which works in 32-bit and 64-bit Office 2010 and later. An .accde or .mde compiled in one bitness only runs in that bitness, so distribute compiled front ends that match each user's Office.

In Access, go to File → Account → About Access; the dialog states 32-bit or 64-bit. In VBA, #If Win64 is True when running in 64-bit Office.

Many: Environ("USERNAME") instead of GetUserName, Application.FileDialog instead of GetOpenFileName, Application.FollowHyperlink instead of ShellExecute for opening files and URLs, and a DoEvents-based wait or Sleep declared PtrSafe for pauses. Fewer API calls means less to maintain.