Win32 API Programming with Visual Basic

Page 322
  19. Windows Hooks  
   
  A hook is a technique for observing various portions of the Windows message stream. Windows defines more than a dozen different types of hooks, the locations of several of which are shown in Figure 19-1 (along with subclassing).  
   
  The principle behind hook is quite similar to that of subclassing. If a message passes through a portion of the Windows messaging system in which a hook is installed, the system will call a hook procedure which we must implement.  
 
  Global and Thread-Specific Hooks  
   
  A hook may be thread-specific, in which case it hooks messages (of the specified hook type) sent only to a particular thread, or it may be global (or system-wide), in which case it hooks all messages (of the specified hook type) throughout the system. Hooks of some types can be set for either individual threads or globally, whereas other hook types (such as journal hooks) must be global.  
   
  The difference between thread-specific hooks and global hooks is quite significant. In the first place, global hooks reduce performance on a system-wide basis and are usually used for debugging purposes.  
   
  More seriously, a global hook procedure will need to be called from any thread throughout the system that receives an appropriate message type. Hence, the hook procedure must be placed in a module that is accessible to all threads in the system that is, a DLL.  
   
  Indeed, whenever a thread in one process sets a hook on a thread in another process, the issue of cross-process communication must be addressed. To illustrate, imagine that Thread A in Process A wants to set a mouse hook on Thread B in Process B. Thread A must call the SetWindowsHookEx function (discussed soon) to  
Page 323
   
   
   
  Figure 19-1.

Windows hooks

 
   
  set the hook, passing this function the thread ID of Thread B. However, it is the hook procedure in Thread B that is called when a mouse message is sent to Thread B.  
   
  This raises two issues: how does Thread A get the hook procedure into the address space of Process B, and, once this is done, how does Thread A get the results of any processing done by the hook procedure in Process B?  
   
  The answer to the first question is that Windows takes care of this when Thread A calls SetWindowsHookEx. But for the system to do so, the hook procedure must be  
Page 324
   
  in a DLL, so that Windows can inject (map) that DLL into the address space of Process B. (We will discuss DLL injection in detail in Chapter 20, DLL Injection and Foreign Process Access.) Unfortunately, this takes global hooks out of the realm of Visual Basic, because we cannot write a traditional DLL in VB.  
   
  Thus, a global hook can potentially cause the injection of a DLL into every existing process space. Fortunately, if the DLL can be loaded at its default base address in each case, then it is not necessary to commit additional physical memory to a new virtual instance of the DLL.  
   
  Also, the official documentation is a little vague as to when these DLLs will be released. Clearly, a process will not call the API function FreeLibrary to unload a DLL that it is not aware is even loaded! However, Richter says in his book:  
  When a thread calls the UnhookWindowsHookEx function, the system cycles through its internal list of processes into which it had to inject the DLL and decrements the DLL's lock count. When this lock count reaches 0, the DLL is automatically unmapped from the process's address space.  
   
  Indeed, some experimentation seems to bear this out. Injected DLLs appear to disappear in a ghostly fashion when no longer needed. Nevertheless, global hooks should be used with circumspection.  
   
  Our plan now is to discuss the general principles of Windows hooks and then implement a thread-specific hook entirely within VB. Then, with the help of a simple DLL written in VC++, we will implement a global hook. In Chapter 20, we will discuss the process of DLL injection in more detail.  
 
  Setting the Hook  
   
  To set a Windows hook, we use the SetWindowsHookEx function:  
 
  HHOOK SetWindowsHookEx(

   int idHook,          // type of hook to install

   HOOKPROC lpfn,       // address of hook procedure

   HINSTANCE hMod,      // handle to application instance

   DWORD dwThreadId     // identity of thread to install hook for

);

 
   
  or, in VB:  
 
  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

 
   
  If successful, the SetWindowsHookEx function returns a handle to the newly created hook. The idHook parameter specifies the type of hook to install. We will  
