You updated Windows 11 or upgraded to 64-bit Office, opened your Access database or Excel workbook, and hit a wall: "Compile error: 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."
This error stops your entire VBA project from compiling. Nothing runs until every Win32 API Declare statement is fixed. If you have a handful of declarations, the fix takes minutes. If you have hundreds spread across dozens of modules, it can take days. This guide walks you through exactly what broke, how to fix it, and when to automate the process.
When you call a Windows API function from VBA, you use a Declare statement to tell VBA the function's name, which DLL it lives in, and what parameters it takes. In 32-bit Office, these declarations worked without any special annotation.
64-bit Office changed the rules. On a 64-bit system, memory pointers and handles are 64 bits wide instead of 32. If VBA passed a 32-bit value where Windows expected a 64-bit pointer, your application would crash or corrupt memory. Microsoft's solution: require the PtrSafe keyword on every Declare statement. This keyword tells the VBA compiler, "I've reviewed this declaration and confirmed it's safe for 64-bit execution."
Without PtrSafe, 64-bit VBA refuses to compile the project at all. It does not matter whether the declaration actually uses pointers. Every single Declare Function and Declare Sub in your project must have the keyword, or the entire codebase is locked out.
This error typically appears in one of three scenarios:
Regardless of the trigger, the fix is the same: update every Declare statement in your VBA project.
Open the VBA editor (Alt+F11) and use Ctrl+F to search across your entire project. Search for these patterns:
Declare FunctionDeclare SubMake sure to select "Current Project" as the search scope, not just the current module. Write down every module and line number where you find a match. In large projects, you may find declarations in standard modules, class modules, form code-behind modules, and even worksheet code modules.
Pay special attention to:
modAPI, modWin32, or basWindows — these are common naming conventions for API declaration modules#If VBA7 or #If Win64) that may already have partial fixesThe PtrSafe keyword goes between Declare and Function (or Sub). Here is the pattern:
' Before (breaks on 64-bit Office):
Declare Function GetTickCount Lib "kernel32" () As Long
' After (works on 64-bit Office):
Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
That single keyword addition is enough to make the code compile. But for many declarations, you also need to fix the data types — otherwise the code will compile but produce wrong results or crash at runtime.
In 32-bit VBA, handles and pointers were declared as Long (32-bit integer). In 64-bit VBA, these must be LongPtr, which automatically sizes to 32 or 64 bits depending on the Office bitness.
The rule: any parameter or return value that represents a window handle (hWnd), memory pointer, instance handle (hInstance), or any value returned by a Windows API that is a handle must change from Long to LongPtr.
Values that are actual numeric quantities — counts, pixel sizes, flags, error codes — stay as Long.
Here are the Win32 API declarations that appear most often in Access and Excel VBA projects, with the correct 64-bit versions:
' Before:
Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
' After:
Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Return type changes to LongPtr because it returns a window handle.
' Before:
Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, lParam As Any) As Long
' After:
Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As LongPtr, ByVal wMsg As Long, _
ByVal wParam As LongPtr, lParam As Any) As LongPtr
hWnd, wParam, and the return value are all pointer-sized. wMsg stays Long because it is a message constant.
' Before:
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
' After:
Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Only needs PtrSafe. The parameter is a millisecond count, not a pointer, so it stays Long.
' Before:
Declare Function GetTickCount Lib "kernel32" () As Long
' After:
Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
Returns a tick count (a numeric value), not a handle. Stays Long.
' Before:
Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
(ByVal hWnd As Long, ByVal lpOperation As String, _
ByVal lpFile As String, ByVal lpParameters As String, _
ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
' After:
Declare PtrSafe Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
(ByVal hWnd As LongPtr, ByVal lpOperation As String, _
ByVal lpFile As String, ByVal lpParameters As String, _
ByVal lpDirectory As String, ByVal nShowCmd As Long) As LongPtr
hWnd is a handle. The return value is an instance handle. Both become LongPtr.
' Before:
Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, _
ByVal cch As Long) As Long
' After:
Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hWnd As LongPtr, ByVal lpString As String, _
ByVal cch As Long) As Long
hWnd becomes LongPtr. cch (character count) and the return value (number of characters copied) stay Long.
If your VBA project needs to run on both 32-bit and 64-bit Office installations, use conditional compilation:
#If VBA7 Then
Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
#Else
Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
#End If
VBA7 is true in Office 2010 and later (which support PtrSafe and LongPtr). For variables that hold handles, use the same conditional pattern or simply use LongPtr everywhere — it resolves to Long on 32-bit Office automatically when running under VBA7.
Fixing the Declare statements is only half the job. Every variable in your code that stores a value returned from a handle-returning API call must also change from Long to LongPtr:
' Before:
Dim hWnd As Long
hWnd = FindWindow("ThunderDFrame", vbNullString)
' After:
Dim hWnd As LongPtr
hWnd = FindWindow("ThunderDFrame", vbNullString)
If you fix the Declare but leave the variable as Long, VBA will silently truncate the 64-bit handle to 32 bits. Your code will compile and may even appear to work — until it doesn't, with unpredictable crashes.
If your VBA project has a handful of API declarations in one or two modules, the manual fix is straightforward. But many legacy Access databases and Excel workbooks have a different reality:
In these cases, manual find-and-replace is error-prone. Missing one handle variable means a potential runtime crash that may not surface for weeks. Getting the Long-vs-LongPtr distinction wrong on even one parameter means silent data corruption.
Upload your Access database or Excel file. LegacyLift scans every VBA module, finds all Declare statements missing PtrSafe, identifies which parameters need LongPtr conversion, and generates a corrected version of your file. It also flags handle-storing variables that need updating and provides a detailed report of every change made.
| Parameter/Return Type | Use | Reason |
|---|---|---|
| Window handle (hWnd) | LongPtr | Pointer-sized handle |
| Device context (hDC) | LongPtr | Pointer-sized handle |
| Instance handle (hInstance) | LongPtr | Pointer-sized handle |
| Memory pointer (lpParam, lParam) | LongPtr | Pointer to memory address |
| wParam | LongPtr | Pointer-sized message parameter |
| Pixel counts, sizes | Long | Numeric value, not a pointer |
| Flags, constants, error codes | Long | Numeric value, not a pointer |
| Character counts, buffer sizes | Long | Numeric value, not a pointer |
| Boolean results (0/non-zero) | Long | Numeric value, not a pointer |
The PtrSafe compile error is a hard blocker — nothing in your VBA project runs until every Declare statement is fixed. The fix has two parts: adding the PtrSafe keyword to every declaration, and changing Long to LongPtr for any parameter or return value that represents a handle or pointer. For small projects, this is a straightforward manual task. For large legacy codebases with hundreds of API calls across many modules, it's a minefield of subtle bugs waiting to happen.
If you're dealing with a complex VBA project and want it done right the first time, LegacyLift's self-service tool handles the scanning and fixing automatically. For databases with additional compatibility issues beyond PtrSafe, our done-for-you service includes a free consultation to assess the full scope of work.