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