Page 325
   
  discuss some hook types soon. If the hook is intended to be thread-specific, then the dwThreadId parameter is the thread ID of the target thread. If the hook is intended to be global, then dwThreadId should be set to NULL (0). The values of the other two parameters depend upon the scope of the hook. There are two possibilities.  
   
  If the hook is a local hook, that is, if it is a thread-specific hook for a thread in the process that is calling SetWindowsHookEx, then the hook procedure will also be in the calling process. Hence, it can be referred to by its address. In this case, lpfn is the address of the hook procedure and hMod is NULL (0).  
   
  On the other hand, if the thread is thread-specific for a foreign thread, or if the hook is global, then lpfn is the address of the hook procedure within the DLL that contains that procedure, and hMod is the instance handle of this DLL, that is, the base address of this DLL. To be absolutely clear, these values (lpfn and hMod) will, of course, be for the copy of the DLL that is loaded in the calling process. However, Windows will translate these values to the correct values for each process that is forced (by the hook) to load the DLL. Put another way, Windows will use the base address of the DLL and the address of the hook procedure in the calling process to determine the offset of the hook procedure within the DLL:  
 
  offset of hook proc = address of hook proc - base address of DLL  
   
  This offset can be used to find the hook procedure regardless of where the DLL is loaded into a given process space.  
   
  When an application no longer requires a hook, it should call UnhookWindowsHookEx:  
 
  BOOL UnhookWindowsHookEx(

   HHOOK hhk // handle to hook procedure to remove

);

 
 
  Hook Procedures  
   
  Hook procedures have the form:  
 
  LRESULT CALLBACK HookProc(

   int nCode,

   WPARAM wParam,

   LPARAM lParam

);

 
   
  where nCode is a hook code that depends upon the hook type. The other parameters also depend on the hook type, but generally contain information about the message. For instance, wParam may be the message number and lParam may be a pointer to further message information.  
Page 326
 
  Hook Types  
   
  Windows implements more than a dozen type of hooks (as shown in Figure 19-1). Note that some hooks are allowed to modify a message, whereas others can only examine the message. Here is a sampling:  
 
  WH_CALLWNDPROC

Windows calls a WH_CALLWNDPROC hook procedure before passing a message generated by a call to SendMessage to the receiving window procedure. Note that this hook is allowed to examine a hooked message, but not modify it.

 
 
  WH_CALLWNDPROCRET

Windows calls the WH_CALLWNDPROCRET hook procedure after the window procedure has processed a message generated by SendMessage. This hook works only within the process that generated the message. This type of hook also gives information about the results of the message-processing by the window procedure.

 
 
  WH_GETMESSAGE

Windows calls the WH_GETMESSAGE hook procedure when the GetMessage function has retrieved a message from a thread's message queue, but before passing the message to the destination window procedure. This type of hook is allowed to modify hooked messages.

 
 
  WH_JOURNALRECORD and WH_JOURNALPLAYBACK

Windows calls the WH_JOURNALRECORD hook procedure when a message is removed from the system message queue. Thus, it monitors hardware (mouse and keyboard) messages only. This hook is generally used, in conjunction with the WH_JOURNALPLAYBACK hook, to record and later play back user input. The WH_JOURNALRECORD hook is a global hook it cannot be used to hook an individual thread. A WH_JOURNALRECORD hook procedure must not modify hooked messages.

 
 
  WH_KEYBOARD

Windows calls the WH_KEYBOARD hook procedure when an application calls GetMessage (or PeekMessage) and the retrieved message is a keyboard message (WM_KEYUP or WM_KEYDOWN).

 
 
  WH_MOUSE

Windows calls the WH_MOUSE hook procedure when an application calls GetMessage (or PeekMessage) and the retrieved message is a mouse message.

 
 
  WH_SHELL

Windows calls the WH_SHELL hook procedure when the Windows shell application is about to be activated and when a top-level window is created or destroyed.

 
Page 327
 
  Hook Chains  
   
  It is important to understand that more than one hook of a particular type may exist in the system at any given time. Indeed, several applications may have installed, say, WH_MOUSE hooks. For this reason, Windows maintains a separate hook chain for each type of hook. A hook chain is just a list of pointers to the hook procedures for that hook type.  
   
  When a message occurs that is associated with a particular type of hook, Windows passes that message to the first (most recently created) hook procedure in the hook chain for that hook type. It is up to the hook procedure to determine what to do with the message. However, as noted earlier, some hooks are allowed to alter or delete messages, whereas others are not.  
   
  If a hook procedure wants to (or must) pass the message along the hook chain, it calls the function CallNextHookEx:  
 
  LRESULT CallNextHookEx(

   HHOOK hhk,       // handle to current hook (returned by SetWindowsHookEx)

   int nCode,       // hook code passed to hook procedure

   WPARAM wParam,   // value passed to hook procedure

   LPARAM lParam    // value passed to hook procedure

);

 
   
  As you can see, this basically just passes on the message information to the next hook procedure in the hook chain.  
 
  Example: A Local Hook  
   
  Implementing a local hook is not hard. Figure 19-2 shows the main window of our example utility rpiLocalHook.  
   
   
   
  Figure 19-2.

A local mouse hook example

 
Page 328
   
  The entire code behind the form is:  
 
  Option Explicit

Sub EnableHook()

   ghHook = SetWindowsHookEx(WH_MOUSE, AddressOf MouseProc, 0&, App.ThreadID)

End Sub

Sub DisableHook()

   UnhookWindowsHookEx ghHook

End Sub

Private Sub cmdDisable_Click()

   DisableHook

   ghHook = 0

   lblIsHooked = "Not Hooked"

   lblIsHooked.ForeColor = &HO

End Sub

Private Sub cmdEnable_Click()

   EnableHook

   lblIsHooked = "Hooked"

   lblIsHooked.ForeColor = &HFF

End Sub

Private Sub cmdExit_Click()

   Unload Me

End Sub

Private Sub Form_Unload(Cancel As Integer)

   If ghHook <> 0 Then DisableHook

End Sub

 
   
  The action here is in the EnableHook procedure, wherein lies the call to SetWindowsHookEx:  
 
  ghHook = SetWindowsHookEx(WH_MOUSE, AddressOf MouseProc, 0& App.ThreadID)  
   
  The supporting standard module contains the mouse hook procedure. Example 19-1 is the entire code for this module.  
   
  Example 19-1. SetWindowsHookEx  
   
  Option Explicit

Public ghHook As Long

Dim mhs As MOUSEHOOKSTRUCT

Dim sText As String

Public Const WH_MOUSE = 7

Type POINTAPI

     x As Long

     y As Long

End Type

 
Page 329
   
  Example 19-1. SetWindowsHookEx (continued)  
   
  Type MOUSEHOOKSTRUCT

     pt As POINTAPI

     hwnd As Long

     wHitTestCode As Long

     dwExtraInfo 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 CallNextHookEx Lib  user32  ( _

   ByVal ghHook As Long, _

   ByVal ncode As Long, _

   ByVal wParam As Integer, _

   ByVal lParam As Long _

) As Long

Declare Function UnhookWindowsHookEx Lib  user32  ( _

   ByVal ghHook As Long _

) As Long

Declare Sub CopyMemory Lib  kernel32  Alias  RtlMoveMemory  ( _

   Destination As Any, _

   Source As Any, _

   ByVal Length As Long)

-----

Public Function MouseProc (ByVal ncode As Long, ByVal wParam As Long, _

   ByVal lParam As Long) As Long

 Documentation says to do this

If ncode < 0 Then

     Forward message and get out

   MouseProc = CallNextHookEx(ghHook, ncode, wParam, lParam)

   Exit Function

End If

 Get MOUSEHOOKSTRUCT pointed to by lParam

CopyMemory mhs.pt.x, ByVal lParam, LenB(mhs)

 Fill text box with message data

sText =  MsgID:   & wParam

sText = sText & vbCrLf &  For window:   & Hex$ (mhs.hwnd)

sText = sText & vbCrLf &  X:   & mhs.pt.x &  Y:   & mhs.pt.y

sText = sText & vbCrLf &  Hit test:   & GetConstant(mhs.wHitTestCode)

frmMain.txtMsg.Text = sText

 Forward to next hook

 
Page 330
   
  Example 19-1. SetWindowsHookEx (continued)  
   
  MouseProc = CallNextHookEx(ghHook, ncode, wParam, lParam)

End Function

-----

Function GetConstant (vValue As Variant) As String

 Function returns name of constant for given value.

Dim sName As String

Select Case vValue

   Case 18

      sName =  HTBORDER

   Case 15

     sName =  HTBOTTOM

   Case 16

      sName =  HTBOTTOMLEFT

   Case 17

      sName =  HTBOTTOMRIGHT

   Case 2

      sName =  HTCAPTION

   Case 1

      sName =  HTCLIENT

   Case -2

      sName =  HTEFROR

   Case 4

      sName =  HTGROWBOX

   Case 6

      sName =  HTHSCROLL

   Case 10

      sName =  HTLEFT

   Case 9

      sName =  HTMAXBUTTON

   Case 5

      sName =  HTMENU

   Case 8

      sName =  HTMINBUTTON

   Case 0

      sName =  HTNOWHERE

   Case 11

      sName =  HTRIGHT

   Case 3

      sName =  HTSYSMENU

   Case 12

      sName =  HTTOP

   Case 13

      sName =  HTTOPLEFT

   Case 14

      sName =  HTTOPRIGHT

   Case -1

 
Page 331
   
  Example 19-1. SetWindowsHookEx (continued)  
   
        sName =  HTTRANSPARENT

   Case 7

      sName =  HTVSCROLL

End Select

GetConstant = sName

End Function

 
   
  Note that lParam points to a MOUSEHOOKSTRUCT structure variable that contains message data. The dwHitTestCode data is interesting, since it describes where the mouse pointer is when the message is generated: on a window border, on a scrollbar, on a menu, and so on. For more on these values, see the MSDN documentation for the message WM_NCHITTEST.  
 
  Example: A Global Hook  
   
  As discussed earlier, to set a global hook, we need to make the hook procedure accessible to all threads, which means placing the hook procedure in a DLL. Since VB cannot produce traditional DLLs, we need to use another programming environment, such as VC++.  
   
  The rpiGlobalHook application consists of two parts. The global hook is actually implemented in a pair of modules a form module called frmRpiHook and a standard module called basRpiHook. The form frmRpiHook is shown in Figure 19-3. This form is intended to be loaded but remain hidden. Thus, it functions somewhat like a class module, but also provides a window (the form itself) that can be subclassed.  
   
   
   
  Figure 19-3.

The global hook form

 
   
  These two modules can be added to any VB project in order to implement a global mouse hook. The rpiGlobalHook application also contains this installing project, in the form of the frmMain form shown in Figure 19-4 and the basMain standard module.  
   
  Remember that our goal here is to demonstrate the technique of creating a global hook, not to produce a commercial-quality product. Such a product could be created as an ActiveX control. Also, we have not included any error checking in this simple example.  
Page 332
   
   
   
  Figure 19-4.

A global mouse hook example

 
   
  Figure 19-5 shows the entire project.  
   
   
   
  Figure 19-5.

The global hook project

 
   
  Here are the steps shown in Figure 19-5:  
   
  1. When the user clicks the Enable Hook button, the installing project loads the form frmRpiHook shown in Figure 19-3.  
   
  2. The Load event of frmRpiHook subclasses itself by calling SetWindowLong and passing it the address of the window procedure in basRpiHook.  
   
  3. The installing program then calls the SetGlobalHook procedure, which is implemented in the frmRpiHook form.  
   
  4. This function calls the DLL function rpiSetGlobalMouseHook,  
   
  passing it the handle of the form (its own handle) and the message IDs of the  
   
  messages to process. Note that you must put this DLL in your Windows System  
   
  directory or change the Lib clause in the appropriate declarations  
   
  in the source code before this program will work.  
