从以往子类化跟踪MouseLeave深入讨论VB6的自定义Hook类

一、关于起因

之前发过一篇博文,是关于VB6中跟踪鼠标移出事件的示例(http://www.cnblogs.com/alexywt/p/5891827.html)

随着业务状况的不断发展,提出了更多的挑战和问题.

其一:子类化在VB6的IDE调试阶段会出现崩溃情况,需要实现子类化的无崩溃调试;

其一:我的窗体或自定义控件中可能有很多控件需要跟踪鼠标移出事件;甚至可能会通过代码来动态添加控件,要监听移出事件,通常是用WithEvents,但VB6的该关键字不支持数组对象的事件跟踪.也就是说要找到一种方式来批量处理大量控件的鼠标移出事件.

二、修改CHooker类使子类化在IDE调试时不崩溃

我对原来的Hooker类进行了改进,使其能适应调试模式,而不至于造成IDE崩溃:废话不多说,直接上修改过之后的类模块代码如下,其中关键部分在GetWndProcAddress过程中对LogMode分类进行处理,该过程的代码源自"嗷嗷叫的老马"http://www.cnblogs.com/pctgl/articles/1586841.html

  1 Option Explicit
  2
  3 Private Const WM_MOUSELEAVE = &H2A3&
  4 Private Const WM_MOUSEMOVE = &H200
  5 Private Const TME_LEAVE = &H2&
  6
  7 Private Type TRACKMOUSEEVENTTYPE
  8     cbSize As Long
  9     dwFlags As Long
 10     hwndTrack As Long
 11     dwHoverTime As Long
 12 End Type
 13
 14 Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, Optional ByVal Length As Long = 4)
 15 Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
 16 Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
 17 Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
 18 Private Declare Function VirtualProtect Lib "kernel32" (lpAddress As Any, ByVal dwSize As Long, ByVal flNewProtect As Long, lpflOldProtect As Long) As Long
 19 Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
 20 Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
 21 Private Declare Function GetProcessHeap Lib "kernel32" () As Long
 22 Private Declare Function HeapAlloc Lib "kernel32" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal dwBytes As Long) As Long
 23 Private Declare Function HeapFree Lib "kernel32" (ByVal hHeap As Long, ByVal dwFlags As Long, ByVal lpMem As Long) As Long
 24 Private Declare Function TrackMouseEvent Lib "user32" (lpEventTrack As TRACKMOUSEEVENTTYPE) As Long
 25
 26 Private Type ThisClassSet
 27     s_srcWndProcAddress     As Long
 28     s_Hwnd                  As Long
 29     s_BlockProtect          As Long
 30     n_heapAlloc             As Long
 31 End Type
 32
 33 Private LinkProc()              As Long
 34 Private PG                      As ThisClassSet
 35 Private mMouseLeaveTracking     As Boolean
 36 Private mHookObject             As Object
 37
 38 Public Event GetWindowMessage(Result As Long, ByVal cHwnd As Long, ByVal Message As Long, ByVal wParam As Long, ByVal lParam As Long)
 39 Public Event MouseLeave()
 40
 41 Private Sub HookProc(Result As Long, ByVal cHwnd As Long, ByVal Message As Long, ByVal wParam As Long, ByVal lParam As Long)
 42     Dim tTrackML As TRACKMOUSEEVENTTYPE ‘一个移开事件结构声明
 43     ‘子类化接口过程
 44     RaiseEvent GetWindowMessage(Result, cHwnd, Message, wParam, lParam)
 45     Select Case Message
 46     Case WM_MOUSEMOVE
 47         If Not mMouseLeaveTracking Then
 48             mMouseLeaveTracking = True
 49             ‘initialize structure
 50             tTrackML.cbSize = Len(tTrackML)
 51             tTrackML.hwndTrack = cHwnd
 52             tTrackML.dwFlags = TME_LEAVE
 53             ‘start the tracking
 54             TrackMouseEvent tTrackML
 55         End If
 56     Case WM_MOUSELEAVE
 57         RaiseEvent MouseLeave
 58         mMouseLeaveTracking = False
 59     End Select
 60     Result = CallWindowProc(PG.s_srcWndProcAddress, ByVal cHwnd&, ByVal Message&, ByVal wParam&, ByVal lParam&)
 61 End Sub
 62
 63 Private Function GetWndProcAddress(ByVal OrgWindowProc As Long, ByVal SinceCount As Long) As Long
 64 ‘   地址指针 = GetWndProcAddress( 取第 N 个公共函数(属性)  =或= 所有公共函数个数 + 第 N 个私有函数的函数地址)
 65     Dim mePtr As Long
 66     Dim jmpAddress As Long
 67     Dim i As Long
 68
 69     mePtr = ObjPtr(Me)
 70     CopyMemory jmpAddress, ByVal mePtr, 4
 71     CopyMemory jmpAddress, ByVal jmpAddress + (SinceCount - 1) * 4 + &H1C, 4
 72
 73     If App.LogMode = 0 Then
 74
 75         ReDim LinkProc(15) As Long
 76
 77         LinkProc(0) = &H83EC8B55
 78         LinkProc(1) = &H75FFFCC4
 79         LinkProc(2) = &H1075FF14
 80         LinkProc(3) = &HFF0C75FF
 81         LinkProc(4) = &HB90875
 82         LinkProc(5) = &HFF000010
 83         LinkProc(6) = &H1F883D1
 84         LinkProc(7) = &H4D8D1575
 85         LinkProc(8) = &H6851FC
 86         LinkProc(9) = &HB8000020
 87         LinkProc(10) = &H3000
 88         LinkProc(11) = &H458BD0FF
 89         LinkProc(12) = &HB807EBFC
 90         LinkProc(13) = &H4000
 91         LinkProc(14) = &HC2C9D0FF
 92         LinkProc(15) = &H10
 93
 94         CopyMemory ByVal VarPtr(LinkProc(4)) + 3, GetProcAddress(GetModuleHandle("vba6.dll"), "EbMode"), 4&             ‘ Label Sign: 0100000
 95         CopyMemory ByVal VarPtr(LinkProc(8)) + 3, ObjPtr(Me), 4&             ‘ Label Sign: 0200000
 96         LinkProc(10) = jmpAddress                                               ‘ Label Sign: 0300000
 97         LinkProc(13) = PG.s_srcWndProcAddress                                                ‘ Label Sign: 0400000
 98
 99         PG.n_heapAlloc = HeapAlloc(GetProcessHeap, &H8, 64&)
