Saturday, October 31, 2009

WINDOWS API 2 - Hiểu rõ cách làm việc của Hệ điều hành Windows

Khi nắm rõ bản chất của Windows API để lập trình sâu với hệ thống, ta cũng cần hiểu biết sơ bộ về Hệ điều hành Windows cách thức điều khiển của Hệ điều hành đối với ứng dụng để có thể can thiệp như bổ sung chức năng thậm chí biến đổi nó, bắt thực hiện theo hướng của mình, ngay cả khi hướng này ngược hẳn với công dụng truyền thống !!!

Các chương trình ứng dụng trong Windows có thể có nhiều cửa sổ phục vụ cho nó. Cửa sổ có thể là Form thậm chí là Dialog. Mỗi cửa sổ này đều có một handle (Cán) để hệ thống nhận biết do chính hệ điều hành Windows tạo ra. Cán cửa sổ này là chỉ số duy nhất.

Hệ điều hành và chương trình ứng dụng đều duy trì các hàng đợi các chỉ lệnh cần thực hiện. Mỗi ứng dụng đều có hàng đợi (Message Queue). Khi người sử dụng ra lệnh hoặc có một biến cố, các chương trình điều khiển thiết bị nhập (INPUT) sẽ chuyển các thông tin vào thành chỉ lệnh và đặt chỉ lệnh này vào hàng đợi hệ thống (System Message Queue). Hệ điều hành lấy lần lượt các chỉ lệnh trong hàng đợi hệ thống kiểm tra để xác định cửa sổ nào sẽ tiếp nhận thi sẽ đặt vào hàng đợi của nó (thread message) một chỉ lệnh tương ứng. Các chương trình ứng dụng căn cứ vào chỉ lệnh này để thực hiện cũng như xử lý chúng.

Các cửa sổ giống như một động cơ tự động chạy theo một vòng lặp. Tiếp "nhiên liệu" cho các "động cơ" này là hệ điều hành Windows. Hệ điều hành Windows nhận các chỉ lệnh (message) từ hàng đợi của hệ điều hành, dùng một hàm dạng API (Như kỳ 1 - Hàm này bản chất là một thủ tục) để cung cấp chỉ lệnh tới cửa sổ thông qua cán (handle) của cửa sổ.

Có nghĩa là bản thân trong mỗi cửa sổ luôn có một hàm gọi là WinProc (Đôi khi gọi là WinMain()). Hàm này là cốt lõi xử lý của cửa sổ. Trong hàm, nó lặp đi lặp lại liên miên 2 dòng lệnh sau thông qua cấu trúc:
Do While 0 <>GetMessage (message, 0, 0,0)
TranslateMessage message
DispatchMessage message
Loop

Trong đó message là chỉ lệnh mà Hệ điều hành cung cấp, thông qua cán (handle) của cửa sổ. Đương nhiên, nếu chỉ lệnh có giá trị WM_QUIT thì hàm WinProc trong cửa sổ chấm dứt vòng lặp.

Còn nếu chỉ lệnh message khác giá trị trên, thì 2 dòng lệnh trên sẽ thực hiện. Cụ thể:
TranslateMessage message -> Dịch chỉ lệnh thành dạng dữ liệu khác đặt kết quả này vào hàng đợi của ứng dụng.
DispatchMessage message ->Nhận chỉ lệnh từ hàm GetMessage và gửi cho hệ thống. Hệ thống sẽ đưa chỉ lệnh cho ứng dụng.

Windows có hàng ngàn chỉ lệnh khác nhau đó là các hằng dạng WM_* (Windows đặt tên cho tiện gọi thôi, vì bản chất các hằng này là một con số - Rất khó nhớ. Nếu phân tích chi tiết ra, những con số này lại là dãy số 0 và 1, tức là bật tắt ấy mà).
Một hàm WinProc luôn nhận vào trong nó các biến theo khuôn mẫu sau để xử lý:
Function WinProc(hwnd as Long, wc as WNDCLASSEX, message as MSG, wParam as Long, lparam as Long)

Nếu hàm WinProc không xử lý các chỉ lệnh, nó phải đưa trả chỉ lệnh cho hệ điều hành xử lý thông qua hàm DefWindowProc. Hàm DefWindowProc gởi lại chỉ lệnh WM_CLOSE cho WinProc. Hàm WinProc sẽ lại gởi trả WM_CLOSE cho DefWindowProc một lần nữa như mô tả ở trên.