Page 333
   
  5. The DLL function rpiSetGloablMouseHook sets up the global mouse hook by calling SetWindowsHookEx, thus setting up the hook's mouse procedure in the DLL.  
   
  6. When a mouse message is generated, the system injects the DLL into the target process space, and the message is passed to the mouse procedure in the target process. If the message ID matches the lMsgID argument, the mouse procedure sends a WM_COPYDATA message to the window procedure of the subclassed form frmRpiHook.  
   
  7. The window procedure calls the function rpiMouseHookProc, which is defined by the user in the installing project.  
   
  Note that steps 1 5 (and step 7) take place in the installing application's process, and Step 6 takes place in the process that is the target of the mouse message, but the WM_COPYDATA message is marshalled from the target process to the installing process.  
   
  Now let us look at some of the actual code.  
   
  frmRpiHook  
   
  The code behind the form frmRpiHook is:  
 
  Option Explicit

Private mhHook As Long

Private mhPrevWndProc As Long

' -----

Public Property Get hHook() As Long

   hHook = mhHook

End Property

' -----

Public Property Get hPrevWndProc() As Long

   hPrevWndProc = mhPrevWndProc

End Property

' -----

Public Function SetGlobalHook (ByVal lMsgID As Long) As Long

' Call DLL to set hHook property

' Returns handle to hook (0 indicates error)

mhHook = rpiSetGlobalMouseHook(Me.hwnd, lMsgID)

SetGlobalHook = mhHook

End Function

' -----

Public Function FreeGlobalHook() As Boolean

   FreeGlobalHook = (rpiFreeGlobalMouseHook <> 0)

End Function

 

 

Page 334
 
  ' -----

Private Sub Form_Load()

' Subclass the form

mhPrevWndProc = SetWindowLong(Me.hwnd, GWL_WNDPROC, AddressOf WindowProc)

End Sub

' -----

Private Sub Form_Unload(Cancel As Integer)

' Remove subclassing

SetWindowLong Me.hwnd, GWL_WNDPROC, mhPrevWndProc

' When unloading form, check for a valid hook and free it

If hHook <> 0 Then FreeGlobalHook

Set frmRpiHook = Nothing

End Sub

 
   
  The form has two properties:  
 
  hHook

The handle of the hook (if set). Read-only.

 
 
  hPrevWndProc

Handle to form's original window procedure. Read-only.

 
   
  And two methods:  
 
  SetGlobalHook

Calls DLL to set hook for messages.

 
 
  FreeGlobalHook

Calls DLL to free hook.

 
   
  The SetGlobalHook method is:  
 
  Public Function SetGlobalHook (ByVal lMsgID As Long) As Long

' Call DLL to set hHook property

' Returns handle to hook (0 indicates error)

mhHook = rpiSetGlobalMouseHook(Me.hwnd, lMsgID)

SetGlobalHook = mhHook

End Function

 
   
  This method calls the rpiSetGlobalMouseHook function in our DLL, passing it the handle of the subclassed form (this form) and the message IDs of the messages that we want to process. Similarly, the FreeGlobalHook method calls the rpiFreeGlobalMouseHook DLL function. We will take a look at these DLL functions soon.  
   
  basRpiHook  
   
  The accompanying standard module basRpiHook contains the required declarations, along with the window procedure for the subclassed form. It is necessary to put the window procedure in a standard module because the VB AddressOf  
Page 335
   
  operator requires its argument to be in a standard module. If it weren't for this, we could place the entire project in the form module. What a shame.  
   
  Here is the code for the window procedure:  
 
  Public Function WindowProc(ByVal hwnd As Long, ByVal iMsg As Long, _

   ByVal wParam As Long, ByVal lParam As Long) As Long

' Window procedure for subclassed form.

If iMsg = WM_COPYDATA Then

   ' lParam has pointer to COPYDATASTRUCT structure.

   ' Get it out immediately

   ' First copy COPYDATASTRUCT

   CopyMemory cds.dwData, ByVal lParam, LenB(cds)

   ' Then copy MOUSEHOOKSTRUCT

   CopyMemory mhs.pt.x, ByVal cds.lpData, LenB(mhs)

   ' Call user-implemented mouse hook procedure

   rpiMouseHookProc mhs.hwnd, cds.dwData, mhs.pt.x, _

      mhs.pt.y, mhs.wHitTestCode, mhs.dwExtraInfo

   ' Do not pass message this on to form

   Exit Function

End If

' Call original window procedure

WindowProc = CallWindowProc(frmRpiHook.hPrevWndProc, _

   hwnd, iMsg, wParam, lParam)

End Function

 
   
  When the global mouse hook is set, all mouse messages are observed by our DLL. For each message, the DLL sends a WM_COPYDATA message to the window procedure WindowProc, which processes the incoming message, calls the user-implemented rpiMouseHookProc (in the installing project), and kills this message. All other messages are passed on to the default window procedure for the form.  
   
  The data in the parameters of WindowProc is as follows:  
 
  hwnd

The handle of the subclassed form. This is not of much use to us.

 
 
  iMsg

The message number, which is always WM_COPYDATA

 
 
  wParam

The handle of the sending window, which is always 0 because there is no sending window (the DLL does the sending).

 
Page 336
 
  lParam

A pointer to a variable of type COPYDATASTRUCT, marshalled from the target process by the call to SendMessage using WM_COPYDATA

 
   
  The COPYDATASTRUCT is:  
 
  Type COPYDATASTRUCT

   dwData As Long   ' message ID of the hooked mouse message

   cbData As Long   ' count of bytes pointed to by lpData--equal to 20

   lpData As Long   ' pointer to variable of type MOUSEHOOKSTRUCT

End Type

 
   
  The MOUSEHOOKSTRUCT structure is:  
 
  Type MOUSEHOOKSTRUCT

   pt As POINTAPI           ' POINT structure with mouse coordinates

   hwnd As Long             ' handle of the window receiving the mouse message

   wHitTestCode As Long     ' describes location of mouse

   dwExtraInfo As Long     ' extra info with message, normally 0

End Type

 
   
  Finally, the DLL expects the user-implemented mouse hook procedure to have the form:  
 
  Public Sub rpiMouseHookProc(ByVal hwnd As Long, _

   ByVal iMsg As Long, _

   ByVal x As Long, _

   ByVal y As Long, _

   ByVal wHitTestCode As Long, _

   ByVal dwExtraInfo As Long)

 
   
  rpiGlobalHook.dll  
   
  Now for the DLL, which is written in VC++. Fortunately, it is not complicated. The source code is also on the accompanying CD.  
   
  First, here is the rpiSetGlobalHook procedure, which essentially just calls SetWindowHookEx. It also saves the handle to the hook, the handle to the subclassed form, and the message IDs to process in shared variables; that is, variables that are shared by all processes that load the DLL.  
 
  int WINAPI rpiSetGlobalMouseHook(int hSubclassedForm, int iMsgID)

{

   // Sets a mouse hook and returns its handle, or 0 for failure.

   // Input is handle to subclassed form and message ID's to hook.

   // Sets the shared variable ghSubclassedForm containing handle

   // of subclassed form, the msgID and the ghHook variable.

   // If hook already set, do not set it again

   if (!ghHook == 0)

      return 0;

   ghHook = SetWindowsHookEx(

      WH_MOUSE,

 

 

Page 337
 
        (HOOKPROC)MouseProc,

      hDLLInst,

      0);

      // Save subclassed form handle

      ghSubclassedForm = (HANDLE)hSubclassedForm;

      // Save the message ID(s)

      lMsgID = iMsgID;

      return (int)ghHook;

}

 
   
  The rpiFreeGlobalMouseHook is even simpler:  
 
  int WINAPI rpiFreeGlobalMouseHook()