100         CopyMemory ByVal PG.n_heapAlloc, LinkProc(0), 64&
101         VirtualProtect ByVal PG.n_heapAlloc, ByVal 64&, ByVal &H40&, PG.s_BlockProtect
102         GetWndProcAddress = PG.n_heapAlloc
103
104     Else
105         ReDim LinkProc(10)
106         LinkProc(0) = &H83EC8B55
107         LinkProc(1) = &H75FFFCC4
108         LinkProc(2) = &H1075FF14
109         LinkProc(3) = &HFF0C75FF
110         LinkProc(4) = &H458D0875
111         LinkProc(5) = &H6850FC
112         LinkProc(6) = &HB8000010
113         LinkProc(7) = &H2000
114         LinkProc(8) = &H458BD0FF
115         LinkProc(9) = &H10C2C9FC
116
117         CopyMemory ByVal VarPtr(LinkProc(5)) + 3, ObjPtr(Me), 4&             ‘ Label Sign: 0100000
118         LinkProc(7) = jmpAddress                                               ‘ Label Sign: 0200000
119         VirtualProtect ByVal VarPtr(LinkProc(0)), ByVal 40&, ByVal &H40&, PG.s_BlockProtect
120         GetWndProcAddress = VarPtr(LinkProc(0))
121
122     End If
123
124 End Function
125
126 Public Function StartHook(HookObject As Object) As Long
127     Dim cHwnd As Long
128     Set mHookObject = HookObject
129     cHwnd = HookObject.hWnd
130     ‘设置指定窗口的子类化
131     PG.s_Hwnd = cHwnd
132     PG.s_srcWndProcAddress = GetWindowLong(cHwnd, ByVal -4&)
133     SetWindowLong ByVal cHwnd, ByVal -4&, ByVal GetWndProcAddress(PG.s_srcWndProcAddress, 4)
134     StartHook = PG.s_srcWndProcAddress
135 End Function
136
137 Public Property Get HookObject() As Object
138     Set HookObject = mHookObject
139 End Property
140
141 Public Sub UnHook()
142     ‘取消窗口子类化
143     SetWindowLong ByVal PG.s_Hwnd&, ByVal -4&, ByVal PG.s_srcWndProcAddress&
144     Set mHookObject = Nothing
145 End Sub
146
147 Private Sub Class_Terminate()
148     If PG.n_heapAlloc Then
149         UnHook
150         VirtualProtect ByVal PG.n_heapAlloc, ByVal 64&, ByVal PG.s_BlockProtect, PG.s_BlockProtect
151         HeapFree GetProcessHeap, ByVal 0&, PG.n_heapAlloc
152         PG.n_heapAlloc = 0
153     End If
154 End Sub

三、实现批量跟踪一个窗体上多个控件的某事件

实现批量跟踪事件的思路是:在窗体上建立一个函数,来处理多个控件的指定事件

那么问题来了,我们子类化只能针对一个控件实施,假如我们直接在窗体上一个一个控件去子类化并写事件处理函数的话,也就背离了我的目的,因为这样的话有多少个控件就有很多个事件处理函数要写,我们的目的是只要一个函数来处理.

解决的方案是再建立一个包装器类,该类要完成2个事情,其一将需子类化的对象完成子类化,其二将调用的那个窗体中的事件处理函数与子类化关联,使之子类化回调函数即为该窗体中的自定义函数.

那么问题又来了,包装器类的第一个功能很容易实现,我们可以直接在该包装器类内实例化一个CHooker对象,然后调用其StartHook即可,那么要如何关联窗体中的自定义函数了?

因为该自定义函数在窗体中,是不可能通过AddressOf来取得其地址调用的,我们这里通过建立代理类实现

在VB.net中我们用代理用的很频繁,那么在VB6中要怎么做了?

首先我们建立一个接口类IMouseLeaveCallBack,代码如下

1 Public Sub MouseLeave(Hooker As CHooker)
2
3 End Sub

然后在建立代理类CMouseLeaveDelegate,代码如下所示,其关键是在初始化类过程中传递一个类型为IMouseLeaveCallBack的对象进去,随后在mHooker的MouseLeave事件回调中执行该IMouseLeaveCallBack对象的MouseLeave方法,并将mHooker传递给它,以便实现IMouseLeaveCallBack接口的类通过mHooker的HookObject得到发生鼠标移出事件的控件是谁.

 1 Private WithEvents mHooker As CHooker
 2 Private mDelegate As IMouseLeaveCallBack
 3
 4 Public Sub InitClass(HookedObject As Object, DelegateObject As IMouseLeaveCallBack)
 5     Set mHooker = New CHooker
 6     mHooker.StartHook HookedObject
 7     Set mDelegate = DelegateObject
 8 End Sub
 9
10 Private Sub Class_Terminate()
11     If mHooker Is Nothing Then Exit Sub
12     mHooker.UnHook
13     Set mHooker = Nothing
14 End Sub
15
16 Private Sub mHooker_MouseLeave()
17     mDelegate.MouseLeave mHooker
18 End Sub

三、在窗体中实现接口函数

通过前面的处理之后,我们就可以在窗体上实现IMouseLeaveCallBack接口,并写该接口MouseLeave过程的实现代码了.

我的示例很简单,目的是为窗体上的按钮及图片框设置鼠标移动时背景色及鼠标离开后的背景色

其中鼠标移动背景色我是一个一个控件处理,这种方式非常麻烦,如果你想一个函数搞定所有控件的鼠标移动事件,可以参考鼠标移出事件的处理方式.这里我就是用来对比批量处理与单个单个处理的应用效果.

建立一个窗体,在窗体上建立一个按钮,一个PictureBox,我就只用了2个控件,注意按钮的Style要设置成Graphical,否则设置背景色无效.

在窗体中输入如下所示代码,这里我通过一个数组保存所有需要进行Hook的控件,以便能通过一个For循环遍历实施Hook.

Implements IMouseLeaveCallBack

Private mHookedObjects(1 To 2) As Object
Private mMouseLeaveHandles(1 To 2) As CMouseLeaveDelegate

Private Sub Command1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
    Command1.BackColor = RGB(255, 0, 0)
End Sub

Private Sub Form_Load()
    Set mHookedObjects(1) = Me.Command1
    Set mHookedObjects(2) = Me.Picture1
    For i = 1 To 2
        Set mMouseLeaveHandles(i) = New CMouseLeaveDelegate
        mMouseLeaveHandles(i).InitClass mHookedObjects(i), Me
    Next
End Sub

Private Sub IMouseLeaveCallBack_MouseLeave(Hooker As CHooker)
    Hooker.HookObject.BackColor = RGB(255, 255, 0)
End Sub

Private Sub Picture1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
    Picture1.BackColor = RGB(255, 0, 0)
End Sub
时间: 2024-08-15 02:27:09

从以往子类化跟踪MouseLeave深入讨论VB6的自定义Hook类的相关文章

Win32中安全的子类化(翻译)

关于子类化的话题虽然有些旧,但它至今仍然不失为一种开发Windows的强有力技术,在MFC的内核.甚至.NET的内核中都离不开它,希望本连载能对Windows开发的爱好者有所帮助. 原文标题:Safe Subclassing in Win32作者:Kyle MarshMSDN技术组 点击此处查看原文 摘要 本文描述了Win32环境下的子类化,描述了它是如何工作的以及实现安全的子类化必须要遵循的规则.本文涵盖了实例子类化和全局子类化.而超类化则作为一个全局子类化的可选替代方案被介绍.从Win16到

C++ 中超类化和子类化

超类化和子类化没有具体的代码,其实是一种编程技巧,在MFC和WTL中可以有不同的实现方法. 窗口子类化: 原理就是改变一个已创建窗口类的窗口过程函数.通过截获已创建窗口的消息,从而实现监视或修改已创建窗口类的行为属性.可以用来改变或者扩展一个已存在的窗口的行为,而不用重新开发.比如要获得那些预定义控件窗口类(按钮控件.编辑控件.列表控件.下 拉列表控件.静态控件和滚动条控件)的功能而又要修改它们的某些行为. 子类化的优点主要体现在以下两个方面:首先,它不需要创建新的窗口类,不需要了解一个窗口的窗

眼见为实(2):介绍Windows的窗口、消息、子类化和超类化

眼见为实(2):介绍Windows的窗口.消息.子类化和超类化 这篇文章本来只是想介绍一下子类化和超类化这两个比较"生僻"的名词.为了叙述的完整性而讨论了Windows的窗口和消息,也简要讨论了进程和线程.子类化(Subclassing)和超类化(Superclassing)是伴随Windows窗口机制而产生的两个复用代码的方法.不要把"子类化.超类化"与面向对象语言中的派生类.基类混淆起来."子类化.超类化"中的"类"是指W

子类化和超类化区别(介绍Windows的窗口、消息、子类化和超类化)(转)

原文地址:http://maqianli210.blog.sohu.com/75497589.html 这篇文章本来只是想介绍一下子类化和超类化这两个比较“生僻”的名词.为了叙述的完整性而讨论了Windows的窗口和消息,也简要讨论了进程和线程.子类化(Subclassing)和超类化(Superclassing)是伴随Windows窗口机制而产生的两个复用代码的方法.不要把“子类化.超类化”与面向对象语言中的派生类.基类混淆起来.“子类化.超类化”中的“类”是指Windows的窗口类. 0 运

窗口的子类化与超类化——子类化是窗口实例级别的,超类化是在窗口类(WNDCLASS)级别的

1. 子类化 理论:子类化是这样一种技术,它允许一个应用程序截获发往另一个窗口的消息.一个应用程序通过截获属于另一个窗口的消息,从而实现增加.监视或者修改那个窗口的缺省行为.子类化是用来改变或者扩展一个已存在的窗口的行为.而不用重新开发的有效途径.想要获得那些预定义控件窗口类(按钮控件.编辑控件.列表控件.下拉列表控件.静态控件和滚动条控件)的功能而又要修改它们的某些行为的一个便利的方法就是对它们进行子类化.例如,对于一个在对话框中的多行编辑框来说,当用户按下Enter键时,对话框会关闭.通过对

C++ 中超类化和子类化常用API

在windows平台上,使用C++实现子类化和超类化常用的API并不多,由于这些API函数的详解和使用方法,网上一大把.本文仅作为笔记,简单的记录一下. 子类化:SetWindowLong,GetWindowLong,CallWindowProc,FindWindowEx 超类化:GetClassInfoEx,RegisterClassEx,UnRegisterClass 以上函数在代码中的使用见<C++ 中超类化和子类化> VC中基于SDK编程的窗口子类化 VC中基于SDK编程的窗口子类化的

MFC学习笔记之12:用窗口过程WH_CALLWNDPROC钩子+子类化实现皮肤功能

实现方法: 1.定义自己的控件类,比如CButtonEx:public CButton,实现自绘. 2.安装WH_CALLWNDPROC钩子 a.定义HHOOK全局变量 HHOOK g_WndProcHook=NULL; b.在CXXAPP ::InitInstance()中安装钩子 g_WndProcHook=SetWindowsHookEx( WH_CALLWNDPROC, (HOOKPROC)CallWndProc, NULL, GetCurrentThreadId()); c.在CXXA

WIN32窗口子类化----自定义Edit控件的右键菜单

前言 Win32应用程序中,子控件的消息都是分发到其父窗口的消息处理函数中去了,这对于我们需要自定义子控件的某些特性时时十分不方便的,还好,Windows为我们提供了控件子类化的相关接口API.核心的思想是:通过获取子控件的消息处理函数地址,设置子控件的消息处理函数到自己定义的函数里,也就是Get/SetWindowLong API的使用. 测试代码 这里是一个简单的测试程序,在控制台程序中创建一个对话框,然后对话框上有一个EDIT控件(资源编辑器里拖入的,不多说),子类化EDIT控件,右键弹出

窗口子类化

#include <windows.h>#define ID_EDIT     1LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);LRESULT CALLBACK NewWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);WNDPROC OldWndProc;static HINSTANCE hIns;char temp[505];TCHAR szAppName[]