Declaring Windows API functions in Excel VBA allows you to access low-level operating system features, such as registry keys, hardware timers, and user-interface manipulation. In modern 64-bit versions of Office, Windows API declarations require two critical keywords: PtrSafe and LongPtr.
How Do PtrSafe and LongPtr Work in VBA Windows API Declarations?
PtrSafeinforms the VBA compiler that an API declaration is compatible with 64-bit Office.LongPtris a variable type that scales automatically: it occupies 8 bytes on 64-bit Office to store memory pointers and window handles safely, and 4 bytes on 32-bit Office. Values that represent pure numbers or flags must remain standard 32-bitLongvariables.
Quick Syntax Reference
Here is a side-by-side pattern for declaring a Windows API function (in this case, SendMessage):
#If VBA7 Then
' Compatible with Office 2010+ (Scales automatically in 32-bit and 64-bit)
Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" ( _
ByVal hWnd As LongPtr, _
ByVal wMsg As Long, _
ByVal wParam As LongPtr, _
ByVal lParam As LongPtr) As LongPtr
#Else
' Legacy 32-bit Office fallback (Office 2007 and earlier)
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _
ByVal hWnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
#End If
Detailed Mechanics of PtrSafe and LongPtr
The Purpose of PtrSafe
PtrSafe is a compiler assertion keyword. In 64-bit Office, omitting PtrSafe causes an immediate compilation failure: 32-bit vs. 64-bit: Fixing “The code in this project must be updated for use on 64-bit systems.”.
Adding PtrSafe tells the compiler: “I have verified the data types of the pointers and handles in this declaration.”
How LongPtr Solves the Pointer Width Problem
Before Office 2010, memory addresses and Windows handles were stored in standard 4-byte Long variables. When 64-bit Office was released, memory addresses expanded to 8 bytes.
Microsoft introduced LongPtr as an alias data type:
- In 32-bit Office,
LongPtrresolves internally to a standard 4-byteLong. - In 64-bit Office,
LongPtrresolves internally to an 8-byteLongLong.
Because LongPtr adapts based on the host environment, you can write a single declaration that runs in both 32-bit and 64-bit versions of modern Office without manual modifications.
Identifying Which Variables Need LongPtr
A frequent cause of hard Excel crashes after a 64-bit migration is converting the wrong data types.
What to Change to LongPtr:
- Handles: Any variable prefixed with
h(such ashWnd,hdc,hProcess,hIcon,hKey). - Pointers: Any parameter expecting a memory address or structure pointer (such as
lpString,lpBuffer,lpOverlapped). - Pointer-sized integers: Windows messaging parameters that pass addresses, such as
wParamandlParam. - Return Values: Any function that returns a pointer or handle (such as
FindWindow,GetWindowLongPtr,GetDC).
What Must Remain Long:
- Counts and Durations: Timeouts, millisecond intervals (
dwMilliseconds), loop counters. - Flags and Enumerations: Bitmask flags (
dwFlags,uType). - Dimensions and Coordinates: Screen positions (
x,y,cx,cy,nWidth,nHeight). - Return Codes: Success/failure booleans or error codes returning a 32-bit integer (
BOOL,HRESULT).
Common API Upgrade Examples
Example 1: GetTickCount vs. GetTickCount64
GetTickCount returns milliseconds elapsed as a 32-bit integer. It does not use pointers:
' Correct: No parameters are pointers, so only PtrSafe is added
Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
Example 2: Window Positioning with SetWindowPos
Notice how the handle is updated to LongPtr, while the coordinate and flag parameters remain standard Long:
Private Declare PtrSafe Function SetWindowPos Lib "user32" ( _
ByVal hWnd As LongPtr, _
ByVal hWndInsertAfter As LongPtr, _
ByVal X As Long, _
ByVal Y As Long, _
ByVal cx As Long, _
ByVal cy As Long, _
ByVal uFlags As Long) As Long
Example 3: User-Defined Types (UDTs) with Pointers
If an API function accepts a custom Type struct, update the pointer fields inside the struct definition as well:
Private Type POINTAPI
X As Long
Y As Long
End Type
Private Type MSG
hWnd As LongPtr ' Updated: Window handle
Message As Long ' Unchanged: Message ID
wParam As LongPtr ' Updated: Can hold a pointer
lParam As LongPtr ' Updated: Can hold a pointer
Time As Long ' Unchanged: Timestamp
pt As POINTAPI ' Unchanged: Coordinates
End Type
Troubleshooting Crashes After Conversion
If updating your API calls results in sudden Excel crashes or silent failures:
- Verify ByVal vs. ByRef: Windows APIs generally expect parameters passed
ByValunless explicitly documented to take a pointer to a pointer. OmittingByValdefaults toByRefin VBA, passing an address instead of the value and crashing Excel. - Handle Special Functions (
GetWindowLongPtr): On 64-bit Windows,GetWindowLongis redirected toGetWindowLongPtr. Calling the legacy name in 64-bit Office can corrupt the stack:#If VBA7 Then Private Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongPtrA" ( _ ByVal hWnd As LongPtr, _ ByVal nIndex As Long) As LongPtr #Else Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" ( _ ByVal hWnd As Long, _ ByVal nIndex As Long) As Long #End If - Audit Data Conversions: Ensure you are not forcing incompatible string or numeric data types into pointers. To troubleshoot type conflicts, see Runtime Error 13: Type Mismatch (Trying to perform math on a string variable)..
- Clean Error Trapping: Wrap test executions with explicit error monitoring rather than suppressing failures with
On Error Resume Next. Review Using On Error Resume Next vs. On Error GoTo 0 (The right way).. - System Compatibility Framework: If you are managing wide enterprise deployments with mixed versions, refer to the VBA Environment & System Guide: Fixing Security, Version, and Compatibility Errors.