Trao đổi với tôi

http://www.buidao.com

4/2/10

[Programming] Tổng Quan Về Kỹ Thuật Subclass

Hệ điều hành Windows điều khiển các ứng dụng bằng cách gửi các Message đến các Window của các ứng dụng. Những Message này thông báo đến các Window, cho chúng biết khi nào có cú nhắp chuột…và tất cả các thông tin cần thiết khác để Window ứng xử chính xác. Theo cách này, một ứng dụng Windows tối thiểu phải chứa một hàm để xử lí các Message này (hàm WindowProc). Hàm này được khai báo với hệ thống khi Window được tạo để Windows có thể biết sẽ gửi Message đó cho ai, và không bao giờ nhầm lẫn.

Ở đây chúng tôi xin lưu ý để các bạn không bị nhầm lẫn. Thông thường chúng ta quan niệm một Window là một cửa sổ chương trình. Nhưng theo cái nhìn của HĐH Windows thì một Window có thể là một Form, có thể là một TextBox và cũng có thể là một Button… Vì thế nên từ Window mà chúng tôi dùng ở đây là chỉ tất cả các Control chứ không phải là một Form như chúng ta đã thường nghĩ.

Trở lại vấn đề, trong lập trình Windows, Hook(hay còn gọi là Subclass) là kĩ thuật mà trong đó chúng ta sẽ chặn những sự kiện (các message, các cú nhấn chuột, các cú gõ phím) trước khi chúng đến được ứng dụng. Hàm này có thể làm việc trên các sự kiện, hoặc trong một số trường hợp có thể thay đổi hoặc vô hiệu chúng. Hàm chuyên nhận các sự kiện gọi là filter functions (chúng tôi tạm dịch là hàm xử lý Message) và được phân loại tùy theo sự kiện mà nó nhận xử lý.

Kĩ thuật Hook cung cấp một khả năng lập trình rất mạnh, vượt ra ngoài những gì mà môi trường lập trình cung cấp cho chúng ta. Sử dụng kĩ thuật Hook chúng ta có thể xử lí, thay đổi tất cả các Message cho tất cả các control và Dialog Box hoặc Menu của ứng dụng và của hệ thống. Ta cũng có thể chặn bất kì một Message nào khi hàm SendMessage được gọi. Nói chung với kỹ thuật này chúng ta có thể biết được những gì đang xảy ra và thực sự làm chủ chương trình.

Cách SubClass

1. Tạo một Project mới với tên mặc định là Form1.
2. Thêm vào hai nút bấm (Command Button) với tên là Command1 và Command2.
3. Thêm đoạn Code sau vào một Module :

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

Declare Function SetWindowLong Lib "user32" Alias _
"SetWindowLongA" (ByVal hwnd As Long, _
ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Public Const GWL_WNDPROC = -4
Public Const WM_LBUTTONDOWN = &H201
Public IsHooked As Boolean
Global lpPrevWndProc As Long
Global gHW As Long

Public Sub Hook()
If IsHooked Then
MsgBox "Dung hook hai lan mà khong unhook " & _
"neu khong ban se khong the hook."
Else
lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, _
AddressOf WindowProc)
IsHooked = True
End If
End Sub

Public Sub Unhook()
Dim temp As Long
temp = SetWindowLong(gHW, GWL_WNDPROC,lpPrevWndProc)
IsHooked = False
End Sub

Function WindowProc(ByVal hw As Long, ByVal uMsg As _
Long, ByVal wParam As Long, ByVal lParam As Long) As Long
If uMsg = WM_LBUTTONDOWN Then
MsgBox "Hook Test"
Else
WindowProc = CallWindowProc(lpPrevWndProc, hw, _
uMsg, wParam, lParam)
End If
End Function


3. Thêm vào Form đoạn code sau:

Private Sub Form_Load()
gHW = Me.hwnd
Command1.Caption = "Hook"
Command2.Caption = "Unhook"
End Sub

Private Sub Command1_Click()
Hook
End Sub

Private Sub Command2_Click()
Unhook
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode _
As Integer)
If IsHooked Then
Cancel = 1
MsgBox "Unhook truoc khi dong, neu khong VB se Crash, va moi thu ban lam se mat."
End If
End Sub

4. Chạy ứng dụng, nhấn vào nút Hook. Sau đó bấm chuột trái lên Form, một Message box sẽ xuất hiện. Nếu không có lỗi gì thì bạn đã thành công rồi đấy.

