Search code examples
c#vb.netdllmouse-hook

VB.NET - Making this application return two integers to set up mouse hook


I've managed to get this C# code (not my own!) working in VB.NET, to implement a mouse hook in a Console application. Here's the VB.NET code:

Imports System.Runtime.InteropServices
Imports System.Windows.Forms

Module Module1
Class InterceptMouse
    Private Shared _proc As LowLevelMouseProc = AddressOf HookCallback
    Private Shared _hookID As IntPtr = IntPtr.Zero

    Public Shared Sub Main()
        _hookID = SetHook(_proc)
        Application.Run()
        UnhookWindowsHookEx(_hookID)
    End Sub

    Private Shared Function SetHook(proc As LowLevelMouseProc) As IntPtr
        Using curProcess As Process = Process.GetCurrentProcess()
            Using curModule As ProcessModule = curProcess.MainModule
                Return SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle(curModule.ModuleName), 0)
            End Using
        End Using
    End Function

    Private Delegate Function LowLevelMouseProc(nCode As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr

    Private Shared Function HookCallback(nCode As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
        Dim MouseSetting As Integer = MouseMessages.WM_LBUTTONDOWN
        Dim hookStruct As MSLLHOOKSTRUCT
        If nCode >= 0 AndAlso MouseSetting = CType(wParam, MouseMessages) Then
            hookStruct = CType(Marshal.PtrToStructure(lParam, GetType(MSLLHOOKSTRUCT)), MSLLHOOKSTRUCT)
            Console.WriteLine(hookStruct.pt.x & ", " & hookStruct.pt.y)
        End If
        Dim pt As New POINT
        pt.x = hookStruct.pt.x
        pt.y = hookStruct.pt.y
        MouseCoordinates.Add(pt)
        Return CallNextHookEx(_hookID, nCode, wParam, lParam)

    End Function

    Private Const WH_MOUSE_LL As Integer = 14

    Private Enum MouseMessages
        WM_LBUTTONDOWN = &H201
        WM_LBUTTONUP = &H202
        WM_MOUSEMOVE = &H200
        WM_MOUSEWHEEL = &H20A
        WM_RBUTTONDOWN = &H204
        WM_RBUTTONUP = &H205
    End Enum

    <StructLayout(LayoutKind.Sequential)> _
    Public Structure POINT
        Public x As Integer
        Public y As Integer
    End Structure

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

    <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
    Private Shared Function SetWindowsHookEx(idHook As Integer, lpfn As LowLevelMouseProc, hMod As IntPtr, dwThreadId As UInteger) As IntPtr
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
    Private Shared Function UnhookWindowsHookEx(hhk As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
    Private Shared Function CallNextHookEx(hhk As IntPtr, nCode As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
    End Function

    <DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
    Private Shared Function GetModuleHandle(lpModuleName As String) As IntPtr
    End Function
End Class

I've then called it from the Sub Main:

Sub Main()
    InterceptMouse.Main()
End Sub

However, when I debug it, it goes to the Application.Run() line and it doesn't advance to the next line, so I can't see what it's doing behind-the-scenes. It then waits for user input and returns integer coordinates on the Console. This isn't what I want it to do. I want to have this code running in another function or a Background Worker, passing coordinates to the main thread whenever it receives a mouse-click, so that it can handle the rest. The problem is, this code is far too complex for my level of understanding and I don't really know how it works. If I can at least manage to make the Console return mouse coordinates whenever the user clicks, I should be able to ignore it and handle the rest of the code from there.


Solution

  • A good reason? Heck no.

    I like the way you throw caution to the wind. Use at your own peril!

    Not sure exactly how you're going to use this within your Console application...

    ...I've refactored it just a bit:

    Imports System.Windows.Forms
    Imports System.Runtime.InteropServices
    Module Module1
    
        Private counter As Integer = 0
        Private WithEvents IM As InterceptMouse
    
        Sub Main()
            IM = InterceptMouse.Instance
            IM.Start()
    
            While counter < 3
                System.Threading.Thread.Sleep(100)
            End While
    
            IM.Shutdown()
    
            Console.Write("Press Enter to Quit")
            Console.ReadLine()
        End Sub
    
        Private Sub IM_MouseClick(pt As InterceptMouse.POINT) Handles IM.MouseClick
            Console.WriteLine(pt.x.ToString() & ", " & pt.y.ToString())
            counter = counter + 1
        End Sub
    
        Public Class InterceptMouse
            Inherits ApplicationContext
    
            Public Event MouseClick(ByVal pt As InterceptMouse.POINT)
    
            Private Shared _IM As InterceptMouse
            Public Shared ReadOnly Property Instance() As InterceptMouse
                Get
                    If IsNothing(_IM) Then
                        _IM = New InterceptMouse()
                    End If
                    Return _IM
                End Get
            End Property
    
            Private _proc As LowLevelMouseProc = AddressOf HookCallback
            Private _hookID As IntPtr = IntPtr.Zero
            Private T As System.Threading.Thread = Nothing
    
            Private Sub New()
            End Sub
    
            Public Sub Start()
                If IsNothing(T) Then
                    T = New Threading.Thread(AddressOf Main)
                    T.Start()
                End If
            End Sub
    
            Private Sub Main()
                _hookID = SetHook(_proc)
                Application.Run(Me)
                UnhookWindowsHookEx(_hookID)
                T = Nothing
            End Sub
    
            Public Sub Shutdown()
                If Not IsNothing(T) Then
                    Application.Exit()
                End If
            End Sub
    
            Private Function SetHook(proc As LowLevelMouseProc) As IntPtr
                Using curProcess As Process = Process.GetCurrentProcess()
                    Using curModule As ProcessModule = curProcess.MainModule
                        Return SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle(curModule.ModuleName), 0)
                    End Using
                End Using
            End Function
    
            Private Delegate Function LowLevelMouseProc(nCode As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
    
            Private Function HookCallback(nCode As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
                Dim MouseSetting As Integer = MouseMessages.WM_LBUTTONDOWN
                Dim hookStruct As MSLLHOOKSTRUCT
                If nCode >= 0 AndAlso MouseSetting = CType(wParam, MouseMessages) Then
                    hookStruct = CType(Marshal.PtrToStructure(lParam, GetType(MSLLHOOKSTRUCT)), MSLLHOOKSTRUCT)
                    Dim pt As New POINT
                    pt.x = hookStruct.pt.x
                    pt.y = hookStruct.pt.y
                    RaiseEvent MouseClick(pt)
                End If
                Return CallNextHookEx(_hookID, nCode, wParam, lParam)
            End Function
    
            Private Const WH_MOUSE_LL As Integer = 14
    
            Private Enum MouseMessages
                WM_LBUTTONDOWN = &H201
                WM_LBUTTONUP = &H202
                WM_MOUSEMOVE = &H200
                WM_MOUSEWHEEL = &H20A
                WM_RBUTTONDOWN = &H204
                WM_RBUTTONUP = &H205
            End Enum
    
            <StructLayout(LayoutKind.Sequential)> _
            Public Structure POINT
                Public x As Integer
                Public y As Integer
            End Structure
    
            <StructLayout(LayoutKind.Sequential)> _
            Private Structure MSLLHOOKSTRUCT
                Public pt As POINT
                Public mouseData As UInteger
                Public flags As UInteger
                Public time As UInteger
                Public dwExtraInfo As IntPtr
            End Structure
    
            <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
            Private Shared Function SetWindowsHookEx(idHook As Integer, lpfn As LowLevelMouseProc, hMod As IntPtr, dwThreadId As UInteger) As IntPtr
            End Function
    
            <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
            Public Shared Function UnhookWindowsHookEx(hhk As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
            End Function
    
            <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
            Private Shared Function CallNextHookEx(hhk As IntPtr, nCode As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
            End Function
    
            <DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
            Private Shared Function GetModuleHandle(lpModuleName As String) As IntPtr
            End Function
    
        End Class
    
    End Module