1. Hook là gì ?
Trong Windows, khi chúng ta thực hiện các thao tác nhấp chuột, nhấn phím… thì hệ điều hành sẽ chuyển các sự kiện này thành các thông điệp (message) rồi đưa vào hàng đợi (queue) của hệ thống. Sau đó, các thông điệp được trao lại cho từng ứng dụng cụ thể để xử lý.
Hook là một cơ chế mà một ứng dụng có thể chặn các sự kiện, như các thông điệp, thao tác chuột, bàn phím. Hàm dùng để chặn một loại sự kiện riêng biệt được gọi là hook procedure/ Hook function (thủ tục hook / hàm hook) hoặc Filter Function (hàm lọc) . Mỗi khi nhận được sự kiện, hookprocedure có thể thay đổi và thậm chí hủy bỏ các sự kiện đó.
Các mô hình Hook:
- Local hook: là kỹ thuật Hook dùng để bẫy sự kiện ngay trong tiến trình cài đặt.
- Remote hook: là kỹ thuật Hook cho phép bẫy các sự kiện thuộc tiến trình của ứng dụng khác. Trong mô hình này lại tồn tại hai kiểu hook khác :
+ Thread-specific : kiểu Hook này sẽ bẫy sự kiện của một luồng cụ thể.
+ System-wide : bẫy sự kiện của tất cả các luồng trong tất cả các tiến trình đang thi hành trong hệ thống.
Hook là một kỹ thuật xử lý thông điệp rất mạnh cho phép chúng ta can thiệp sâu vào các tiến trình khác nhau, nhưng nó làm ảnh hưởng tới tốc độ của hệ thống, nhất là hook system-wide, vì tất cả các sự kiện của hệ thống sẽ được định hướng tới một hàm nào đó, rõ ràng điều này làm hệ thống chậm đi đáng kể. Vì thế ta chỉ hên hook những thông điệp thật cần thiết và kết thúc việc hook ngay khi không dùng đến nữa.
Hook là một cơ chế mà một ứng dụng có thể chặn các sự kiện, như các thông điệp, thao tác chuột, bàn phím. Hàm dùng để chặn một loại sự kiện riêng biệt được gọi là hook procedure/ Hook function (thủ tục hook / hàm hook) hoặc Filter Function (hàm lọc) . Mỗi khi nhận được sự kiện, hookprocedure có thể thay đổi và thậm chí hủy bỏ các sự kiện đó.
Các mô hình Hook:
- Local hook: là kỹ thuật Hook dùng để bẫy sự kiện ngay trong tiến trình cài đặt.
- Remote hook: là kỹ thuật Hook cho phép bẫy các sự kiện thuộc tiến trình của ứng dụng khác. Trong mô hình này lại tồn tại hai kiểu hook khác :
+ Thread-specific : kiểu Hook này sẽ bẫy sự kiện của một luồng cụ thể.
+ System-wide : bẫy sự kiện của tất cả các luồng trong tất cả các tiến trình đang thi hành trong hệ thống.
Hook là một kỹ thuật xử lý thông điệp rất mạnh cho phép chúng ta can thiệp sâu vào các tiến trình khác nhau, nhưng nó làm ảnh hưởng tới tốc độ của hệ thống, nhất là hook system-wide, vì tất cả các sự kiện của hệ thống sẽ được định hướng tới một hàm nào đó, rõ ràng điều này làm hệ thống chậm đi đáng kể. Vì thế ta chỉ hên hook những thông điệp thật cần thiết và kết thúc việc hook ngay khi không dùng đến nữa.
2. Cơ chế của hook
Như đã nói ở trên, có nhiều loại hook (như chuột, bàn phím) và hệ điều hành luôn duy trì một danh sách các hook procedure cho mỗi loại đó. Mỗi danh sách các Hook procedure này được gọi là hookchain(chuỗi hook) . Bản chất của hook chain là một dãy các con trỏ hàm trỏ đến các Hook procedure.
Khi hệ thống thực hiện một sự kiện nào đó, nó sẽ tìm kiếm trong hook chain tương ứng với sự kiện đó. Nếu một hook procedure phù hợp được tìm thấy, hệ thống sẽ thực hiện nó và chỉ lấy lại quyền điều khiển sau khi hook chain kết thúc. Vì thế khi một hook procedure thực hiện xong, nó phải thực hiện việc chuyển quyền điều khiển cho hook procedure kế tiếp trong hook chain.
Tuy nhiên cơ chế này còn tùy thuộc vào loại hook. Như một số loại hook chỉ có thể theo dõi các thông điệp, vì vậy cho dù hook procedure có chuyển quyền điều khiển cho hook procedure kế tiếp hay không, hệ thống vẫn sẽ tự động làm việc này.
Một điểm cần lưu ý là hook sẽ làm chậm hệ thống, vì thế bạn chỉ nên cài đặt hook khi cần thiết và loại bỏ nó khi đã hoàn tất công việc.
Khi hệ thống thực hiện một sự kiện nào đó, nó sẽ tìm kiếm trong hook chain tương ứng với sự kiện đó. Nếu một hook procedure phù hợp được tìm thấy, hệ thống sẽ thực hiện nó và chỉ lấy lại quyền điều khiển sau khi hook chain kết thúc. Vì thế khi một hook procedure thực hiện xong, nó phải thực hiện việc chuyển quyền điều khiển cho hook procedure kế tiếp trong hook chain.
Tuy nhiên cơ chế này còn tùy thuộc vào loại hook. Như một số loại hook chỉ có thể theo dõi các thông điệp, vì vậy cho dù hook procedure có chuyển quyền điều khiển cho hook procedure kế tiếp hay không, hệ thống vẫn sẽ tự động làm việc này.
Một điểm cần lưu ý là hook sẽ làm chậm hệ thống, vì thế bạn chỉ nên cài đặt hook khi cần thiết và loại bỏ nó khi đã hoàn tất công việc.
3. Phân loại hook
Có nhiều loại hook được phân biệt dựa vào các sự kiện, thông điệp mà Hook procedure can thiệp vào. Danh sách dưới đây liệt kê các loại hook:
WH_CALLWNDPROC and WH_CALLWNDPROCRET : cho phép bạn theo dõi các thông điệp gởi đến các thủ tục cửa sổ (window procedure).
WH_CBT : hệ thống gọi thủ tục hook WH_CBT : trước khi activating, creating, destroying, minimizing, maximizing, moving, hoặcsizing một cửa sổ ; trước khi hoàn thành một lệnh hệ thống, trước khi loại bỏ một sự kiện chuột hoặc bàn phím từ hàng đợi thông điệp hệ thống …
WH_DEBUG : hệ thống gọi một thủ tục hook WH_DEBUG trước khi gọi một thủ tục hook kết hợp với bất kỳ hook khác trong hệ thống.
WH_FOREGROUNDIDLE: các hook WH_FOREGROUNDIDLE cho phép bạn thực hiện các tác vụ ưu tiên thấp trong thời gian tác vụ đó đang ở chế độ foreground idle.
WH_GETMESSAGE : hook WH_GETMESSAGE cho phép một ứng dụng theo dõi các thông điệptrả về bởi hàm GetMessage hoặc PeekMessage.
WH_JOURNALPLAYBACK : các hook WH_JOURNALPLAYBACK cho phép một ứng dụng chèn thêm các thông điệp vào hàng đợi thông điệp hệ thống.
WH_JOURNALRECORD: các hook WH_JOURNALRECORD cho phép bạn giám sát và ghi lại các sự kiện đầu vào.
WH_KEYBOARD_LL : Các hook WH_KEYBOARD_LL cho phép bạn theo dõi các sự kiện bàn phím trong hàng đợi ứng dụng.
WH_KEYBOARD : hook WH_KEYBOARD cho phép một ứng dụng theo dõi lưu lượng truy cập thông điệp của thông điệp WM_KEYDOWN và WM_KEYUP được trả về bởi hàm GetMessage hoặc PeekMessage.
WH_MOUSE_LL : các hook WH_MOUSE_LL cho phép bạn theo dõi các sự kiện chuột trong hàng đợi ứng dụng.
WH_MOUSE : các hook WH_MOUSE cho phép bạn theo dõi các thông điệp chuột được trả về bởi hàm GetMessage hoặc PeekMessage.
WH_MSGFILTER and WH_SYSMSGFILTER : cho phép theo dõi chính các thông điệp được xử lý bởi menu, scrollbar, dialog…
WH_SHELL
Chi tiết xem thêm tại thư viện lập trình MSDN.
WH_CALLWNDPROC and WH_CALLWNDPROCRET : cho phép bạn theo dõi các thông điệp gởi đến các thủ tục cửa sổ (window procedure).
WH_CBT : hệ thống gọi thủ tục hook WH_CBT : trước khi activating, creating, destroying, minimizing, maximizing, moving, hoặcsizing một cửa sổ ; trước khi hoàn thành một lệnh hệ thống, trước khi loại bỏ một sự kiện chuột hoặc bàn phím từ hàng đợi thông điệp hệ thống …
WH_DEBUG : hệ thống gọi một thủ tục hook WH_DEBUG trước khi gọi một thủ tục hook kết hợp với bất kỳ hook khác trong hệ thống.
WH_FOREGROUNDIDLE: các hook WH_FOREGROUNDIDLE cho phép bạn thực hiện các tác vụ ưu tiên thấp trong thời gian tác vụ đó đang ở chế độ foreground idle.
WH_GETMESSAGE : hook WH_GETMESSAGE cho phép một ứng dụng theo dõi các thông điệptrả về bởi hàm GetMessage hoặc PeekMessage.
WH_JOURNALPLAYBACK : các hook WH_JOURNALPLAYBACK cho phép một ứng dụng chèn thêm các thông điệp vào hàng đợi thông điệp hệ thống.
WH_JOURNALRECORD: các hook WH_JOURNALRECORD cho phép bạn giám sát và ghi lại các sự kiện đầu vào.
WH_KEYBOARD_LL : Các hook WH_KEYBOARD_LL cho phép bạn theo dõi các sự kiện bàn phím trong hàng đợi ứng dụng.
WH_KEYBOARD : hook WH_KEYBOARD cho phép một ứng dụng theo dõi lưu lượng truy cập thông điệp của thông điệp WM_KEYDOWN và WM_KEYUP được trả về bởi hàm GetMessage hoặc PeekMessage.
WH_MOUSE_LL : các hook WH_MOUSE_LL cho phép bạn theo dõi các sự kiện chuột trong hàng đợi ứng dụng.
WH_MOUSE : các hook WH_MOUSE cho phép bạn theo dõi các thông điệp chuột được trả về bởi hàm GetMessage hoặc PeekMessage.
WH_MSGFILTER and WH_SYSMSGFILTER : cho phép theo dõi chính các thông điệp được xử lý bởi menu, scrollbar, dialog…
WH_SHELL
Chi tiết xem thêm tại thư viện lập trình MSDN.
4. Cài đặt Hook
Ta có thể cài đặt thủ tục hook vào chuỗi hook bằng việc gọi hàm SetWindowsHookEx và chỉ ra kiểu hook đang gọi thủ tục, việc cài đặt hook có thể thực hiện trên mọi tiến trình trong hệ thống.
Nếu sử dụng hook toàn cục thì phải đặt trong thư viện liên kết động (DLL). Ứng dụng muốn sử dụng thư viện liên kết động phải lấy được handle của thư viện đó. Để nhận Handle của thư viện liên kết động ta có thể sử dụng hàm LoadLibrary với tham số là tên của thư viện. Sau khi có được Handle của DLL, ta sẽ lấy địa chỉ của thủ tục hook trong thư viện liên kết động thông qua hàm GetProcAddress. Sau khi đã có thủ tục hook, sử dụng hàm SetWindowsHookEx để cài đặt thủ tục hook vào trong chuỗi hook.
Nếu sử dụng hook toàn cục thì phải đặt trong thư viện liên kết động (DLL). Ứng dụng muốn sử dụng thư viện liên kết động phải lấy được handle của thư viện đó. Để nhận Handle của thư viện liên kết động ta có thể sử dụng hàm LoadLibrary với tham số là tên của thư viện. Sau khi có được Handle của DLL, ta sẽ lấy địa chỉ của thủ tục hook trong thư viện liên kết động thông qua hàm GetProcAddress. Sau khi đã có thủ tục hook, sử dụng hàm SetWindowsHookEx để cài đặt thủ tục hook vào trong chuỗi hook.
5. Giải phóng Hook
Như đã nói thì hook nên được bỏ đi nếu như không cần thiết nữa bằng cách sử dụng hàm UnhookWindowsHookEx.
Với thread-specific hook, việc sử dụng hàm UnhookWindowsHookEx sẽ giải phóng thủ tục hook. Tuy nhiên với hook toàn tục (system-wide hook) thì hàm này không thể trả tự do cho hàm DLL. Việc gọi hàm LoadLibrary sẽ gọi trong ngữ cảnh của tất cả các tiến trình, tuy nhiên hàm FreeLibrary thì không thể thực hiện với các tiến trình khác. Vì vậy, không có cách nào để giải phóng DLL. Hệ thống chỉ có thể giải phóng DLL khi tất cả các tiến trình liên kết tới DLL đó phải kết thúc hoặc gọi FreeLibrary.
Giải pháp đặt ra cho vấn đề này là xây dựng hàm cài đặt ngay trong thư viện DLL. Bằng việc liên kết tới DLL, ứng dụng có thể cài đặt hook. Và ngay trong DLL cũng phải có hàm giải phóng hook để giải phóng khi không cần đến nữa.
Với thread-specific hook, việc sử dụng hàm UnhookWindowsHookEx sẽ giải phóng thủ tục hook. Tuy nhiên với hook toàn tục (system-wide hook) thì hàm này không thể trả tự do cho hàm DLL. Việc gọi hàm LoadLibrary sẽ gọi trong ngữ cảnh của tất cả các tiến trình, tuy nhiên hàm FreeLibrary thì không thể thực hiện với các tiến trình khác. Vì vậy, không có cách nào để giải phóng DLL. Hệ thống chỉ có thể giải phóng DLL khi tất cả các tiến trình liên kết tới DLL đó phải kết thúc hoặc gọi FreeLibrary.
Giải pháp đặt ra cho vấn đề này là xây dựng hàm cài đặt ngay trong thư viện DLL. Bằng việc liên kết tới DLL, ứng dụng có thể cài đặt hook. Và ngay trong DLL cũng phải có hàm giải phóng hook để giải phóng khi không cần đến nữa.
6. Hàm lọc (Filter Function / Procedure)
Hook procedure là một loại callback function (hàm hồi quy). Hệ thống sẽ gọi các hàm này khi các sự kiện, thông điệp tương ứng với loại hook. Mỗi loại hook có một hook procedure khác nhau nhưng đều có cùng tham số như cú pháp bên dưới. Với mỗi hook procedure khác nhau thì việc xét các giá trị tham số cũng khác nhau.
Danh sách hook procedure tương ứng với từng loại hook được liệt kê trong phần giới thiệu về hàm SetWindowsHookEx.
Danh sách hook procedure tương ứng với từng loại hook được liệt kê trong phần giới thiệu về hàm SetWindowsHookEx.
Cú pháp chung:
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
Tham số:
· nCode: giá trị giúp xác định cách xử lý thông điệp. Nếu nCode nhỏ hơn 0, hook procedure sẽ gọi CallNextHookEx. Ngược lại sẽ thực hiện các công việc xử lý trước khi gọi CallNextHookEx.
· wParam và lParam: tùy theo loại hook sẽ có ý nghĩa khác nhau. Ví dụ nếu bạn hook bàn phím thì wParam sẽ có giá trị của phím được nhấn, lParam sẽ chứa các thông tin về số lần nhấn, các phím tắt, trạng thái phím,…
· nCode: giá trị giúp xác định cách xử lý thông điệp. Nếu nCode nhỏ hơn 0, hook procedure sẽ gọi CallNextHookEx. Ngược lại sẽ thực hiện các công việc xử lý trước khi gọi CallNextHookEx.
· wParam và lParam: tùy theo loại hook sẽ có ý nghĩa khác nhau. Ví dụ nếu bạn hook bàn phím thì wParam sẽ có giá trị của phím được nhấn, lParam sẽ chứa các thông tin về số lần nhấn, các phím tắt, trạng thái phím,…
7. Khả năng của Hook – Tại sao phải sử dụngHook
Hook cung cấp các khả năng mạnh cho các ứng dụng chạy trên nền Windows, các ứng dụng này có thể dùng hook để :
· Xử lý hoặc định nghĩa tất cả các thông điệp cho dialog box, message box, scroll bar, hoặc menu của một ứng dụng. (Sử dụng hook WH_MSGFILTER).
· Xử lý hoặc định nghĩa tất cả các thông điệp cho dialog box, message box, scroll bar, hoặc menu của hệ thống. (Sử dụng hook WH_SYSMSGFILTER).
· Xử lý hoặc định nghĩa tất cả các thông điệp (bất chấp là thông điệp gì) của hệ thống mỗi khi GetMessage hoặc PeekMessage được gọi. (Sử dụng hook WH_GETMESSAGE)
· Xử lý hoặc định nghĩa tất cả các thông điệp (bất chấp là thông điệp gì) của hệ thống mỗi khi SendMessage được gọi. (Sử dụng hook WH_CALLWNDPROC).
· Thu (Record) và phát lại (Playback) các sự kiện keyboard và mouse . (Sử dụng hook WH_JOURNALRECORD, WH_JOURNALPLAYBACK).
· Xử lý , định nghĩa hoặc hủy bỏ tất cả các sự kiện bàn phím.(Sử dụng hook WH_KEYBOARD).
· Xử lý , định nghĩa hoặc hủy bỏ tất cả các sự kiện chuột (Sử dụng hook WH_MOUSE).
Tận dụng các khả năng trên, các ứng dụng có thể sử dụng hook để :
· Cung cấp phím trợ giúp F1 hỗ trợ menu, dialog box và message box (Sử dụng hook WH_MSGFILTER).
· Cung cấp các tính năng thu và phát các sự kiện mouse và keyboard, thường được gọi là các macro. (Sử dụng hook WH_JOURNALRECORD, WH_JOURNALPLAYBACK).
· Theo dõi các thông điệp để biết được thông điệp nào được gởi đến cửa sổ nào cũng như các hành động nào sẽ làm phát sinh thông điệp tương ứng. (Sử dụng hook WH_GETMESSAGE & WH_CALLWNDPROC). Chương trình Spy trong bộ Win32™ SDK của Windows NT đã rất thành công trong việc sử dụng hook để thực hiện tác vụ này.
Giả lặp các tác vụ input của keyboard và mouse (Sử dụng hook WH_JOURNALPLAYBACK). Chỉ có hook mới có thể cho ta một phương pháp chắc chắn và tin cậy để thực hiện điều này. Nếu ta tiếp cận theo cách khác, gởi message chẳng hạn, Windows sẽ không cập nhật trạng thái của mouse cũng như keyboard, và điều này sẽ dẫn đến những hành động không mong muốn. Còn nếu như hook được sử dụng, các sự kiện sẽ được xử lý y hệt như sự kiện vật lý.
· Xử lý hoặc định nghĩa tất cả các thông điệp cho dialog box, message box, scroll bar, hoặc menu của một ứng dụng. (Sử dụng hook WH_MSGFILTER).
· Xử lý hoặc định nghĩa tất cả các thông điệp cho dialog box, message box, scroll bar, hoặc menu của hệ thống. (Sử dụng hook WH_SYSMSGFILTER).
· Xử lý hoặc định nghĩa tất cả các thông điệp (bất chấp là thông điệp gì) của hệ thống mỗi khi GetMessage hoặc PeekMessage được gọi. (Sử dụng hook WH_GETMESSAGE)
· Xử lý hoặc định nghĩa tất cả các thông điệp (bất chấp là thông điệp gì) của hệ thống mỗi khi SendMessage được gọi. (Sử dụng hook WH_CALLWNDPROC).
· Thu (Record) và phát lại (Playback) các sự kiện keyboard và mouse . (Sử dụng hook WH_JOURNALRECORD, WH_JOURNALPLAYBACK).
· Xử lý , định nghĩa hoặc hủy bỏ tất cả các sự kiện bàn phím.(Sử dụng hook WH_KEYBOARD).
· Xử lý , định nghĩa hoặc hủy bỏ tất cả các sự kiện chuột (Sử dụng hook WH_MOUSE).
Tận dụng các khả năng trên, các ứng dụng có thể sử dụng hook để :
· Cung cấp phím trợ giúp F1 hỗ trợ menu, dialog box và message box (Sử dụng hook WH_MSGFILTER).
· Cung cấp các tính năng thu và phát các sự kiện mouse và keyboard, thường được gọi là các macro. (Sử dụng hook WH_JOURNALRECORD, WH_JOURNALPLAYBACK).
· Theo dõi các thông điệp để biết được thông điệp nào được gởi đến cửa sổ nào cũng như các hành động nào sẽ làm phát sinh thông điệp tương ứng. (Sử dụng hook WH_GETMESSAGE & WH_CALLWNDPROC). Chương trình Spy trong bộ Win32™ SDK của Windows NT đã rất thành công trong việc sử dụng hook để thực hiện tác vụ này.
Giả lặp các tác vụ input của keyboard và mouse (Sử dụng hook WH_JOURNALPLAYBACK). Chỉ có hook mới có thể cho ta một phương pháp chắc chắn và tin cậy để thực hiện điều này. Nếu ta tiếp cận theo cách khác, gởi message chẳng hạn, Windows sẽ không cập nhật trạng thái của mouse cũng như keyboard, và điều này sẽ dẫn đến những hành động không mong muốn. Còn nếu như hook được sử dụng, các sự kiện sẽ được xử lý y hệt như sự kiện vật lý.
8. Ứng dụng của Hook
Hook được ứng dụng khá nhiều trong lập trình và là kĩ thuật không thể thiếu nếu như bạn muốn thực hiện một số ứng dụng điển hình sau:
· Tạo các chương trình record và play back macro.
· Tạo các ứng dụng computer-based training (CBT)
· Bắt và giả lập các thông điệp bàn phím, chuột
· Cung cấp chức năng Help (F1) cho ứng dụng
· Xác định trạng thái rỗi của ứng dụng để thực hiện công việc nào đó.
· Tạo các chức năng debug.
9. Sử dụng HOOK để xây dựng bộ gõ tiếng Việt
Demo chương trình của mình viết bằng ngôn ngữ Autoit:
Các bạn có thể tham khảo mã nguồn mình đã đăng ở đây : [Raitokey]