仕事で開発・保守をしているシステムで、モジュール提供されている部品に不具合があり、
どうしても回避しないといけなかったので、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