Phân tích:
Bạn đừng xem thường ví dụ vừa rồi. Nó có vẻ hơi tầm thường (xét về tác dụng) nhưng lại chứa đựng trong đó rất nhiều thứ. Sau đây tôi sẽ phân tích tỉ mỉ từng bước cho các bạn.
Đầu tiên có một điều cần nhớ là phải bấm vào nút Unhook trước khi đóng ứng dụng, đây là điều quan trọng vì nếu không VB sẽ đỗ vỡ, và nếu bạn quên chưa lưu thì mọi công lao của bạn sẽ đi xuống biển. Vì thế hãy cẩn thận lưu trước khi chạy thử vì không phải lúc nào bạn cũng làm đúng đâu. Một điều hay nữa là hàm của bạn sẽ xử lí trước tất cả các sự kiện của VB. Ví dụ như nếu bây giờ bạn thêm vào sự kiện Form_MouseClick() một công việc gì đó thì nó sẽ không có tác dụng (trừ phi bạn nhấp chuột trái). Bạn có thể tự thử lấy.
Để có thể Hook được một Windows bạn cần phải biết một thứ rất quan trọng đó là Handle của Window cần Hook. Đây là một số nguyên đặc trưng cho mỗi Window, bạn không thể tạo ra nó nhưng có thể truy cập nó bằng thuộc tính hWnd (Ví dụ: Form1.hWnd, Text1.hWnd...). Bây giờ chúng ta trở lại với ví dụ trên. Công việc đầu tiên của kĩ thuật Hook là phải báo với Window hàm xử lý Message của bạn nằm ở đâu trong bộ nhớ để Windows sẽ truyền tham số vào đó khi cần thiết. Chúng ta sử dụng hàm SetWindowLong để làm việc này.

lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, AddressOf WindowProc)


Hàm SetWindowLong có nhiều chức năng khác, trong trường hợp này nó sẽ thay đổi hàm xử lí Message bằng hàm WindowProc do ta định nghĩa. Sau đó nó sẽ lưu lại điạc chỉ trong bộ nhớ của hàm xử lý Message cũ vào biến lpPrevWndProc chúng ta sẽ cần dùng lại nó sau này.
Sau khi đã khai báo xong với Windows chúng ta hoàn có thể tiến hành kĩ thuật Hook. Bây giờ chúng ta hãy xem lại cái quan trọng nhất của chương trình - hàm xử lí Message :

Function WindowProc(ByVal hw As Long, ByVal uMsg As _
Long, ByVal wParam As Long, ByVal lParam As Long) As Long
If uMsg = WM_LBUTTONDOWN Then
MsgBox "Hook Test"
Else
WindowProc = CallWindowProc(lpPrevWndProc, hw, _
uMsg, wParam, lParam)
End If
End Function

Khi hàm này được chạy, nó sẽ có 4 đối số, 4 đối số này sẽ được Windows truyền cho hàm mỗi khi có một sự kiện, đây là ý nghĩa của các đối số :
*hw chính là Handle của Window nhận Message (trong ví dụ này chính là Handle của Form).
*uMsg là Message mà Windows gửi đến cho hàm (bạn sẽ chặn những Message này).
*Còn wParam và lParam là các thông tin đi kèm theo Message, các thông tin này đôi khi cũng rất quan trọng nhưng trong trường hợp này ta chưa cần đến chúng. Chúng ta sẽ gặp lại chúng trong các ví dụ sau.
Trong đó đối số quan trọng nhất là uMsg. Nó là những Message do HĐH Windows gửi đến, và chính là những gì mà chúng ta sẽ chặn lại. Trong ví dụ trên chúng ta đã chặn Message WM_LBUTTONDOWN lại để xử lí. Các Message được gửi đến bằng những con số, chúng được lưu trong các hằng, có thể tìm thấy các hằng này trong thư viện MSDN hoặc API Text-Viewer. Nếu không có Message mà ta muốn chặn thì ta phải trả quyền hoạt động lại cho hàm xử lí Message cũ, đây là điều rất quan trọng và bạn không được phép quên, nếu không chương trình của bạn sẽ chẳng đáp ứng gì đâu. Để trả lại các Message không cần thiết cho hàm xử lí cũ ta sử dụng hàm CallWindowProc, hàm này sẽ gửi trả lại những đối số do Windows gửi đến lại cho hàm xử lý Message cũ, lpPrevWndProc là địa chỉ của hàm xử lý Message cũ trong bộ nhớ mà chúng ta đã lưu lại từ trước :

WindowProc = CallWindowProc(lpPrevWndProc, hw, uMsg, wParam, lParam)

Bạn đã thấy vì sao chúng tai phải lưu lại địa chỉ trong bộ nhớ của hàm xử lý Message cũ rồi chứ (lpPrevWndProc). Đấy là những công việc mang tính thủ tục mà ta cần phải làm khi áp dụng kĩ thuật Subclass.
Note: Trong ví dụ trên bạn phải bấm vào nút Hook để thực sự bắt đầu Hook, và nhớ Unhook trước khi kết thúc chương trình. Nếu bạn đang vò đầu bức tai không hiểu tại sao nó không chạy thì đây là lời giải đáp cho bạn.

reflink: http://www.ddth.com/showthread.php?t=14450