VBA userform - Solve application crash when hooking into mouse scrollwheel (VBA7, Win10/64bit, Word2016/64bit)

3

From hours of searching this site and googling I found that hooking into mouse scroll wheel events from VBA for use in userforms/controls is well documented for 32 bit Office and I got this to work quickly and flawlessly on a Win10/64 bit and Word 2016/32 bit environment. However when moving to a 64 bit Office environment (Win10/64bit) it consistently crashed after calling 'SetWindowsHookEx' and then moving the mouse cursor.

Being aware of the Long vs LongLong (LongPtr) implementation changes from 32 to 64 bit and the inconsistent code examples I found with respect to Long/LongPtr, I checked every bit of my code using the standard Microsoft WIN32API declare statements for 64 bit but it still crashes.

For reference: I'm building my own 'Insert cross-references' functionality as an add-in to Word, for private use.

The event log only shows an 'Exception code: 0xc0000005' occurred in VBE7.dll and I am at a loss as how to continue troubleshooting this. I've spent hours online searching for options, trying different things with my code but to no avail. Can anyone advise how to proceed to drill down on this problem? Any help is appreciated.

The relevant code snippet is below, all declares come from the above linked WIN32API reference except WindowFromPoint because the 'LongLong' type for Point seemed wrong to me. All checks on err.LastDllError report no error , except for SetWindowsHookEx, the msg from err.lastDllError is Command successfully completed. On SetWindowsHookEx the message is empty but a non-zero mouse hook is returned. Moving the mouse directly after this call crashes Word - removing the call to SetWindowsHookEx does not crash Word. I've set a debug.print in MouseProc but it never gets there.

Below code is void of VBA7/WIN64 checks as I wanted a clean code for 64 bit to check and get it working before I merge it with my 32 bit implementation.

Option Explicit

' Window field offsets for GetWindowLong() and GetWindowWord()
Private Const GWL_WNDPROC = (-4)
Private Const GWL_HWNDPARENT = (-8)
Private Const GWL_STYLE = (-16)
Private Const GWL_EXSTYLE = (-20)
Private Const GWL_USERDATA = (-21)
Private Const GWL_ID = (-12)
Private Const GWL_HINSTANCE As Long = (-6)

'set up the variables used for the mousewheel
Private Const WH_MOUSE_LL As Long = 14
Private Const WM_MOUSEWHEEL As LongPtr = &H20A
Private Const HC_ACTION As Long = 0

' DLL messages
Private Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000

Private Type POINTAPI
    X As Long
    Y As Long
End Type

Private Type Msg
    hwnd As LongPtr
    message As Long
    wParam As LongPtr
    lParam As LongPtr
    time As Long
    pt As POINTAPI
End Type

Private Type MOUSEHOOKSTRUCT
    pt As POINTAPI
    hwnd As LongPtr
    wHitTestCode As Long
    dwExtraInfo As LongPtr
End Type

Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
'Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal point As LongLong) As LongPtr
Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal point As LongPtr) As LongPtr
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
Private Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongPtrA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
Private Declare PtrSafe Function SetWindowLongPtr Lib "user32" Alias "SetWindowLongPtrA" (ByVal hwnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As LongPtr) As LongPtr
Private Declare PtrSafe Function SetWindowsHook Lib "user32" Alias "SetWindowsHookA" (ByVal nFilterType As Long, ByVal pfnFilterProc As LongPtr) As LongPtr
Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As LongPtr, ByVal lpfn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As Long) As LongPtr
Private Declare PtrSafe Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal nCode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () As Long

Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As LongPtr) As Long
Private Declare PtrSafe Function FormatMessage Lib "kernel32" Alias "FormatMessageA" (ByVal dwFlags As Long, lpSource As Any, ByVal dwMessageId As Long, ByVal dwLanguageId As Long, ByVal lpBuffer As String, ByVal nSize As Long, Arguments As LongPtr) As Long
Private Declare PtrSafe Function GetLastError Lib "kernel32" () As Long

