KeyBoard Key in的习惯分析(JournalRecord Hook)

来源:cww

如果说,我们想得知某些键被KeyIn的频率,或许会想,那设定Form KeyPreview不就可
以,是啊,不过这样做就变成您要使用者对着您的 Form做事才有,出了这个Form就没有
办法了,所以又要使用其他的方式了,第一个想到的是KeyBoard Hook,是的,这是一个
正确的想法。不过说真的,在VB中我试过了一段时间的Remote Hook for KeyBoard Hook
,如果搭配C或Delphi所作出的.DLL那应没有问题,但以vb本身要做出Remote Hook,似
乎只有使用ActiveX. Dll(因为Remote Hook大多只能搭配.dll来做,而VB5不能做如同
Function Call的.Dll,它只能做出ActiveX.Dll),但我testing 了好久,都没有成功
,而王国荣先生它说会安排在未来的Run PC中做介绍,那这问题就等他来介绍了。

可是除了Keyboard Hook之外尚有其他的Hook来做,那就是JournalRecord Hook,这个
Hook和JournalPlayBack Hook一样,是System Wide,但不用在.Dll之中,是个例外,
但它要求系统中只能有一个这种Hook。JournalRecord Hook是在硬体讯息(Mouse, Keyboaed等)
在System queue中被取出时所引发的Hook,所以 我们也可以拿来用上一用。

其Hook Procedure定义如下
Function JournalRecordProc (
    Byval   code    As Long ,   // hook code
    ByVal   wParam  As Long ,   // undefined
    ByVal   lParam  As Long     // address of message being processed
    ) As Long

code有以下的值
  HC_ACTION      The lParam parameter points to an EVENTMSG structure
                 containing information about a message removed from the
                 system queue. The hook procedure must record the contents of
                 the structure by copying them to a buffer or file.
  HC_SYSMODALOFF A system-modal dialog box has been destroyed. The hook
                 procedure must resume recording.
  HC_SYSMODALON  A system-modal dialog box is being displayed. Until the
                 dialog box is destroyed, the hook procedure must stop recording.

  注:HC_SYSMODALOFF, HC_SYSMODUALON只在16位元程式产生一个system-modal msgbox时
     才会收到。

wParam
  Specifies a NULL value.
lParam
  Points to an EVENTMSG structure that contains the message to be recorded.

而EventMsg的定义如下:
Type EVENTMSG
        message As Long
        paramL As Long
        paramH As Long
        time As Long
        hwnd As Long
End Type

message 若为键盘讯息可为WM_(SYS)KeyUp或WM_(SYSKeyDown),Mouse讯息则是
        WM_XButtonUp / WM_XButtonDown /WM_MOUSEMOVE等
paramL  若为键盘讯息,则是键盘的虚拟码,Mouse讯息则为Mouse相对Screen之x座标(Pixels)
paramH  若为键盘讯息,则是键盘的扫描码,Mouse讯息则为Mouse相对Screen之y座标(Pixels)
time    讯息发生的时间(tick time)

而我们可以用MapVirtualKey来转换虚拟码成UnShift的Ascii值,本来嘛,这种虚拟码或
扫描码本身就没有Shift+某个键 的控制,而按Shift+A ==>"a",那是TranslateMessage()
所做出来的事。所以我们可另外使用GetAsyncKeystate()来Check Shift有没有按下。
这个程式将重要的部份都完成,而记录KeyBoard 到底是Keyin了哪些键,是Keyup/KeyDown
有没有搭配Shift键等,这些可以另外使用资料库或档案来记录,这里便不再多说明。

'以下程式在.BAS
Public Const WM_KEYDOWN = &H100
Public Const WM_KEYUP = &H101
Public Const HC_ACTION = 0

Public Const WH_JOURNALRECORD = 0
Type EVENTMSG
        message As Long
        paramL As Long
        paramH As Long
        time As Long
        hwnd As Long
End Type
Declare Function SetWindowsHookEx Lib "user32" Alias _
   "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _
   ByVal hmod As Long, ByVal dwThreadId As Long) As Long
Declare Function UnhookWindowsHookEx Lib "user32" _
   (ByVal hHook As Long) As Long
Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, _
   ByVal nCode As Long, ByVal wParam As Long, lParam As Any) As Long
Declare Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" _
  (lpvDest As Any, ByVal lpvSource As Long, ByVal cbCopy As Long)
Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" _
  (ByVal wCode As Long, ByVal wMapType As Long) As Long
Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer

Public hHook As Long   ' handle of Hook Procedure
Public msg As EVENTMSG

Sub EnableHook()
   hHook = SetWindowsHookEx(0, AddressOf HookProc, App.hInstance, 0)
End Sub
Sub FreeHook()
    Dim ret As Long
    ret = UnhookWindowsHookEx(hHook)
End Sub
Function HookProc(ByVal code As Long, ByVal wParam As Long, _
                ByVal lParam As Long) As Long
Dim i As Long, j As Integer
If code <> HC_ACTION Then
   HookProc = CallNextHookEx(hHook, code, wParam, lParam)
   Exit Function
End If
CopyMemory msg, lParam, LenB(msg)
If msg.message = WM_KEYUP Then
   j = GetAsyncKeyState(vbKeyShift) 'Check Shift是否正按下
   i = MapVirtualKey(msg.paramL, 2) '转换成unshift Ascii Code
   If j <> 0 Then
     Debug.Print Chr(i And &HFF); " Key Up with Shift Down"
   Else
     Debug.Print Chr(i And &HFF); " Key Up without Shift Down"
   End If
End If
If msg.message = WM_KEYDOWN Then
   j = GetAsyncKeyState(vbKeyShift)
   i = MapVirtualKey(msg.paramL, 2)
   If j <> 0 Then
     Debug.Print Chr(i And &HFF); " Key Down with Shift Down"
   Else
     Debug.Print Chr(i And &HFF); " Key Down without Shift Down"
   End If
End If
HookProc = CallNextHookEx(hHook, code, wParam, lParam)
End Function
'以下程式在Form
Private Sub Form_Load()
Call EnableHook
End Sub

Private Sub Form_Unload(Cancel As Integer)
Call FreeHook
End Sub