Bạn có thể thấy, kết quả và cơ chế xử lý rất lằng nhằng. Ta có thể tóm lại sơ bộ như sau:

Các chỉ lệnh đưa tới ngăn chờ trên thông thường từ các nguồn sau:
1. Hệ thống đặt vào
2. Chương trình khác đặt vào
3. Chính chương trình của mình đặt vào thông qua các hàm SendMessage() và PostMessage().

Tuy nhiên nếu bạn chọn sử dụng hàm SendMessage() thì sau khi chỉ lệnh được WinProc lấy ra xử lý thì chương trình mới tiếp tục chạy tiếp lệnh kế sau. Còn bạn dùng PostMessage() chỉ có tác dụng đặt chỉ lệnh vào hàng đợi và thực hiện ngay lệnh kế tiếp.

Từ đây ta nhận thấy việc xử lý hệ thống của Windows thông qua cơ chế trên thì giản đơn đi rất nhiều. Ta sẽ có các hướng giải quyết tiếp theo như sau:
1. Nếu ta thiết kế một hàm WinProc() mới, Ví dụ NewWinProc(), thay thế hàm WinProc() truyền thống
- Rồi ta đổi địa chỉ của WinProc() gốc sang địa chỉ của WinProc() mà ta thiết kế. Trong thủ tục này sử dụng cấu trúc Select Case để tuỳ vào chỉ lệnh message mà xử lý theo ý mình.
- Trường hợp chỉ lệnh message nào không thể viết được thủ tục thi hành (Không cần thay đổi hoặc khó quá) thì ta gọi thủ tục DefWinProc() của Windows xử lý. (Tức là dễ thì làm, khó trả lại ấy mà). Đây chính là Kỹ thuật Subclass.
- Tuy nhiên nếu như hàm WinProc() cũ, xử lý tốt một số tính năng nào đó thì ta có thể tận dụng thủ tục bằng cách gọi lại bằng hàm CallWindowProc().
- Thậm chí ta có thể lồng WinProc() vào trong NewWinProc() để xử lý
(Xem các bài ví dụ của Nguyễn Phương Thảo có sử dụng API).

2. Trường hợp không thể thay được hàm WinProc() bằng NewWinProc() (Ví dụ như bạn lập trình với các chương trình ứng dụng khác như Winword, Excel...) bạn phải chặn các chỉ lệnh trước khi nó được lấy ra khỏi hàng đợi. Đó chính là Kỹ thuật Hooking, một Kỹ thuật cực kỳ mạnh để làm việc với Windows.

Trước khi chỉ lệnh được hàm SendMessage() lấy từ hàng đợi gửi đi, hoặc cũng có thể chỉ lệnh được lấy bằng hàm PickMessage() hay GetMessage(), ta có thể đăng ký với Windows để sử dụng bộ lọc HookFilter. Khi đó những chỉ lệnh cần xử lý đã đăng ký, đều qua HOOK Filter. Ta chỉ việc viết một thủ tục dùng hàm WinProc lấy chỉ lệnh từ HOOK Filter để xử lý. (Xem bài viết chặn các thủ tục in và nhập dữ liệu của Nguyễn Phương Thảo).

3. Các đặc điểm của lớp cửa sổ:

- Mỗi ứng dụng có thể tạo ra nhiều cửa sổ, thông thường các cửa sổ này có những đặc điểm giống nhau và được phân theo từng lớp CLASS. Khi lập trình, bao giờ ta cũng đăng ký lớp với hệ thống thông qua một hàm là RegisterClassEx(). Khi lớp đã được đăng ký (Chỉ 1 lần) thì các thông tin window và địa chỉ hàm WinProc sẽ được lưu trong suốt thời gian mà nó tồn tại.

- Ta có thể thay đổi các thông tin trong bộ đăng ký lớp, khi đó nó sẽ ảnh hưởng đến toàn bộ cửa sổ trong lớp này.

Bạn thân mến! Như vậy bạn đã nắm chắc về cơ chế làm việc của Windows và các thủ tục hệ thống của nó. Hi vọng bạn hãy đọc thật kỹ và hiểu rõ về nó, để những bài viết sau chúng ta sẽ mổ xẻ giải phẫu từ những hàm API cơ sở được dễ dàng hơn.

No comments:

Post a Comment