ĐỀ KIỂM TRA CUỐI KÌ 2 BIÊN SOẠN THEO ĐỊNH HƯỚNG ĐỀ BGD 2025 MÔN TOÁN 10 - CÁN...
Ứng Dụng Cơ Chế Hook Để Xây Dựng Chương Trình Hỗ Trợ Gõ Tiếng Việt Trên Hệ Điều Hành Window 32 Bit
1. [Programming] Ứng dụng cơ chế Hook - Phần 1
Ứng dụng cơ chế Hook để xây dựng chương trình hỗ trợ gõ Tiếng
Việt trên Hệ Điều Hành Windows 32 Bit - Phần 1
I.Sự kiện và thông điệp trên HĐH Windows
I.1.Giới thiệu
Ngày nay, Windows có lẽ không còn xa lạ với người sử dụng PC. Nhắc đến
Windows, người ta thường nghĩ về nó như một Hệ Điều Hành (HĐH) dễ sử dụng, ở
đó, tương tác giữa người dùng với các ứng dụng cũng như với các thành phần tiện ích
của Windows thông qua giao diện đồ họa (GUI), bằng các thao tác trên keyboard,
mouse vô cùng đơn giản.
Một câu hỏi được đặt ra là : “Các ứng dụng làm thế nào để phân loại, lưu giữ cũng
như đáp lại những tương tác đó cho người dùng ?”. Đối với Windows vấn đề này
được giải quyết một cách trọn vẹn : HĐH đưa ra cơ chế thông điệp (message) cùng
với tập hợp các cấu trúc dữ liệu và các hàm API hỗ trợ ứng dụng trong việc giao tiếp
với người dùng.
I.2.Hai loại hàng đợi thông điệp trên Windows
Trước hết, ta cần phải biết làm thế nào HĐH Windows lưu trữ các thông điệp.
Hai loại hàng đợi trong Windows phục vụ cho mục đích này :
+ Hàng đợi hệ thống (The System Queue)
+ Hàng đợi thông điệp của ứng dụng (Application Queue)
Hàng đợi hệ thống (The System Queue)
Windows có các trình điều khiển thiết bị (Drivers) chịu trách nhiệm cho các dịch vụ
ngắt từ thiết bị phần cứng mouse và keyboard. Tại mỗi thời điểm ngắt phát sinh, các
Driver này gọi một điểm vào (hàm) đặc biệt trong USER.EXE để chỉ ra rằng một sự
kiện vừa xảy ra.
Tất cả các sự kiện mouse, keyboard được lưu trong một hàng đợi được gọi là hàng đợi
hệ thống (The System Queue). Đây là hàng đợi dùng chung cho toàn hệ thống , mọi
tiến trình đang chạy đều chia xẻ hàng đợi này. Nhiệm vụ độc nhất của nó là ghi nhận
lại những sự kiện phần cứng (mouse actions, keystrokes) khi chúng xảy ra.
2. Hàng đợi thông điệp của ứng dụng (Application Queue)
Khi một tiến trình được khởi tạo, một hàng đợi đại diện cho nó được tạo ra, hàng đợi
này (đôi khi được gọi là hàng đợi tác vụ) được dùng để chứa những thông điệp sẽ
được gởi cho các cửa sổ của ứng dụng. Những thông điệp này là những thông điệp
được gởi một cách tường minh bằng một trong hai hàm sau :
+ PostMessage
+ PostAppMessage
(Lưu ý : Hàm PostQuitMessage không gởi thông điệp vào hàng đợi ứng dụng
Ứng dụng có thể dùng hai Primitive là GetMessage và PeekMessage doWindows
cung cấp để khảo sát hàng đợi của mình. Hai hàm này cho phép ứng dụng “Lấy một
message ra khỏi hàng đợi” để từ đó phân loại và có những “Trả lời” thích hợp với
người dùng.
I.3.Bàn thêm về GetMessage và PeekMessage
Bên trong HĐH Windows, GetMessage và PeekMessage thi hành cùng mã lệnh.
Điểm khác biệt chính giữa 2 hàm là trong trường hợp không có message nào được trả
về cho ứng dụng. Trong trường hợp này, GetMessage đặt ứng dụng vào trạng thái
“Sleep” trong khi PeekMessage trả về ứng dụng giá trị NULL.
Ngay trước khi GetMessage và PeekMessage trả message về cho ứng dụng, hệ thống
sẽ kiểm tra xem có sự tồn tại của hook WH_GETMESSAGE hay không. Nếu có một
“Filter Function” (thực chất là 1 hàm) ứng với hook trên được cài đặt, hàm này sẽ
được gọi. “Filter Function” không được gọi nếu PeekMessage không tìm thấy
message và trả về NULL.
PeekMessage với tùy chọn PM_NOREMOVE
Theo mặc định, cả PeekMessage và GetMessage sẽ lấy message ra khỏi hàng đợi khi
mỗi message được trả về cho ứng dụng. Tuy nhiên, có đôi lúc, ứng dụng có thể chỉ
muốn vào hàng đợi để kiểm tra sự tồn tại của một messsage mà không muốn gỡ bỏ
nó. Để làm được điều này, chương trình của ta sẽ gọi PeekMessage với tùy chọn
PM_NOREMOVE, lúc này PeekMessage vẫn trả về message như thường lệ nhưng
sẽ không lấy nó ra khỏi hàng đợi.(Chi tiết về GetMessage và PeekMessage có thể
tham khảo tại Article “GetMessage and PeekMessage Internals” trong thư viện
MSDN)
3. II.Sự kiện bàn phím
II.1.Mô hình chung
Mỗi phím trên bàn phím được hệ thống gắn với một giá trị duy nhất gọi là mã quét
(Scan Code), đây là một giá trị phụ thuộc thiết bị, tức là giá trị này sẽ lệ thuộc vào
từng loại bàn phím cụ thể. Khi người dùng gõ một phím, sẽ có 2 mã quét được sinh ra
: một khi phím được nhấn và một khi phím được thả.
Trình điều khiển bàn phím (Keyboard Device Driver) sẽ thông dịch mã quét và
chuyển nó thành mã phím ảo (vitual-key code), một giá trị độc lập thiết bị, được
định nghĩa bởi hệ thống. Sau đó, trình điều khiển tạo một thông điệp bao gồm
scancode, virtual-key code cùng một số thông tin khác (sự lặp phím, trạng thái các
phím Alt, Ctrl...) , đặt vào hàng đợi hệ thống. Hệ thống lấy thông điệp này ra khỏi
hàng đợi hệ thống và gởi đến hàng đợi thông điệp của ứng dụng. Cuối cùng, vòng lặp
thông điệp (Message Loop) sẽ lấy thông điệp ra khỏi hàng đợi ứng dụng và chuyển
nó đến hàm xử lý message thích hợp để xử lý. Quá trình trên có thể minh hoạ qua
hình vẽ sau :
Rõ ràng là nếu ta có một cách nào đó để có được message trước khi nó đến hàm xử lý
thông điệp của cửa sổ, ta có thể thực hiện được một vài thao tác thú vị. Và trên HĐH
Windows, điều này hoàn toàn có thể thực hiện được nhờ cơ chế hook như ta sẽ thấy ở
phần sau.
II.2.Các thông điệp liên quan bàn phím
Khi ta nhấn một phím, một thông điệp WM_KEYDOWN sẽ được đặt vào hàng đợi
của tiểu trình ứng với cửa sổ đang được focus. Và khi ta nhả phím, hàng đợi sẽ có một
thông điệp WM_KEYUP. Hai thông điệp này thường xuất hiện thành cặp. Tuy nhiên,
nếu ta nhấn phím và giữ phím đủ lâu làm đặc tính “Automatic Repeat” của bàn phím
hoạt động, hệ thống sẽ sinh nhiều thông điệp WM_KEYDOWN liên tiếp nhau. Và
lưu ý một điều là khi chúng ta nhả phím, chỉ có một thông điệp WM_KEYUP được
phát sinh mà thôi.
wParam của cả 2 thông điệp trên sẽ cho ta mã phím ảo (virtual-key code) của phím
được nhấn.
lParam dùng 32 bit của mình để mô tả thông tin thêm về sự kiện gõ phím đã tạo ra
message.
4. Thông tin này bao gồm : sự lặp phím, mã quét (scan code), trạn thái phím ALT, trạng
thái phím trước,... Sơ đồ của 32 bit này như sau :
Repeat Count : Hệ thống tăng bộ đếm này lên khi bàn phím phát sinh thông điệp
WM_KEYDOWN nhanh hơn khả năng xử lý message của ứng dụng. Điều này
thường xảy ra khi người dùng giữ phím đủ lâu làm cho đặc tính “Automatic Repeat”
của bàn phím hoạt động. Lúc này, thay vì đặt vào hàng đợi hệ thống nhiều thông điệp
WM_KEYDOWN liên tiếp, hệ thống sẽ kết hợp các message này thành một message
duy nhất và tăng giá trị Repeat Count. Tác vụ nhả phím không làm cho đặc tính
“Automatic Repeat” được kích hoạt, do đó giá trị Repeat Count của thông điệp
WM_KEYUP luôn luôn là 1.
Scan Code : Giá trị phụ thuộc thiết bị được bàn phím phát sinh khi một phím bị nhấn
hay nhả. Đây không phải là mã ký tự đại diện cho phím được nhấn. Ứng dụng thường
không quan tâm đến giá trị này, thay vì vậy, nó sử dụng giá trị độc lập thiết bị là mã
phím ảo (virtual-key code) để xử lý message.
Extended-Key Flag Bit cờ hiệu cho biết một phím có phải là phím mở rộng hay
không. Bit này được bật khi phím bị nhấn là phím mở rộng như : INS, DEL, HOME,
END, ...
Context Code Bit cờ hiệu cho biết tại thời điểm thông điệp phát sinh, phím ALT có
bị nhấn hay không. Bit này bật khi phím ALT bị nhấn, tắt khi phím ALT được nhả.
Previous Key-State Flag Bit cờ hiệu cho biết trạng thái của phím trước khi gởi
message là “up” hay “down”. Bit này là 1 nếu phím là “down”, 0 nếu phím là “up”.
Transition-State Flag Bit này là 0 đối với WM_KEYDOWN, là 1 đối với
WM_KEYUP.Thông điệp WM_KEYDOWN, WM_KEYUP cung cấp cho ta khá
nhiều thông tin về sự kiện gõ phím, tuy nhiên, nó không cho ta mã ký tự của phím
được nhấn. Để có được thông tin này, ứng dụng phải thêm hàm TranslateMessage
vào vòng lặp thông điệp của mình.Hàm này chuyển thông điệp WM_KEYDOWN
thành thông điệp WM_CHAR chứa mã ký tự ứng với phím được gõ, sau khi đã kiểm
tra trạng thái của phím SHIFT và CAPS LOCK.
II.3.Giả lập gõ phím
Đôi khi chúng ta muốn có một sự kiện gõ phím mà không có sự tác động về mặt vật
lý đối với bàn phím. Hàm keybd_event giúp ta có được khả năng này, chức năng của
nó là giả lập một thao tác đối với bàn phím (nhấn hay nhả). Khai báo của hàm này
như sau :
5. VOID keybd_event(
BYTE bVk, // virtual-key code
BYTE bScan, // hardware scan code
DWORD dwFlags, // function options
ULONG_PTR dwExtraInfo // additional keystroke data
);
Ý nghĩa của từng tham số :
bVk : mã phím ảo của phím cần giả lập.
bScan : tham số này không dùng.
dwFlags : xác định loại thao tác cần giả lập, tham số này là một trong các giá
trị sau :
Giá trị Ý nghĩa
KEYEVENTF_EXTENDEDKEYScan Code được đi trước bởi giá trị 224.
KEYEVENTF_KEYUP Giả lập thao tác nhả phím.
dwExtraInfo : thông tin thêm về sự kiện ta giả lập.
III.Cơ chế Hook trên HĐH Windows
III.1.Giới thiệu
Trên HĐH Windows, một hook là cơ chế mà nhờ đó một hàm có thể chặn
các sự kiện (message, mouse actions, keystrokes) trước khi chúng đến
ứng dụng. Hàm này có thể thực hiện một số thao tác trên sự kiện, và
trong một vài trường hợp có thể định nghĩa lại hoặc hủy bỏ sự kiện mà
nó bắt được. Một đặc điểm quan trọng cần lưu ý là các hàm này được
gọi bởi bản thân HĐH Windows chứ không phải bởi ứng dụng.
Các hàm thuộc loại vừa nêu thường được gọi là Filter Function, chúng được phân
loại theo loại sự kiện mà chúng can thiệp.Ví dụ, một Filter Function hứng tất cả các
sự kiện mouse sẽ khác với một Filter Function can thiệp các sự kiện keyboard …
Một Filter Funtion muốn được Windows gọi thì trước tiên nó phải được gắn với một
Windows Hook, công việc này thường được gọi là “Cài đặt một hook”. Windows
Hook thật ra là các loại hook khác nhau được HĐH định nghĩa, nó giúp người sử
6. dụng có thể cài đặt Filter Function của mình đúng với mục đích mà họ mong muốn.
Khi cài đặt hook, ta xác định loại hook thông qua các hằng số có dạng WH_XXX, ví
dụ WH_KEYBOARD xác định loại hook dùng để can thiệp các sự kiện thuộc về bàn
phím.
Một Windows Hook có thể có nhiều Filter Function gắn với nó, trong tình huống này,
Windows sẽ duy trì một chuỗi các Filter Function. Hàm được cài đặt gần nhất sẽ ở
đầu chuỗi, hàm được cài đặt lâu nhất sẽ ở cuối chuỗi. Lúc này, nếu có một sự kiện xảy
ra và sự kiện này tương ứng với loại hook ta đã cài đặt, Windows sẽ gọi hàm đầu tiên
trong chuỗi các Filter Function ứng với hook đó.
III.2.Khả năng và ứng dụng của cơ chế Hook
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ộ
7. 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ý.
III.3.Quản lý chuỗi các Filter Function
Giao diện lập trình ứng dụng (API) của Windows cung cấp 3 hàm để thao tác với
hook :
· SetWindowsHookEx
· UnhookWindowsHookEx
· CallNextHookEx
Cài đặt một Filter Function vào chuỗi các Filter Function của một hook
Tác vụ này được thực hiện thông qua hàm SetWindowsHookEx, khai báo của
hàm này như sau :
HHOOK SetWindowsHookEx(
int idHook, // hook type
HOOKPROC lpfn, // hook procedure
HINSTANCE hMod, // handle to application instance
DWORD dwThreadId // thread identifier
);
Ý nghĩa của từng tham số :
idHook : Xác định loại hook mà ta muốn cài đặt, tham số này có thể là một trong các
giá trị sau :
· WH_CALLWNDPROC
· WH_CALLWNDPROCRET
· WH_CBT
· WH_DEBUG
8. · WH_FOREGROUNDIDLE
· WH_GETMESSAGE
· WH_JOURNALPLAYBACK
· WH_JOURNALRECORD
· WH_KEYBOARD
· WH_MOUSE
· WH_MSGFILTER
· WH_SYSMSGFILTER
Mỗi giá trị trên xác định một loại hook mà ta muốn cài đặt, mỗi loại hook có một ý
nghĩa và tình huống sử
dụng khác nhau, chi tiết về từng loại hook sẽ được đề cập sau.
lpfn : Địa chỉ của Filter Function mà ta muốn gắn với hook, hàm này phải được
“export” bằng macro thích
hợp khi ta cài đặt.
hMod : Handle của module chứa Filter Function. Nếu ta cài đặt một hook cục bộ
(nghĩa là sự thực thi của
Filter Function chỉ ảnh hưởng đối với tiến trình cài đặt hook), tham số này phải là
NULL. Còn nếu ta muốn
có một hook với phạm vi toàn hệ thống (tức là mọi tiến trình đang hiện hữu đều chịu
ảnh hưởng bởi Filter
Function của ta), tham số này sẽ là Handle của DLL chứa Filter Function.
dwThreadID : Định danh của thread ứng với hook đang được cài đặt . Nếu tham số
này là một số khác 0,
Filter Function được gắn với hook chỉ được gọi trong ngữ cảnh của một thread xác
định. Còn nếu
dwThreadID = 0, Filter Function sẽ có phạm vi toàn hệ thống, và dĩ nhiên, nó sẽ được
gọi trong ngữ cảnh
của bất kỳ thread nào đang tồn tại trên HĐH. Có thể sử dụng hàm
GetCurrentThreadId để lấy được
handle của thread muốn cài đặt hook.
9. Một hook có thể được sử dụng ở mức hệ thống, ở mức cục bộ, hoặc ở cả hai mức vừa
nêu. Bảng sau mô
tả loại hook cùng tầm ảnh hưởng của nó :
WH_CALLWNDPROC Thread , global
WH_CALLWNDPROCRET Thread , global
WH_CBT Thread , global
WH_DEBUG Thread , global
WH_FOREGROUNDIDLE Thread , global
WH_GETMESSAGE Thread , global
WH_JOURNALPLAYBACKGlobal
WH_JOURNALRECORD Global
WH_KEYBOARD Thread , global
WH_MOUSE Thread , global
WH_MSGFILTER Thread , global
WH_SYSMSGFILTER Global
Với một loại hook xác định, hook cục bộ sẽ được gọi trước, sau đó là hook toàn cục.
SetWindowsHookEx trả về handle của hook được cài đặt (là 1 giá trị có kiểu
HHOOK). Giá trị này cần được
lưu lại để dùng trong hàm UnhookWindowsHookEx khi ta muốn gỡ bỏ hook. Nó sẽ
trả về NULL nếu không
thể gắn Filter Function vào hook, trong trường hợp này, hãy gọi hàm GetLastError
để biết lý do cài đặt hook
thất bại, giá trị mã lỗi là một trong các giá trị sau :
ERROR_INVALID_HOOK_FILTER : hằng số xác định loại hook sai.
ERROR_INVALID_FILTER_PROC : địa chỉ Filter Function không chính
xác.
ERROR_HOOK_NEEDS_HMOD : tham số hMod của hàm
SetWindowsHookEx đang là NULL trong khi ứng dụng muốn cài đặt một
hook toàn cục.
ERROR_GLOBAL_ONLY_HOOK : Ứng dụng đang cố cài đặt một hook
cục bộ trong khi bản chất của loại hook này là hook toàn cục.
ERROR_INVALID_PARAMETER : giá trị của tham số dwThreadID
không chính xác
ERROR_JOURNAL_HOOK_SET : Đã có một hook loại “Journal”
(WH_JOURNALPLAYBACK hoặc WH_JOURNALRECORD) đang hiện
hữu trong hệ thống. Vì lý do an toàn, Windows chỉ cho phép ta cài đặt một và
chỉ một hook loại “Journal” tại một thời điểm.
ERROR_MOD_NOT_FOUND : Ứng dụng không thể định vị handle của
DLL trong khi muốn cài đặt một hook toàn cục.
Các giá trị khác : Vấn đề an toàn của HĐH không cho phép hook này được
cài đặt, hoặc hệ thống đã hết bộ nhớ.
Tải bản FULL (18 trang): bit.ly/36dR03q
Dự phòng: fb.com/TaiHo123doc.net
10. Gỡ bỏ một Filter Function ra khỏi chuỗi các Filter Function của một hook
Windows cung cấp hàm UnhookWindowsHookEx giúp ta thực hiện việc
này.Khai báo của nó như sau :
BOOL UnhookWindowsHookEx(
HHOOK hhk // handle to hook procedure
);
Tham số duy nhất của hàm này là handle của hook cần được gỡ bỏ. Giá trị này là giá
trị được trả về từ hàm SetWindowsHookEx khi ta cài đặt hook.
Chi tiết về Filter Function
Đây là thời điểm thích hợp để bàn về Filter Function. Nhắc lại rằng Filter Function là
một hàm được gắn với loại hook mà ta muốn cài đặt. Hàm này được gọi bởi HĐH
Windows chứ không được gọi bởi ứng dụng, đây cũng là lý do mà đôi khi người ta
thường gọi nó là “Callback Function”. Tuy nhiên , để thống nhất về mặt thuật ngữ,
từ nay về sau ta vẫn gọi nó là Filter Function.
Tất cả các Filter Function đều có dạng sau :
LRESULT CALLBACK FilterFunc(int nCode, WPARAM wParam, LPARAM
lParam);
Lưu ý : “FilterFunc” chỉ là tên hàm tượng trưng, khi cài đặt, Filter Function sẽ có tên
bất kỳ theo ý của lập trình viên.
· Ý nghĩa của từng tham số truyền cho hàm.
nCode : tham số này thường được gọi là “hook code”, Filter Function sử dụng giá trị
này để quyết định cách thức xử lý đối với sự kiện.
(Lưu ý là điều này hoàn toàn tuỳ thuộc vào lập trình viên. Có thể hình dung hook
code đem lại những thông tin nào đó, và từ những thông tin này, lập trình viên sẽ
quyết định xử lý sự kiện bắt được như thế nào theo hướng riêng của anh ta).
Giá trị của hook code tùy thuộc vào từng loại hook cụ thể, và mỗi loại hook sẽ có tập
hợp những giá trị hook code đặc trưng của riêng mình. Có một quy luật mà dường
như các Filter Function của mọi loại hook cần tuân thủ : Khi Windows truyền cho
hàm giá trị hook code âm, Filter Function không được xử lý sự kiện mà phải gọi hàm
CallNextHookEx với chính những tham số mà HĐH truyền cho nó. Sau đó, nó phải
trả về giá trị được trả về bởi hàm CallNextHookEx. wParam, lParam : Đây là
những thông tin cần thiết cho Filter Function trong quá trình xử lý sự kiện. Các giá trị
này sẽ có ý nghĩa khác nhau tuỳ thuộc vào từng loại hook. Ví dụ, Filter Function gắn
với hook WH_KEYBOARD sẽ nhận mã phím ảo (Virtual-Key Code) từ wParam,
đồng thời có được từ lParam thông tin mô tả trạng thái của bàn phím khi sự kiện gõ
phím xảy ra.
3136976