HookMe

Description

This is code that goes into, well, probably every project I ever write. [EDIT: Scratch that. Found a new way that I'd highly recommend. This one's still interesting, for a number of reasons, so I'll leave it here. But for any new projects, or even if you're revisiting an old one, take a look at my HookXP sample.] I was once kidded by the author of MsgHook that I virtually made a living explaining to people how to use that tool. Yeah, I wrote a lot of columns over the years, some listed below, that involved subclassing. But since VB5 shipped, production code omitted the OCXs, and stuck to pure inline message hooking.

Originally, I used a single BAS file for all subclassing, but found that solution to be wanting. In particular, it meant I had to modify the code module for each application. And, it prevented sinking the messages in more than a single object type. With my latest approach, I think you'll agree, we've come to be about as flexible as possible.

HookMe now includes an IHookSink interface that allows you to redirect window messages back into any object you desire. Your forms can sink their messages within themselves, or delegate message handling to companion or even global classes. Here's the entire IHookSink interface:

Public Function WindowProc( _
   hWnd As Long, msg As Long, wp As Long, lp As Long) As Long
   ' Stub to be used with MHookMe.bas
End Function

In your class, control, or form module, you simply Implement IHookSink, then provide code for the WindowProc method. As a very simple example, to watch the message stream for every textbox on a form, and "eat" any WM_PASTE messages that happen to not meet some arbitrary criteria (say, evaluate to positive values), you could use code like this:

Color-coded with vbMarkUp - try it today!
Option Explicit

Private Const WM_PASTE As Long = &H302

Implements IHookSink

Private Sub Form_Load()
   Dim i As Long
   For i = Text1.LBound To Text1.UBound
      Call HookWindow(Text1(i).hWnd, Me)
   Next i
End Sub

Private Sub Form_Unload(Cancel As Integer)
   Dim i As Long
   For i = Text1.LBound To Text1.UBound
      Call UnhookWindow(Text1(i).hWnd)
   Next i
End Sub

Private Function IHookSink_WindowProc(hWnd As Long, msg As Long, wp As Long, lp As Long) As Long
   Dim buffer As String
   Dim nVal As Long
   Dim nRet As Long

   Select Case msg
      Case WM_PASTE
         ' Validate clipboard contents.
         If Clipboard.GetFormat(vbCFText) Then
            buffer = Clipboard.GetText
            On Error Resume Next
               nVal = CLng(buffer)
            On Error GoTo 0
            ' Only allow >0 values to be pasted.
            If nVal > 0 Then
               nRet = InvokeWindowProc(hWnd, msg, wp, lp)
            Else
               nRet = 0  ' eat it!
            End If
         End If

      Case Else
         nRet = InvokeWindowProc(hWnd, msg, wp, lp)
   End Select
   ' Return results.
   IHookSink_WindowProc = nRet
End Function

Note the second parameter to the HookWindow procedure. Here, we used Me, to route window messages back into the current form. But that parameter could be any object that implements the IHookSink interface.

But Is It Safe?

It is, if you save. Seriously, running message hooks within the IDE is just downright inherently problematic if you enter Break Mode. Some people use special purpose DLLs to protect them from this situation. I prefer to just be careful. Often, I test the compiled state of an application before calling HookWindow, especially after I have fully debugged the WindowProc code. I'd advise the same precaution.

Published

This sample, or the one from which it originally derived, was published (or at least peripherally mentioned) in the following article(s):

APIs Usage

This sample uses the following API calls:

Module Library Function
CSizeGrip.cls kernel32
user32








RtlMoveMemory
DrawFrameControl
GetClientRect
GetDC
GetSystemMetrics
GetWindowRect
InvalidateRect
IsWindow
PostMessage
PtInRect
ReleaseDC
MHookMe.bas kernel32
user32



RtlMoveMemory
CallWindowProc
GetProp
GetWindowLong
SetProp
SetWindowLong
MWindowStyles.bas user32

GetWindowLong
SetWindowLong
SetWindowPos

Don't see what you're looking for? Here's a complete API cross-reference.

Download

Download HookMe.zip   Please, enjoy and learn from this sample. Include its code within your own projects, if you wish. But, in order to insure only the most recent code is available to all, I ask that you don't share the sample by any form of mass distribution.

Download HookMe.zip, 15Kb, Last Updated: Thursday, May 12, 2005

See Also

The following resources may also be of interest: