2014年2月5日水曜日

vb.netでキーボードHook

仕事で開発・保守をしているシステムで、モジュール提供されている部品に不具合があり、
どうしても回避しないといけなかったので、Hookにて対処する事にしました。

現象としては、「ある特定の項目にフォーカスがある際に、F3を押すとゼロ除算エラー」
モジュール側の不具合であることは調査により発覚。しかし、モジュールは直せない。

ということで、HookにてF3を拾って、潰すというロジックです。

いろいろ調べていると、".net framework"ではグローバルフックができず、マウス、キーボードのみ。とか書いてはありましたが、APIを使えるのでさほど気にもならないかなと思ってます。

とはいえ、Hookは低レベル(OSに近い部分)での制御になりますので、使い方には十分ご注意ください。
何の処理でもCancelなんてしようものなら、全ての入力を受け付けなくなってしまう事もあり得ます。

ソース

クラスライブラリ側


・APIを直接フックを実施
・フックにより取得できるメッセージの中からキーコードを取得
・イベントとして外部へ公開


Imports System.Runtime.InteropServices

''' <summary>
''' キーボードフックを行うライブラリ
''' </summary>
Public Class KeyboardHook
  Implements IDisposable

  Dim WH_KEYBOARD_LL As Integer = 13
  Private Shared hHook As Integer = 0

  Private hookproc As CallBack

  Public Event KeyboardEvents(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs)

  Public Delegate Function CallBack( _
    ByVal nCode As Integer, _
    ByVal wParam As IntPtr, _
    ByVal lParam As IntPtr) As Integer

#Region "DLL Declare"

  'Import for the SetWindowsHookEx function.
  <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
   Public Overloads Shared Function SetWindowsHookEx _
     (ByVal idHook As Integer, ByVal HookProc As CallBack, _
      ByVal hInstance As IntPtr, ByVal wParam As Integer) As Integer
  End Function

  'Import for the GetModuleHandle function.
  <DllImport("kernel32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
   Public Overloads Shared Function GetModuleHandle _
      (ByVal lpModuleName As String) As IntPtr
  End Function

  'Import for the CallNextHookEx function.
  <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
   Public Overloads Shared Function CallNextHookEx _
     (ByVal idHook As Integer, ByVal nCode As Integer, _
      ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
  End Function

  'Import for the UnhookWindowsHookEx function.
  <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
     Public Overloads Shared Function UnhookWindowsHookEx _
       (ByVal idHook As Integer) As Boolean
  End Function

  'KeyboardHookStruct structure declaration.
  <StructLayout(LayoutKind.Sequential)> Public Structure KeyboardLLHookStruct
    Public vkCode As Integer
    Public scanCode As Integer
    Public flags As Integer
    Public time As Integer
    Public dwExtraInfo As Integer
  End Structure

#End Region

#Region "Constructor"

  Public Sub New()
    hookproc = AddressOf KeybordHookProc
    hHook = SetWindowsHookEx(WH_KEYBOARD_LL, hookproc, GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0)
    If hHook.Equals(0) Then
      MsgBox("SetWindowsHookEx Failed")
    End If
  End Sub

#End Region

#Region "KeybordHookProc"

  Private Function KeybordHookProc( _
    ByVal nCode As Integer, _
    ByVal wParam As IntPtr, _
    ByVal lParam As IntPtr) As Integer

    If (nCode < 0) Then
      Return CallNextHookEx(hHook, nCode, wParam, lParam)
    End If

    Dim hookStruct As New KeyboardLLHookStruct()
    hookStruct = CType(Marshal.PtrToStructure(lParam, hookStruct.GetType()), KeyboardLLHookStruct)

    Dim e As New System.Windows.Forms.KeyEventArgs(DirectCast(hookStruct.vkCode, System.Windows.Forms.Keys))
    OnKeyboardEvents(e)

    'キーCancelなら継続しない
    If (e.Handled = True) Then
      Return 1
    End If
    Return CallNextHookEx(hHook, nCode, wParam, lParam)
  End Function

#End Region

#Region "OnKeyboardEvents"

  Protected Sub OnKeyboardEvents(ByVal e As KeyEventArgs)

    RaiseEvent KeyboardEvents(Me, e)

  End Sub

#End Region

  Public Sub Dispose() Implements System.IDisposable.Dispose

    If (hHook > 0) Then
      UnhookWindowsHookEx(hHook)
    End If

  End Sub


End Class



潰す側(コントロールをラップ)


・ライブラリ初期化と解放
・イベントでF3が来るのを監視しておき、F3が来て、かつ対象のコントロールの場合、イベントキャンセル


  'F3潰し用のHookイベントハンドラ
  Private keyHook As KeyboardHook

  Protected Overrides Sub OnCreate()
    MyBase.OnCreate()

    If (Me.DesignMode = False) Then
      keyHook = New KeyboardHook()
      AddHandler keyHook.KeyboardEvents, AddressOf keyHook_KeyboardEvents
    End If

  End Sub
  Private Sub keyHook_KeyboardEvents(ByVal sender As Object, ByVal e As KeyEventArgs)

    'F3は潰す
    If (e.KeyCode = Keys.F3) Then
      If TypeOf (DirectCast(Me.TopLevelControl, System.Windows.Forms.Form).ActiveControl) Is エラーを起こすコントロールの型 Then
        e.Handled = True
      End If
    End If
  End Sub

  Protected Overrides Sub OnDestroy()
    If (Me.DesignMode = False) Then
      If Not (keyHook Is Nothing) Then
        keyHook.Dispose()
      End If
    End If
    MyBase.OnDestroy()
  End Sub

0 件のコメント: