vb.netwinapimouse-hook

MouseHook - MSLLHOOKSTRUCT.mouseData always returns the same value when handling MouseWheel


I have searched the web tirelessly and tried every possible solution I have come across; yet cannot find a working solution.

I have a mouse hook that I have been using for a long time now and it works great except for when I try to distinguish forward/back mouse wheel messages.

I have seen the mouseData member of the MSLLHOOKSTRUCT structure declared as an Integer and UInteger. Either way I declare it, I am unable to get the HIWORD for the WM_MOUSEWHEEL message to distinguish forwards from back because the value is always the same when I wheel forward or back.

Test Output:

Wheel Forward:

mouseData: 23074474
HIWORD: 352

Wheel Back:

mouseData: 23074474
HIWORD: 352

Code:

    <Extension>
    Public Function HIWORD(value As Integer) As Integer
        Return BitConverter.ToInt16(BitConverter.GetBytes(value), 2)
    End Function

    Private Shared MouseHookProcedure As HookProcHandler
    Private Shared PreviousCallback As Object()
    Private Shared ProcPtrSet As Boolean
    Private Shared HookHandle As IntPtr

    Public Shared Function Start() As Boolean
        If HookHandle <> IntPtr.Zero Then Return True
        If Not ProcPtrSet Then
            ProcPtrSet = True
            MouseHookProcedure = New HookProcHandler(AddressOf HookProc)
        End If
        Using p As Process = Process.GetCurrentProcess()
            Using m As ProcessModule = p.MainModule
                SetWindowsHookEx(HOOKTYPE.WH_MOUSE, MouseHookProcedure, Pinvoke.Kernel32.Functions.GetModuleHandle(m.ModuleName), Pinvoke.Kernel32.Functions.GetCurrentThreadId)
            End Using
        End Using
        If HookHandle = 0 Then Return False Else Return True
    End Function

    Public Shared Function [Stop]() As Boolean
        Return UnhookWindowsHookEx(HookHandle)
    End Function

    Private Shared Function HookProc(nCode As Integer, wParam As IntPtr, lParam As IntPtr) As Integer
        Dim MSLLHS As MSLLHOOKSTRUCT = Marshal.PtrToStructure(lParam, GetType(MSLLHOOKSTRUCT))
        If nCode < 0 Then
            Return CallNextHookEx(HookHandle, nCode, wParam, lParam)
        Else
            ' Catch duplicate messages caused by the default behavior of the MouseProc function.
            Dim Msg As MouseMessages = CType(wParam, MouseMessages)
            If PreviousCallback IsNot Nothing Then
                If CType(PreviousCallback(0), MouseMessages) = Msg AndAlso CompairMSLLHOOKSRUCT(PreviousCallback(1), MSLLHS) AndAlso CType(PreviousCallback(2), Date) = Now Then
                    GoTo NextCallback
                End If
            End If
            ' Raise Event
            Select Case Msg
                Case MouseMessages.MouseWheel
                    Msg = If(CType(MSLLHS.mouseData, Integer).HIWORD > 0, MouseMessages.MouseWheelForward, MouseMessages.MouseWheelBack)
                    RaiseEvent Message(Msg MSLLHS)
                Case Else
                    RaiseEvent Message(Msg, MSLLHS)
            End Select
            PreviousCallback = New Object(2) {CType(wParam, MouseMessages), MSLLHS, Now}
NextCallback:
            Try
                Return CallNextHookEx(HookHandle, nCode, wParam, lParam)
            Catch ex As Exception
                Return [Stop]()
            End Try
        End If
    End Function

    Private Shared Function CompairMSLLHOOKSRUCT(left As MSLLHOOKSTRUCT, right As MSLLHOOKSTRUCT) As Boolean
        Return left.pt.X = right.pt.X AndAlso left.pt.Y = right.pt.Y AndAlso left.mouseData = right.mouseData AndAlso left.flags = right.flags AndAlso left.time = right.time AndAlso left.dwExtraInfo = right.dwExtraInfo
    End Function

Pinvoke:

Public Delegate Function HookProcHandler(nCode As System.Int32, wParam As System.IntPtr, lParam As System.IntPtr) As System.Int32

<DllImport(User32, CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall, SetLastError:=True)>
Public Shared Function UnhookWindowsHookEx(idHook As System.IntPtr) As System.Boolean
End Function

<DllImport(User32, CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall, SetLastError:=True)>
Public Shared Function SetWindowsHookEx(idHook As System.Int32, lpfn As HookProcHandler, hInstance As System.IntPtr, threadId As System.Int32) As System.IntPtr
End Function

<DllImport(User32, CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall, SetLastError:=True)>
Public Shared Function CallNextHookEx(hhk As System.IntPtr, nCode As System.Int32, wParam As System.IntPtr, lParam As System.IntPtr) As System.IntPtr
End Function

<DllImport(Kernel32, CharSet:=CharSet.Auto, SetLastError:=True)>
Public Shared Function GetModuleHandle(lpModuleName As System.String) As System.IntPtr
End Function

<StructLayout(LayoutKind.Sequential)>
Public Structure MSLLHOOKSTRUCT    
    Public pt As POINT
    Public mouseData As System.UInt32
    Public flags As System.UInt32
    Public time As System.UInt32
    Public dwExtraInfo As System.IntPtr
End Structure

<StructLayout(LayoutKind.Sequential)>
Public Structure POINT
    Public X As System.Int32
    Public Y As System.Int32
    Public Sub New(ByVal X As System.Int32, ByVal Y As System.Int32)
        Me.X = X
        Me.Y = Y
    End Sub
End Structure

Public Enum MouseMessages As System.UInt32
    MouseWheel = WindowsMessages.WM_MOUSEWHEEL ' &H20E
    ' Etc...
End Enum

EDIT
This is not a continuance of the original question, nor an addition question, but somewhat of an elaboration to the solution provided by Remy Lebeau.

For anyone who may need it, MOUSEHOOKSTRUCTEX is defined as such:

<StructLayout(LayoutKind.Sequential)>
Public Structure MOUSEHOOKSTRUCTEX    
    Public mouseHookStruct As MOUSEHOOKSTRUCT       
    Public mouseData As System.Int32    
End Structure

Thank you so much for all your help guys!


Solution

  • Your HookProc is using the wrong struct type.

    You are installing a WH_MOUSE hook, which uses the MOUSEHOOKSTRUCT struct in its lParam. But your code is casting the lParam to the MSLLHOOKSTRUCT struct instead, which is used by WH_MOUSE_LL not WH_MOUSE (hence the LL in the struct's name).

    As such, the MSLLHOOKSTRUCT.mouseData field you are trying to access in memory is actually the MOUSEHOOKSTRUCT.hwnd field. The value remains the same because successive WM_MOUSEWHEEL messages are likely being sent to the same window.

    Note that there is no mouseData field in the MOUSEHOOKSTRUCT struct. There is, however, a mouseData field in the MOUSEHOOKSTRUCTEX struct. So you can cast the lParam to that struct type instead when the message is WM_MOUSEWHEEL.

    Also, note that depending on implementation, WM_MOUSEWHEEL may be a synthesized message, not an actual mouse event, so it may not show up in any mouse hook at all. You might want to consider using a WH_GETMESSAGE or WH_CALLWNDPROC hook instead of a WH_MOUSE hook.