Dim n As Long
Private mCtl As MSForms.Control
Private mbHook As Boolean

Private mLngMouseHook As LongPtr
Private mListBoxHwnd As LongPtr

Sub HookListBoxScroll64(frm As Object, ctl As MSForms.Control)

    Dim tPT As POINTAPI
    Dim lngAppInst As LongPtr
    Dim hwndUnderCursor As LongPtr
    Dim ptLL As LongLong

    GetCursorPos tPT
    Debug.Print "GetCursorPos err: " & GetWin32ErrorDescription(err.LastDllError)

    ptLL = PointToLongLong(tPT)
    Debug.Print "PointToLongLong err: " & GetWin32ErrorDescription(err.LastDllError)

    hwndUnderCursor = WindowFromPoint(ptLL)
    Debug.Print "WindowFromPoint err: " & GetWin32ErrorDescription(err.LastDllError)

    If Not IsNull(frm.ActiveControl) And Not frm.ActiveControl Is ctl Then
        ctl.SetFocus
    End If

    If mListBoxHwnd <> hwndUnderCursor Then
        UnhookListBoxScroll64
        Debug.Print "UnhookListBoxScroll64 err: " & GetWin32ErrorDescription(err.LastDllError)

        Set mCtl = ctl
        mListBoxHwnd = hwndUnderCursor
        lngAppInst = GetWindowLongPtr(mListBoxHwnd, GWL_HINSTANCE)
        Debug.Print "GetWindowLongPtr AppInst: " & lngAppInst & ", err: " & GetWin32ErrorDescription(err.LastDllError)

        If Not mbHook Then
            mLngMouseHook = SetWindowsHookEx(WH_MOUSE_LL, AddressOf MouseProc, lngAppInst, 0)
            Debug.Print "SetWindowsHookEx hook: " & mLngMouseHook & ", err: " & GetWin32ErrorDescription(err.LastDllError)
            mbHook = mLngMouseHook <> 0
        End If
    End If

End Sub

Private Function MouseProc( _
                        ByVal nCode As Long, ByVal wParam As LongPtr, _
                        ByRef lParam As MOUSEHOOKSTRUCT) As LongPtr
    Debug.Print "MouseProc"

    Dim idx As Long
    On Error GoTo errH
    If (nCode = HC_ACTION) Then
        Dim ptLL As LongLong
        ptLL = PointToLongLong(lParam.pt)
        If WindowFromPoint(ptLL) = mListBoxHwnd Then
            If wParam = WM_MOUSEWHEEL Then
                MouseProc = True
                If TypeOf mCtl Is frame Then
                    If lParam.hwnd > 0 Then idx = -10 Else idx = 10
                    idx = idx + mCtl.ScrollTop
                    If idx >= 0 And idx < ((mCtl.ScrollHeight - mCtl.Height) + 17.25) Then
                        mCtl.ScrollTop = idx
                    End If
                ElseIf TypeOf mCtl Is UserForm Then
                    If lParam.hwnd > 0 Then idx = -10 Else idx = 10
                    idx = idx + mCtl.ScrollTop
                    If idx >= 0 And idx < ((mCtl.ScrollHeight - mCtl.Height) + 17.25) Then
                        mCtl.ScrollTop = idx
                    End If
                Else
                    If lParam.hwnd > 0 Then idx = -1 Else idx = 1
                    idx = idx + mCtl.ListIndex
                    If idx >= 0 Then mCtl.ListIndex = idx
                End If
            Exit Function
            End If
        Else
            UnhookListBoxScroll64
        End If
    End If
    MouseProc = CallNextHookEx( _
                            mLngMouseHook, nCode, wParam, ByVal lParam)
    Exit Function
errH:
    UnhookListBoxScroll64
End Function
vba
ms-word
word-vba
asked on Stack Overflow May 15, 2017 by Boeryepes • edited May 20, 2017 by Boeryepes

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0