VBA PtrSafe Error After Windows Update — How to Fix It

Published November 6, 2025 · 7 min read · VBA

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.

What the PtrSafe Error Actually Means

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.

Why This Surfaces After a Windows Update

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.

Step 1: Find All Affected Declarations

Open the VBA editor (Alt+F11) and use Ctrl+F to search across your entire project. Search for these patterns:

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

Step 2: Add the PtrSafe Keyword

The 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.

Step 3: Update Long to LongPtr for Handles and Pointers

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.

Common Declarations: Before and After

Here are the Win32 API declarations that appear most often in Access and Excel VBA projects, with the correct 64-bit versions:

FindWindow

' 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.

SendMessage

' 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.

Sleep

' 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.

GetTickCount

' 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.

ShellExecute

' 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.

GetWindowText

' 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.

Supporting Both 32-bit and 64-bit Office

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.

Don't Forget the Variables

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.

When Manual Fixing Isn't Practical

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.

LegacyLift fixes PtrSafe errors automatically

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.

Quick Reference: Long vs. LongPtr Decision Table

Parameter/Return TypeUseReason
Window handle (hWnd)LongPtrPointer-sized handle
Device context (hDC)LongPtrPointer-sized handle
Instance handle (hInstance)LongPtrPointer-sized handle
Memory pointer (lpParam, lParam)LongPtrPointer to memory address
wParamLongPtrPointer-sized message parameter
Pixel counts, sizesLongNumeric value, not a pointer
Flags, constants, error codesLongNumeric value, not a pointer
Character counts, buffer sizesLongNumeric value, not a pointer
Boolean results (0/non-zero)LongNumeric value, not a pointer

Summary

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.