{

   HRESULT hr;

   // Free hook hh. Return 0 on failure.

   hr = UnhookWindowsHookEx(ghHook);

   ghHook = 0;

   return hr;

}

 
   
  Finally, we come to the mouse procedure for the hook. As mentioned earlier, this procedure gathers up the mouse message data and sends it off to the subclassed form in the VB project using a WM_COPYDATA message, which is automatically marshalled by Windows. It then calls the next hook procedure in the hook chain.  
 
  LRESULT WINAPI MouseProc(int nCode, WPARAM wParam, LPARAM lParam)

{

   // If nCode<0 then do not process the message

   if (nCode < 0)

        return CallNextHookEx(ghHook, nCode, wParam, lParam);

   // Check for message ID filter

   if ( (lMsgID == 0)    (lMsgID & wParam) ) //   is logical OR, & is bitwise AND

{

      // Set up data to copy in message

      copydata.dwData = wParam;      //message ID here

      copydata.lpData = (MOUSEHOOKSTRUCT*) lParam;

      copydata.cbData = sizeof(MOUSEHOOKSTRUCT);

      // Send message

      if (!ghSubclassedForm == 0) {

      SendMessage(ghSubclassedForm, WM_COPYDATA,

         NULL, (LPARAM) &(copydata.dwData) );

      }

   }

   return CallNextHookEx(ghHook, nCode, wParam,lParam);

}

 
Page 338
   
  The Installing Application  
   
  The code for the installing process's form, that is, the code behind the form in Figure 19-3, is:  
 
  Option Explicit

Dim hMouseHook As Long

Const WM_LBUTTONDOWN = &H201

Const WM_RBUTTONDOWN = &H204

' -----

Private Sub cmdEnable_Click()

' Load hook form but no need to show

Load frmRpiHook

' Set hook

" frmRpiHook.SetGlobalHook WM_LBUTTONDOWN + WM_RBUTTONDOWN

' For all mouse messages

frmRpiHook.SetGlobalHook 0

' Save handle to hook for later freeing

hMouseHook = frmRpiHook.hHook

lblIsHooked = "Hooked"

lblIsHooked.ForeColor = &HFF

End Sub

' -----

Private Sub cmdDisable_Click()

   frmRpiHook.FreeGlobalHook

   hMouseHook = 0

   lblIsHooked = "Not Hooked"

   lblIsHooked.ForeColor = &H0

End Sub

' -----

Private Sub cmdExit_Click()

   If hMouseHook <> 0 Then frmRpiHook.FreeGlobalHook

   Unload Me

End Sub

' -----

Private Sub Form_Unload(Cancel As Integer)

   ' This will remove subclassing and free hook

   Unload frmRpiHook

End Sub

 
   
  Finally, the user code for processing the mouse messages is in a standard module for the installing application. This procedure processes the mouse information in the same manner as that of the local hook example:  
Page 339
 
  Public Sub rpiMouseHookProc(ByVal hwnd As Long, ByVal iMsg As Long, _

   ByVal x As Long, ByVal y As Long, ByVal wHitTestCode As Long, _

   ByVal dwExtraInfo As Long)

Dim sText As String

' Fill text box with message data

sText = "MsgID: " & iMsg

sText = sText & vbCrLf & "For window: " & Hex$ (hwnd)

sText = sText & vbCrLf & "X: " & X & "Y: " & y

sText = sText & vbCrLf & "Hit test: " & GetConstant(wHitTestCode)

frmMain.txtMsg.Text = sText

frmMain.txtMsg = sText

End Sub

 
   
  Unlike the local hook example, we can now move the mouse over any window in the system and hook the generated mouse messages.

Категории