Search code examples
excelvbawinapims-wordpowerpoint

What is the correct VBA declaration for the WinAPI MonitorFromPoint?


This Microsoft documentation would lead you to think it's this:

Declare PtrSafe Function MonitorFromPoint Lib "user32.dll" ( _
                                               POINT As POINTAPI, _
                                               ByVal dwFlags As LongPtr) As Long

Along with a UDT:

Type POINTAPI
    X As Long
    Y As Long
End Type

But calling this function always returns 0 on 64-bit Office and error 49 "Bad DLL calling convention" on 32-bit Office. FWIW, I left out the conditional compiler VBA7 directive for simplicity.

This appears to work on both 32 and 64 bit Office:

Declare PtrSafe Function MonitorFromPoint Lib "user32.dll" ( _
                                               ByVal X As Long, _
                                               ByVal Y As Long, _
                                               ByVal dwFlags As LongPtr) As Long

But given the numerous references online (and the Microsoft documentation) using POINTAPI, I don't understand why. Even ChatGPT prefers X & Y but who cares right? ;-)

I also don't understand why the declaration doesn't appear in this Microsoft documentation "Win32API_PtrSafe.TXT"


Solution

  • This Microsoft documentation would lead you to think

    Not really, it wouldn't.

    DWORD is always Long, HMONITOR is always LongPtr, and the POINT structure is accepted by value (POINT pt) rather than by reference (POINT* pt \ LPPOINT pt). VBA's POINT As POINTAPI is ByRef POINT As POINTAPI.

    The correct declaration, however, is tricky, because VBA cannot do byval structs.

    For x86 where the arguments are passed on the stack you could simply inline the struct fields:

    Declare PtrSafe Function MonitorFromPoint Lib "user32.dll" ( _
                                                   ByVal X As Long, _
                                                   ByVal Y As Long, _
                                                   ByVal dwFlags As Long) As LongPtr
    

    That won't work for x64, because the POINT struct is 8 bytes in size, which means it goes into a single register under x64. For that to be doable from VBA, you must declare the function in a way that accepts the entire 8 bytes of the struct as a single integer.
    LongLong fits the bill, but it does not exist in 32-bit VBA.
    Currency could also work, provided VBA treats it as an integer, which I'm not sure it does and cannot verify right now.

    The cheap and dirty solution therefore is:

    Public Type POINTAPI
        X As Long
        Y As Long
    End Type
    
    
    #If Win64 Then
        Private Type POINTAPI_AsLongLong
            Value As LongLong
        End Type
        
        Private Declare PtrSafe Function MonitorFromPointInternal _
            Lib "user32.dll" Alias "MonitorFromPoint" ( _
                                                       ByVal pt As LongLong, _
                                                       ByVal dwFlags As Long) As LongPtr
                                                       
        Public Function MonitorFromPoint(pt As POINTAPI, ByVal dwFlags As Long) As LongPtr
            Dim t As POINTAPI_AsLongLong
            LSet t = pt
          
            MonitorFromPoint = MonitorFromPointInternal(t.Value, dwFlags)
        End Function
    #Else
        Private Declare PtrSafe Function MonitorFromPointInternal _
            Lib "user32.dll" Alias "MonitorFromPoint" ( _
                                                       ByVal X As Long, _
                                                       ByVal Y As Long, _
                                                       ByVal dwFlags As Long) As LongPtr
        
        Public Function MonitorFromPoint(pt As POINTAPI, ByVal dwFlags As Long) As LongPtr
            MonitorFromPoint = MonitorFromPointInternal(pt.X, pt.Y, dwFlags)
        End Function
    #End If