Từ 1 tới 8 trên tổng số 8 kết quả

Đề tài: Hook – Ví dụ nhỏ dành cho "người mù"

  1. #1
    Ngày gia nhập
    05 2007
    Nơi ở
    HCMC
    Bài viết
    60

    Mặc định Hook – Ví dụ nhỏ dành cho "người mù"

    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 kỹ thuật cho phép một hàm có thể chặn, theo dõi, xử lý, hoặc hủy bỏ các thông điệp trước khi chúng “mò” đến được ứng dụng.

    Hai ví dụ thường gặp của Hook là ứng dụng soạn thảo văn bản tiếng Việt (Unikey, Vietkey…) và ứng dụng tra từ điển trực tiếp trên màn hình (Click’n’See, Lạc Việt MTD, English Study…). Chúng xử lý thông điệp từ bàn phím để đổi văn bản sang tiếng Việt, hoặc xử lý thông điệp từ con chuột để lấy văn vản dưới con trỏ. Chương trình KeyLogger chuyên ăn cắp mật khẩu cũng sử dụng kỹ thuật này.

    Hook hoạt động như thế nào?

    Xét ở mặt phạm vi hoạt động thì có 2 loại Hook: Hook toàn cục (có phạm vi ảnh hưởng đến toàn hệ thống) và Hook cục bộ (chỉ có tác dụng trên ứng dụng được cài Hook).

    Xét về mặt chức năng, Hook có 15 loại ứng với nhóm sự kiện mà nó sẽ xử lý. Ví dụ:
    - WH_KEYBOARD: cho phép đón nhận các thông điệp từ bàn phím
    - WH_MOUSE cho phép đón nhận các sự kiện có liên quan đến con trỏ chuột
    - WH_MSGFILTER và WH_SYSMSGFILTER: cho phép theo dõi chính các thông điệp được xử lý bởi menu, scrollbar, dialog…
    Các loại còn lại bạn có thể tìm thấy trong CD MSDN tại /winui/winui/windowsuserinterface/windowing/hooks/abouthooks.htm

    Ứng với mỗi loại Hook, Windows sẽ có một chuỗi các hàm lọc (filter function) để xử lý. Ví dụ, khi người dùng nhấn phím, thông điệp này sẽ được truyền qua tất cả các hàm lọc thuộc nhóm WH_KEYBOARD.

    Các hàm lọc này sẽ do chúng ta tùy ý định nghĩa. Mục tiêu của hàm lọc và bắt lấy thông điệp để giám sát, sửa đổi và chuyển tiếp, hoặc ngăn chặn không cho nó đến được ứng dụng. Hàm lọc được gọi bởi Windows nên còn gọi là hàm CallBack.

    Muốn cài đặt một Hook toàn cục (Hook hệ thống), thì hàm lọc phải được đặt trong file DLL.

    Cài đặt Hook có khó không?

    Về cơ bản, chỉ cần biết chút kiến thức về hệ điều hành Windows (cơ chế hoạt động và quản lý thông điệp). Thêm một ít kiến thức về C++, API, và DLL (Dynamic Link Library). Tuy nhiên, Hook là một… nghệ thuật (và người Hook cũng là 1 nghệ sỹ ), và chúng ta có thể làm đủ trò tùy vào trí tưởng tượng và “nội công” của mình.

    Sơ lượt các bước cài đặt (một Hook hệ thống)

    1. Bước 1: tạo file DLL chứa hàm lọc. Mọi hàm lọc đều có dạng như sau:
    Visual C++ Code:
    1. LRESULT CALLBACK <tên hàm>(int nCode, WPARAM wParam, LPARAM lParam)
    Bên trong hàm lọc, chúng ta có thể tùy ý làm đủ chuyện trời đất. Tuy nhiên, phải tuân thủ theo một số nguyên tắt sau:
    - Nếu nCode < 0 thì không được làm gì mà phải gọi ngay hàm CallNextHookEx() (sẽ trình bày sau) và trả về giá trị... return bởi nó.
    - Nếu có xử lý ít nhiều thì hàm lọc phải trả về giá trị khác 0

    2. Bước 2: tạo chương trình cài đặt Hook. Chương trình này sử dụng 3 hàm API sau đây để “móc” hàm lọc vào các sự kiện:

    a) Hàm cài đặt một hàm lọc
    Visual C++ Code:
    1. HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId)
    Các tham số có ý nghĩa như sau:
    + idHook: xác định loại hook mà ta muốn cài đặt (ví dụ: WH_KEYBOARD, WH_MSGFILTER…)
    + lpfn : địa chỉ hàm lọc mà ta muốn gắn với hook
    + hMod : handle của module chứa hàm lọc (là handle của file DLL trong trường hợp Hook toàn cục)
    + dwThreadID : định danh của thread ứng với hook đang được cài đặt (nếu = 0 thì Hook có phạm vi toàn hệ thống và bao hảm cả mọi thread đang tồn tại)

    b) Hàm gỡ bỏ một hàm lọc
    Visual C++ Code:
    1. BOOL UnhookWindowsHookEx(HHOOK hHook)
    Tham số:
    + hHook: handle của hook cần gỡ bỏ (chính là giá trị được trả vể bởi hàm SetWindowsHookEx khi cài đặt)

    c) Hàm gọi hàm lọc kế tiếp trong chuỗi
    Như đã nói ở trên, hệ thống duy trì một chuỗi các hàm lọc cho mỗi loại sự kiện. Do đó, sau khi một hàm lọc thực hiện xong “nghĩ vụ” của mình, nó sẽ gọi hàm lọc tiếp theo trong chuỗi. Nhờ đó, người ta có thể cài nhiều Hook tại một sự kiện.
    Visual C++ Code:
    1. LRESULT CallNextHookEx(HHOOK hHook, int nCode, WPARAM wParam, LPARAM lParam)
    Ý nghĩa tham số:
    + hHook: là handle của hook hiện hành
    + nCode : hook code để gởi đến hook kế tiếp
    + wParam và lParam: chứa thông tin mở rộng của thông điệp

    Nhứt đầu quá! Ví dụ đi

    Ví dụ dưới đây (có file đính kèm) sẽ minh họa cách cài một cái Hook đơn giản bằng Visual C++: chặn bắt sự kiện nhấn phím. Khi chạy, chương trình sẽ “tàng hình” (chỉ nhìn thấy trong Task Manager). Nếu người dùng “lỡ tay” nhấn phím Enter (ở bất kỳ đâu) sẽ bị “bắn” ra một cái message box có dòng chữ “sonhn say hello” (rất dễ nổi khùng!).

    1. Bước 1:

    Trước hết, tạo một Project VC++ Win32 DLL (xin đừng nhầm với dạng DLL Class Library) với 1 file duy nhất có nội dung như sau:

    File “TestDLL.cpp”

    Visual C++ Code:
    1. #include <windows.h>
    2.  
    3. //vùng nhớ dùng chung, chứa biến handle của Hook
    4. #pragma data_seg("SHARED_DATA")
    5. HHOOK   hGlobalHook = NULL;
    6. #pragma data_seg()
    7.  
    8. //hàm lọc sự kiện nhấn phím
    9. __declspec(dllexport) LRESULT CALLBACK FillKeyboard(int nCode, WPARAM wParam, LPARAM lParam)
    10. {
    11.     //nếu sự kiện là nhấn phím và mã phím là Enter
    12.     if ((nCode == HC_ACTION) && (wParam == 13))
    13.     {
    14.         MessageBox(0, "sonhn say hello :)", "Hello", 0);
    15.         return 1;
    16.     }
    17.  
    18.     //gọi Filter Function kế tiếp trong chuỗi các Filter Function
    19.     return CallNextHookEx(hGlobalHook, nCode, wParam, lParam);
    20. }
    21.  
    22. //hàm ấn định biến hGlobalHook tại vùng nhớ dùng chung
    23. __declspec(dllexport) void SetGlobalHookHandle(HHOOK hHook)
    24. {
    25.    hGlobalHook = hHook;
    26. }

    Đoạn code trên có 3 điểm cần chú ý:
    - Đoạn khai báo vùng nhớ dùng chung “SHARED_DATA”: vùng nhớ này chứa handle của Hook được cài. Sở dĩ nó phải được đặt trong vùng nhớ dùng chung vì cả 2 chương trình (DLL và EXE) đều cần truy phải truy xuất vào.
    - Hàm lọc sự kiện nhấn phím: hàm này thực hiện nhiệm vụ: khi sự kiện là nhấn phím (nCode == HC_ACTION) và phím được nhấn là Enter (wParam == 13) thì “bắn” cái message box ra.
    - Hàm ấn định biến hGlobalHook: hàm này sẽ được gọi bởi chương trình EXE sau đây để ấn định handle cho Hook mà nó sẽ cài.

    Tạo file Module-Definition (.def) có nội dung như bên dưới để export các hàm bên trong file DLL và khai báo vùng nhớ dùng chung.

    File “TestDLL.def”

    Visual C++ Code:
    1. LIBRARY "TestDLL"
    2. EXPORTS
    3.     FillKeyboard
    4.     SetGlobalHookHandle
    5.     SECTIONS
    6.  
    7. SHARED_DATA Read Write Shared

    Biên project trên để thu được file “TestDLL.dll”

    2. Bước 2:

    Tạo project VC++ dạng Win32 Application để thực hiện cài hàm lọc (trong file TestDLL.dll) vào sự kiện nhấn phím.

    File “TestHook.cpp”

    Visual C++ Code:
    1. #include <windows.h>
    2.  
    3. //các biến toàn cục
    4. HHOOK hHook = NULL;     //handle của hook
    5. HMODULE hDll;           //handle của DLL
    6.  
    7. //định nghĩa con trỏ hàm SetGlobalHookHandle() trong file DLL
    8. typedef VOID (*LOADPROC)(HHOOK hHook);
    9.  
    10. BOOL InstallHook();
    11.  
    12. int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
    13. {
    14.     //cài đặt hook, exit nếu thất bại
    15.     if (InstallHook() == FALSE)
    16.     {
    17.         MessageBox(0, "Can not install hook!", "Error", 0);
    18.         return -1;
    19.     }
    20.  
    21.     MSG msg;
    22.     BOOL bRet;
    23.     //vòng lặp đón và xử lý các message
    24.     while((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
    25.     {
    26.         TranslateMessage(&msg);
    27.         DispatchMessage(&msg);
    28.     }
    29.  
    30.     return 0;
    31. }
    32.  
    33. //Hàm cài đặt hook
    34. BOOL InstallHook()
    35. {
    36.     //load file DLL
    37.     if (LoadLibrary("TestDLL.dll") == NULL)
    38.     {
    39.         MessageBox(0, "Can not load DLL file.", "Error", 0);
    40.         return FALSE;
    41.     }
    42.  
    43.     //lấy handle của file DLL
    44.     HMODULE hDLL = GetModuleHandle("TestDLL");
    45.  
    46.     //exit nếu load DLL không thành công
    47.     if (hDLL == NULL)
    48.     {
    49.         return FALSE;
    50.     }
    51.  
    52.     //cài đặt hook, phạm vi toàn cục
    53.     hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)GetProcAddress(hDLL,"FillKeyboard"), hDLL, 0);
    54.  
    55.     //exit nếu cài đặt Hook không thành công
    56.     if (hHook == NULL)
    57.     {
    58.         return FALSE;
    59.     }
    60.  
    61.     //lấy địa chỉ hàm SetGlobalHookHandle() trong file DLL
    62.     LOADPROC fPtrFcnt;
    63.     fPtrFcnt = (LOADPROC)GetProcAddress(hDLL, "SetGlobalHookHandle");
    64.     if (fPtrFcnt == NULL)
    65.     {
    66.         return FALSE;
    67.     }
    68.  
    69.     //ấn định handle của hook vào vùng nhớ dùng chung (giữa DLL và ứng dụng này)
    70.     fPtrFcnt(hHook);
    71.    
    72.     return TRUE;
    73. }

    Đừng hốt hoảng vì độ dài của nó. Đọc kỹ 1 chút, bạn sẽ hiểu được vấn đề vì chúng đã được ghi chú khá chi tiết rồi.

    Chú ý:
    - Nhằm đơn giản hóa quá trình cài đặt, ví dụ này không có thao tác gỡ bỏ Hook. Khi chạy, chương trình sẽ “tàng hình” và muốn tắt nó, bạn phải “end task” nó bằng Task Manager(!). Lúc đó, Hook cũng sẽ được tự động được gỡ bỏ.
    - Nếu bạn muốn cài đặt thao tác gỡ bỏ Hook, hãy viết thêm đoạn code xử lý sự kiện WM_DESTROY và gọi hàm dưới đây:
    Visual C++ Code:
    1. //hàm gỡ bỏ hook, với hHook là handle của hook
    2. BOOL UninstallHook()
    3. {
    4.     if ((hHook != 0) && (UnhookWindowsHookEx(hHook) == TRUE))
    5.     {
    6.         hHook = NULL;
    7.         return TRUE;
    8.     }
    9.     return FALSE;
    10. }

    Kết luận

    Hook là một kỹ thuật tương đối khó và kiến thức về nó cũng khá rộng, khó có thể trình bày hết trong phạm vi một bài viết. Các bạn quan tâm có thể tìm hiểu thêm trên website http://msdn.microsoft.com và các tài liệu liên quan. Chúc thành công.
    Đã được chỉnh sửa lần cuối bởi sonhn : 08-10-2007 lúc 08:54 AM.

  2. #2
    Ngày gia nhập
    10 2008
    Bài viết
    13

    Về phần hook mình không làm được phần hook hàm của win, đại loại kiểu hook để làm click&see ấy, bạn nào có kinh nghiệm có thể hướng dẫn mình phần này được không

  3. #3
    Ngày gia nhập
    06 2007
    Nơi ở
    C:\WINDOWS\system32\dllcache\
    Bài viết
    3,006

    Xin tổng hợp thêm tài liệu sưu tầm ở đây
    Tài liệu sưu tầm trên blog của Benina
    001 : _http://rootbiez.blogspot.com/2009/11/programming-co-che-hook-va-ung-dung-to.html

    [Programming] Cơ chế Hook và ứng dụng to lớn của nó

    Cơ chế Hook và ứng dụng to lớn của nó

    Author: griever

    Link: _http://cnpmk51-bkhn.org/forums/archive/index.php?t-622.html

    Bài 1:

    hnay đang kiếm guide để làm autoPlay cho vltk thì đc biết đến cái này, ngồi 1 lúc ngẫm ra mấy thứ lên đây chia sẻ cùng a e.

    chắc mọi người đã nghe qua nhiều về nó, ví dụ làm Vietkey hay Unikey đều phải sử dụng cơ chế này

    ngoài ra cơ chế này còn sử dụng để làm keylogger, autoPlay của các game oline...

    đầu tiên, em xin nói qua về cơ chế thông điệp của Windows
    - khi 1 sự kiện xảy ra tại cửa sổ con (click chuột, gõ 1 phím...), HDH sẽ phát ra 1 thông điệp tới cửa sổ cha, ở đây thông điệp sẽ đc hàm xử lý của cửa sổ cha xử lý. Tùy theo thông điệp đc gửi đến là gì và mục đích của người lập trình mà cách thức xử lý sẽ khác nhau.

    - hàm xử lý là hàm dùng để xử lý các thông điệp đã đc chặn, bắt

    - có thể tác động đến 1 cửa sổ bất kỳ bằng cách gửi thông điệp đến chính cửa sổ đó. Khi đó nó sẽ tiếp nhận thông điệp này và thực hiện các yêu cầu của thông điệp.
    khi học các hàm API mọi người sẽ nghĩ ngay đến hàm SendMessage( )
    - 1 cửa sổ có thể là 1 button, 1 edit text, 1 hộp thoại,...

    tiếp theo, em xin nói về bản chất của Hook (đây là theo "ngu ý" của em thôi): Hook thực chất là bẫy & sửa các thông điệp của 1 cửa sổ bất kỳ trước khi các thông điệp này đc hàm xử lý của cửa sổ đó xử lý.
    nói cách khác, khi HDH gửi 1 thông điệp đến cửa sổ cha, Hook sẽ chặn các thông điệp này, thay đổi chúng...tùy theo mục đích của người lập trình.

    ví dụ khi 1 ứng dụng đc khởi động (ví dụ Yahoo chẳng hạn), thông điệp WM_CREATE sẽ đc phát ra, khi đó nếu ta dùng Hook để bắt lấy thông điệp này và thay nó bằng thông điệp WM_DESTROY thì ứng dụng đó hiển nhiên sẽ tự động exit! (giống kavo đó)

    vậy tại sao dùng cơ chế chặn bắt thông thường kô đc???:Smlie (105):
    sẽ có ý kiến cho rằng có thể dùng hàm SendMessage( ) để gửi thông điệp WM_DESTROY tới 1 ứng dụng nào đó để làm cho ứng dụng đó tự exit! Hoặc gửi thông điệp WM_GETTEXT tới 1 edit text để lấy đc nội dung của edit text đó! Như vậy thì làm keylogger hay virus sẽ đơn giản hơn bao nhiêu
    nếu ai đã biết về hàm này thì sẽ biết là hàm này yêu cầu HANDLE của cửa sổ tiếp nhận thông điệp, hiểu nôm na thế này: thông điệp là 1 gói bưu kiện, HDH là ngưởi gửi bưu kiện, cửa sổ đang đc nói đến là người nhận bưu kiện, HANDLE của cửa sổ là dịa chỉ của ngươi nhận, còn hàm SendMessage( ) là công ty chuyển phát.
    tuy nhiên có những lúc ta kô chỉ cần hàm sendmessage này, bởi hàm này chỉ có tác dụng gửi thông điệp đi chứ kô có tác dụng "nghe" thông điệp được gửi đến cho chương trình.

    do đó, sử dụng cơ chế chặn bắt thông thường thì hầu như là vô tác dụng! => phải dùng Hook thôi

    cách cài đặt Hook thì em kô nói ở đây vì kiếm trên google chắc không thiếu

    bài sau em xin nói về 1 ứng dụng quan trọng khác của Hook đó là "lây" code (inject code) vào 1 ứng dụng bên ngoài

    Bài 2:

    h em xin trình bày 1 ứng dụng quan trọng khác của Hook là làm keylogger hay malware!
    phần này em nêu ra chỉ với mục đích "cọ xát" và "học hỏi" là chính, vì bản thân em cũng mới tìm hiểu về lĩnh vực này thôi, và cũg chưa hiểu tường tận lắm

    Keylogger chắc ai cũg biết , đó là phần mêm dùng để thu thập thông tin vê password hay ID bằng cách bắt các thông điệp về bàn phím. ở đây em xin nói đến 1 loại keylogger khác, mục đích cũng giống như trên nhưng cách làm "nông dân" hơn. đó là lấy thông tin của edit text chứa pass hay ID.

    cách làm của Keylogger này khá là đơn giản, giống cách làm các loại autoplay bây h.
    trước hết cần phải quy ước 1 số thuật ngữ
    remote process: là ứng dụng chứa thông tin về pass và ID. Hoặc với các loại autoplay thì nó chính là các loại game oline(vltk...)

    cách thức chung của các loại này là chèn ("lây") code vào remote process. Gọi là "lây" bởi vì đoạn code đó thực sự kô nằm trong remote process. chỉ là remote process sẽ thực hiện các đoạn code đã bị "lây nhiễm". Có 3 cách để lây nhưng em xin chỉ nói 1 cách dùng Hook thôi, đó là:
    + đặt code cân thiết vào 1 file DLL, nghĩa là khi viết 1 hàm thay vì ta viết hàm đó vào chương trinh chính thì ta viết trong DLL.
    + dùng Hook để map file DLL của ta vào remote process.

    Sau khi đã thực hiện 2 bước trên thì remote process sẽ tự thực hiện các lệnh đã có trong đoạn code của ta:rolleyes:
    --2 bước trên thực hiện đêu rất dễ--cách viết DLL thì search trên mạng kô thiếu và thực hiện cũng khá dễ

    đến đây thì có 2 hướng là keylogger và autoplay.
    trước tiên nói về keylogger:
    mục đích của keylogger là thu thập pass và ID, vậy trong file DLL ta chỉ cần xài hàm SendMessage là đủ. Khi đó ta tạo ra 1 hàm đệm trong DLL có các nhiệm vụ sau: cài Hook vào remote process, gọi hàm SendMessage với thông điệp WM_GETTEXT để lấy thông tin về pass và ID , bỏ cài đặt Hook (bước này giống như khi ta giải phóng 1 bộ nhớ đã đc cấp phát)

    sau đây là cách thức thực hiện chi tiết:
    B1: tạo 1 DLL có hàm đệm như trên
    B2: tạo chương trinh chính có các chức năng như sau:
    + lấy handle của edit text chứa pass hay ID
    + link chương trinh chính này với DLL ở trên (khi link 1 DLL với 1 ứng dụng thi ứng dụng sẽ sử dụng đc các hàm trong DLL--đó là lí do tại sao nó đc gọi la DLL - thư viện liên kết động)
    + gọi hàm đệm trong DLL ra. Đến đây thì ta đã lấy đc pass và ID rồi , chỉ việc gửi pass này về mail của mình là xong

    B1 thì kô nói làm j nữa
    B2 có các chú ý sau:
    - làm sao lây đc handle của edit text chứa pass hay ID đây???
    trong sample em down trên mạng vê thì nó dùng cơ chế bắt chuột. Khi con chuột rê đến đâu nó sẽ dùng 1 số hàm API để định vị cửa sổ mà con chuột đang chỉ đến, vì muốn gõ pass thì các pác phải click chuột vào cái khung password, khi đó con chuột chỉ đúng edit text chứa password tuy nhiên theo kinh nghiệm của em thi cái đó rất hiếm xảy ra. Khi gõ ID xong thì các pác gõ pass thế nào, dùng chuột click vào khung password hay ấn tab? Em nghĩ là 80% là ấn Tab => làm keylogger theo cách này không ổn lắm, em cũng chỉ đưa ra loại này để làm quen hơn với Hook thôi. Pác nào quan tâm làm thử cũng hay

    dưới đây là đoạn code dùng để định vị cửa sổ:

    Visual C++ Code:
    1. HWND SmallestWindowFromPoint( const POINT point )
    2. {
    3.     RECT rect, rcTemp;
    4.     HWND hParent, hWnd, hTemp;
    5.  
    6.     hWnd = ::WindowFromPoint( point );
    7.     if( hWnd != NULL )
    8.     {
    9.         ::GetWindowRect( hWnd, &rect );
    10.         hParent = ::GetParent( hWnd );
    11.  
    12.         // Has window a parent?
    13.         if( hParent != NULL )
    14.         {
    15.             // Search down the Z-Order
    16.             hTemp = hWnd;
    17.             do{
    18.                 hTemp = ::GetWindow( hTemp, GW_HWNDNEXT );
    19.  
    20.                 // Search window contains the point, hase the same parent, and is visible?
    21.                 ::GetWindowRect( hTemp, &rcTemp );
    22.                 if(::PtInRect(&rcTemp, point) && ::GetParent(hTemp) == hParent && ::IsWindowVisible(hTemp))
    23.                 {
    24.                     // Is it smaller?
    25.                     if(((rcTemp.right - rcTemp.left) * (rcTemp.bottom - rcTemp.top)) < ((rect.right - rect.left) * (rect.bottom - rect.top)))
    26.                     {
    27.                         // Found new smaller window!
    28.                         hWnd = hTemp;
    29.                         ::GetWindowRect(hWnd, &rect);
    30.                     }
    31.                 }
    32.             }while( hTemp != NULL );
    33.         }
    34.     }
    35.  
    36.     return hWnd;
    37. }

    như vậy là xong thằng keylogger

    à, ở bài trước em có nhầm 1 chút: hàm SendMessage có thể gửi thông điệp đi bất kỳ đâu, kô cứ phải trong cùng 1 ưng dụng, nó chỉ kô thể gửi thông điệp WM_GETTEXT tới 1 cửa sổ có đặt flag ES_PASSWORD mà nằm ngoài ứng dụng gọi nó thôi, do vậy nếu ta chỉ dùng hàm SendMessage với mục đích khác thì kô cần xài Hook đâu.

    (*)flag ES_PASSWORD có tác dụng biến các ký tự nhập vào thành 1 ký tự đặc biệt duy nhất, thông thường là * ví dụ các pác gõ abcd thì nó hiển thị là ****

    còn thằng autoplay em cũng chưa tim hiểu nhiêu nên chưa dám nói , hẹn hum sau sẽ nói tiếp

    cái này ngoài lề thôi, nó kô liên quan j đến Hook cả, nhưng nó sẽ giúp các pác tìm HANDLE 1 cách dễ dàng
    _http://msdn.microsoft.com/en-us/magazine/cc301495.aspx

    Bài 3:

    tiếp theo bài này em xin viết về cơ chế của AutoPlay

    các pm autoplay bây h đọc thông tin từ bộ nhớ game và dùng hook để thay đổi các thông tin cần thiết.

    đến đây em chia thành 2 phần, phần 1 là đọc thông tin từ bộ nhớ game, phần 2 là ghi thông tin.

    phần 1: đọc thông tin game
    phần này thì kô cần dùng đến Hook vì bản thân ta kô tác động gì đến game cả ("đọc" theo đúng nghĩa đen của nó)
    các thông tin trong bộ nhớ game được lưu trong các địa chỉ (có thể tĩnh hoặc động), vấn đề ở đây chỉ là làm sao "lấy" được các địa chỉ cần thiết và "đọc" nó như thế nào???
    để "lấy" đc các địa chỉ cần thiết là rất phức tạp, phải biết debug game may ra mới lấy đc :( nhưng ma thay các việc đó đã có các bậc tiền bối của chúng ta lo roài , thay vì phải ngồi hàng h để debug game, ta có thể search trên mạng trong vài giây là dc

    lấy ví dụ với game VLTK, điều ta quan tâm nhất là thông tin vê char và mob (quái vật)

    Tất cả các nhân vật (người chơi, quái, Npc...) được lưu thông tin trong mảng gồm 256 đối tượng, mỗi đối tượng kích thước 0x7E4C, địa chỉ lưu địa chỉ của mảng trên là 0x00D3A570. Đối tượng 0 để trống, đối tượng 1 là người chơi, còn lại là quái & Npc.

    như vậy ta đã có đc các địa chỉ cần thiết rồi (trong mảng trên thì phần tử a[1] lưu thông tin về người chơi, các phân tử còn lại(từ a[2] đến a[255]) lưu thông tin về mob. mảng a có địa chỉ tĩnh là 0x00D3A570).
    với các game khác thi các pác tự search trên google sẽ thấy thôi

    bi h đến bước thứ 2 là đọc dữ liệu như thế nào?
    tất cả các nhân vật(người chơi, mob, npc...) đều thuộc cùng 1 lớp chung(không biết các nhà lập trình game gọi thế nào nhưng em tạm gọi đó là class CNpc
    cấu trúc của class này tùy từng game sẽ khác nhau, ở đây em xin lấy ví dụ về game VLTK

    Visual C++ Code:
    1. class CNpc
    2. {
    3.     ...
    4.     DWORD m_NpcKind; //0x0020 1=mod, 1=player,...
    5.     ...
    6.     DWORD m_Doing; //0x00EC 1=stand, 2=walk, 3=run...
    7.     ...
    8.     int m_CurLife; //0x0B44
    9.     int m_CurLifeMax;
    10.     int m_CurLifeRep;
    11.     int m_CurMana;
    12.     int m_CurManaMax;
    13.     ...
    14.     BOOL m_bRideHorse; //0x0D58
    15.     char Name[32]; //0x0D5C Tên nhân vật
    16.     int m_n***;
    17.     ...
    18.     BOOL m_FightMode; //0x0EAC
    19.  
    20. };

    vi kô tìm đc class đầy đủ nên các pác cần chú ý tới các địa chỉ OFFSET của từng biến thành viên để việc đọc dữ liệu đc chính xác, với các biến thành viên trên thì cũng đã đủ dùng để làm 1 autoplay tự buff máu, mana, và tự rao bán đồ.
    để đọc dữ liệu trong bộ nhớ game các pác dùng hàm API ReadProcessMemory

    nói qua về hàm này, hàm này có 5 tham số, tham số đâu tiên là handle của game vltk, tham số thứ 2 là địa chỉ offset mà ta sẽ lấy ra để đọc, tham số thứ 3 là buffer ta dùng để đọc dữ liệu, tham số thứ 4 là kích thước của dữ liệu ta sẽ đổ vào buffer, tham số cuối kô cân xét, cứ để NULL

    sau đây là các bước thực hiện chi tiết
    B1: tìm handle của process của game vltk, các pác có thể tham khảo cách tìm process ở đây: http://www.codeproject.com/KB/threads/getprocessid.aspx
    B2: dùng hàm ReadProcessMemory để đọc các thông tin cần thiết
    đây là ví dụ với game vltk

    Visual C++ Code:
    1. CNpc Npc;
    2. LPBYTE lpBaseAdd, lpCurAdd;
    3. //đọc địa chỉ đầu của mảng Npc
    4. ReadProcessMemory(m_hVLProcess, (LPBYTE)NPC_BASE_ADD, (LPVOID)&lpBaseAdd, 4, NULL);
    5. //tính toán địa chỉ của ngươi chơi (PLAYER_INDEX = 1)
    6. lpCurAdd = lpBaseAdd + PLAYER_INDEX*NPC_DATA_SIZE;
    7. //đọc thông tin vê ngươi chơi
    8. ReadProcessMemory(m_hVLProcess, lpCurAdd, (LPVOID)&Npc, sizeof(CNpc), NULL);
    9. ...

    trong đó m_hVLProcess là handle của process của game.
    lpBaseAdd là địa chỉ của mảng Npc, lpCurAdd là địa chỉ hiện tại, có đc băng cách + lpBaseAdd với (chỉ số trong mảng)*NPC_DATA_SIZE, NPC_DATA_SIZE chính là kích thước của 1 đối tượng trong mảng = 0x7E4C (đã nói ở trên), ở đây nếu muốn đọc thông tin của player thi + lpBaseAdd với 1*0x7E4C (do chỉ số của player trong mảng = 1)

    như vậy la ta đã có thông tin cơ bản về người chơi, mob...
    ngoài ra có thể sử dụng thêm 1 vài hàm API để rao, bán đồ

    Visual C++ Code:
    1. void PostChatMessage(LPCTSTR szChatMsg)
    2. {
    3.     //đặt focus cho cửa sổ chat
    4.     ::PostMessage(m_hVLWin, WM_KEYDOWN, VK_RETURN, 0x001C0001);
    5.     //Xóa nội dung trong cửa sổ chat
    6.     ::PostMessage(m_hVLWin, WM_KEYDOWN, VK_DOWN, 0x00500001);
    7.     while (szChatMsg[0])
    8.     {
    9.     ::PostMessage(m_hVLWin, WM_CHAR, LOBYTE(szChatMsg[0]), 0);
    10.     szChatMsg++;
    11.     }
    12.     //đặt focus cho cửa sổ game
    13.     ::PostMessage(m_hVLWin, WM_KEYDOWN, VK_RETURN, 0x001C0001);
    14. }

    ở đây m_hVLWin là handle của game vltk(khác với handle của process đấy nhé)

    để tìm handle của game vltk thì các pác chỉ cần dùng hàm FindWindow( )

    như vậy đến đây là quá đủ để làm cái AutoSell + AutoBuff máu& mana rồi đó!

    Bài 4:

    về vấn đề lấy Handle của cửa sổ và process (tiến trình) ở phần trên em viết hơi rời rạc, để em tổng hợp lại cho các pác tiện theo dõi

    I. Lấy handle của cửa sổ chính (main window)
    - dùng hàm FindWindow( ) là đơn giản nhất, để sử dụng hàm này chỉ cần biết đc title của cửa sổ đó, ví dụ đối với game vltk thì title là Vo Lam Truyen Ky

    II. Lấy handle của cửa sổ con, ví dụ lấy handle của edit text chứa password trong game vltk

    ở đây ta dùng hàm FindWindowEx( )
    prototype: FindWindowEx(handle của CS cha, handle của cửa sổ con, tên của class của cửa sổ con, tên của cửa sổ con)

    các pác để ý tham số thứ 2, đây chính là handle của cửa sổ con trước đó tính theo trục Z, nếu để NULL thì máy sẽ tìm từ trên cùng xuống dưới.
    Vì trong các cửa sổ con có thể có thêm cửa sổ con (con của con) nữa vì thế cần 1 vòng lặp while ở đây để duyệt tất cả các cửa sổ con nằm trong main window.
    ví dụ ở đây cần tìm các cửa sổ con thỏa mãn đk chúng là edit text.

    B1 tìm handle của main window

    B2 duyệt lần lượt các cửa sổ con ( có lẽ phải dùng đệ quy )

    Visual C++ Code:
    1. HWND hMainWnd; //lưu handle của cửa sổ chính
    2. HWND hTemp; //lưu handle của 1 cửa sổ con
    3.  
    4. hMainWnd = FindWindow(NULL,"Vo Lam Truyen Ky");
    5. if (hMainWnd == NULL) //chưa khởi động game???
    6. return;

    h thì phần duyệt các pác tự làm, vì em kô rành vê đệ qui lắm :(, nhưng nhớ dùng hàm FindWindowEx để tìm các cửa sổ con.
    Về phần duyệt đệ qui thì nó cũng giống như duyệt cái Window Explorer thôi, cứ duyệt lần lượt từ trên xuống dưới, duyệt đến đâu lại tim cửa sổ con của nó...

    III.lấy handle của 1 process(tiến trình)
    tham khảo tại đây
    _http://www.codeproject.com/KB/threads/getprocessid.aspx

    uốn chặn một cách đầy đủ, chính xác
    ^_,^

    Tổng hợp các câu chuyện hài hước vui nhộn, sử dụng Speech Synthesis để đọc : https://www.youtube.com/channel/UCLk...Tjrg/playlists


    Bùi Tấn Quang

  4. #4
    Ngày gia nhập
    06 2007
    Nơi ở
    C:\WINDOWS\system32\dllcache\
    Bài viết
    3,006

    Tài liệu sưu tầm trên blog của Benina

    002: _http://rootbiez.blogspot.com/2009/11/programming-ung-dung-co-che-hook-phan-1.html

    [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.

    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)

    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.

    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 :

    Visual C++ Code:
    1. VOID keybd_event(
    2.     BYTE bVk, // virtual-key code
    3.     BYTE bScan, // hardware scan code
    4.     DWORD dwFlags, // function options
    5.     ULONG_PTR dwExtraInfo // additional keystroke data
    6. );

    Ý 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_EXTENDEDKEY


    Scan 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ử 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ộ 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 :

    Visual C++ Code:
    1. HHOOK SetWindowsHookEx(
    2.     int idHook, // hook type
    3.     HOOKPROC lpfn, // hook procedure
    4.     HINSTANCE hMod, // handle to application instance
    5.     DWORD dwThreadId // thread identifier
    6. );

    Ý 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

    · 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.

    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_JOURNALPLAYBACK


    Global

    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ớ.

    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.

    · Gọi Filter Function kế tiếp trong chuỗi các Filter Function Khi một hook được cài đặt, Windows gọi hàm đầu tiên trong chuỗi các Filter Function, và kể từ thời điểm này, trách nhiệm Windows không còn nữa. Filter Function hiện hành phải đảm bảo với hệ thống là có được lời gọi đến hàm kế tiếp trong chuỗi các Filter Function. Bởi lẽ, có thể có một ứng dụng khác cũng cài đặt cùng loại hook để thi hành một số tác vụ nào đó, và nếu như ta không cho Filter Function của ứng dụng này tham gia xử lý sự kiện, sẽ có vấn đề rắc rối xảy ra. Vấn đề sẽ càng trở nên nghiêm trọng nếu ứng dụng này là một chương trình thuộc hệ thống, và rõ ràng sẽ không có gì đảm bảo cho sự an toàn của hệ thống chúng ta. Để giải quyết vấn đề trên, hãy sử dụng hàm CallNextHookEx, khai báo của nó như sau :

    Visual C++ Code:
    1. LRESULT CallNextHookEx(
    2.     HHOOK hhk, // handle to current hook
    3.     int nCode, // hook code passed to hook procedure
    4.     WPARAM wParam, // value passed to hook procedure
    5.     LPARAM lParam // value passed to hook procedure
    6. );

    Tham số đầu tiên là handle của hook hiện hành, giá trị này có thể lấy được từ hàm SetWindowsHookEx

    khi cài đặt hook. Ba tham số tiếp theo là những giá trị mà Windows truyền cho Filter Function.Trong một

    số tình huống, Filter Function hiện hành có thể không muốn chuyển sự kiện cho Filter Function khác

    trong cùng một chuỗi. Lúc này, nếu loại hook ta đang cài đặt cho phép huỷ bỏ sự kiện, và Filter Function

    của ta cũng có cùng quyết định là hủy bỏ, nó sẽ không phải gọi hàm CallNextHookEx.

    Có một thực tế mà các lập trình viên buộc phải chấp nhận : họ sẽ không thể biết được vị trí Filter Function

    của mình trong chuỗi các Filter Function. Bởi vì khi ta gọi hàm CallNextHookEx, Windows mới là đối

    tượng quyết định xem phải chọn hàm nào để bàn giao sự kiện, và quá trình này dĩ nhiên đã được HĐH

    giấu kín. Do đó, một cách khôn ngoan (và bắt buộc) để đảm bảo an toàn hệ thống là tuân thủ các nguyên

    tắc của HĐH khi cài đặt và sử dụng một hook.

    Filter Function cho Hook toàn cục và DLL Như đã có lần đề cập, khi ta muốn cài đặt một hook toàn cục,

    Filter Function phải là một hàm nằm trong một DLL (Dynamic Link Library). Tuy nhiên, vẫn có một ngoại

    lệ khi ta cài đặt hai hook toàn cục WH_JOURNALRECORD và WH_JOURNALPLAYBACK. Bởi lẽ đối với

    hai loại hook này, Windows gọi Filter Function theo một cách đặc thù, nên hàm này không cần phải nằm

    trong DLL.

    III.4. Giới thiệu hook WH_KEYBOARD và WH_GETMESSAGE

    Bây giờ sẽ là chi tiết của Filter Function gắn với hook WH_KEYBOARD và WH_GETMESSAGE, hai loại hook liên quan có thể được chọn để cài đặt . Filter Function cho hook WH_KEYBOARD

    Windows gọi Filter Function của hook này khi GetMessage hoặc PeekMessage trả về các thông điệp WM_KEYDOWN, WM_KEYUP. Prototype của hàm này có dạng như sau :

    LRESULT CALLBACK KeyboardProc(

    int code, // hook code

    WPARAM wParam, // virtual-key code

    LPARAM lParam // keystroke-message information );

    code : xác định cách thức Filter Function xử lý thông điệp, nó là một trong hai giá trị sau :

    Value


    Meaning

    HC_ACTION


    Thông điệp đã được lấy ra khỏi hàng đợi, wParam và lParam chứa thông tin về thông điệp mà Filter Function nhận được.

    HC_NOREMOVE


    wParam và lParam mang thông tin của thông điệp, và thông điệp vẫn còn nằm trong hàng đợi (Ứng dụng gọi PeekMessage với tuỳ chọn PM_NOREMOVE).

    Nếu code là giá trị âm, Filter Function phải chuyển thông điệp cho Filter Function khác bằng cách gọi CallNextHookEx, rồi trả về giá trị được trả về bởi hàm CallNextHookEx.

    wParam : chứa mã phím ảo của phím được nhấn.

    lParam : các thông tin khác về sự kiện gõ phím, các thông tin này đã được mô tả ở các phần trên.

    Filter Function cho hook WH_GETMESSAGE

    Như phần đầu đã giới thiệu, trước khi GetMessage và PeekMessage trả thông điệp về cho ứng dụng, thông điệp sẽ được chuyển cho Filter Function của hook để hàm này có thể thực hiện một số thao tác kiểm tra, chỉnh sửa thông tin của message. Tuy nhiên, Filter Function không thể hủy bỏ thông điệp mà nó nhận được. Đây là nguyên tắc cơ bản khi sử dụng hook WH_GETMESSAGE. Filter Function của hook này có dạng sau :

    Visual C++ Code:
    1. LRESULT CALLBACK GetMsgProc(
    2.     int code, // hook code
    3.     WPARAM wParam, // removal option
    4.     LPARAM lParam // message
    5. );

    code : Filter Function sử dụng giá trị này để quyết định xem mình có phải xử lý message hay không. Nếu code=HC_ACTION, Filter Function phải xử lý thông điệp. Nếu code là một giá trị âm, Filter Function phải chuyển message cho hàm khác bằng cách gọi CallNextHookEx mà không có bất cứ sự thay đổi nào trên thông điệp. Sau đó, nó phải trả về giá trị được trả về bởi hàm CallNextHookEx.

    wParam : giá trị tham số này cho ta biết message mà Filter Function nhận được có được lấy ra khỏi hàng đợi hay chưa. Nó có thể là một trong các giá trị sau

    Giá trị


    Ý nghĩa

    PM_NOREMOVE


    Message vẫn còn nằm trong hàng đợi, điều này đồng nghĩa với việc ứng dụng gọi PeekMessege với tuỳ chọn PM_NOREMOVE .

    PM_REMOVE


    Message đã được lấy ra khỏi hàng đợi, ứng dụng đã gọi

    GetMessage, hoặc PeekMessage với tuỳ chọn PM_REMOVE.

    lParam : trỏ đến cấu trúc MSG chứa thông tin chi tiết về message.

    IV.Cài đặt và sử dụng một hook toàn cục

    Phần này sẽ nêu vắn tắt cách cài đặt và sử dụng một hook toàn cục, chúng ta sẽ xét một bài toán đơn giản sau làm ví dụ minh họa :

    Giả sử ta đang soạn thảo văn bản trong một môi trường nào đó (Notepad, Microsoft Word,...), hãy viết một chương trình theo dõi các phím được gõ của người dùng, nếu là phím ‘0’ ở góc phải bàn phím thì trên màn hình soạn thảo sẽ xuất hiện thêm 2 ký tự ‘d’ và ‘t’.

    Phân tích sự kiện để xác định loại hook phù hợp

    Theo phát biểu của bài toán trên, ta nghĩ ngay đến hook WH_KEYBOARD, bên cạnh đó cũng có một hook có thể làm nên chuyện, đấy chính là hook WH_GETMESSAGE (nên nhớ rằng Filter Function ứng với loại hook này có thể hứng mọi loại thông điệp trả về từ GetMessage hoặc PeekMessage).

    Hướng tiếp cận dễ nhận thấy để giải quyết vấn đề là chặn thông điệp WM_KEYDOWN hoặc WM_KEYUP (không cần chặn cả hai), kiểm tra là phím gì, nếu là phím ‘0’ của bàn phím số thì dùng hàm keybd_event để gởi phím ‘d’ và ‘t’.

    Nếu ta dùng hook WH_KEYBOARD, Filter Function của nó sẽ được gọi bất cứ khi nào xuất hiện message WM_KEYDOWN hoặc WM_KEYUP. Kết quả là nếu ta không phân biệt rõ hai thông điệp này, Filter Function sẽ được gọi 2 lần chỉ với một động tácgõ phím. Mặt khác để phân biệt 2 message này cần phải thực hiện các phép toán trên bit đối với giá trị lParam, đồng thời phải nhớ sự khác nhau giữa lParam của WM_KEYDOWN với lParam của WM_KEYUP. Điều này tương đối là phức tạp.

    Còn nếu ta dùng hook WH_GETMESSAGE, vấn đề sẽ đơn giản hơn, bởi việc phân biệt hai message trên sẽ được thực hiện bằng chính tên của hai thông điệp đó. Hãy nhớ rằng đối với loại hook này, lParam của Filter Function trỏ đến cấu trúc MSG của thông điệp hứng được.

    Với lý giải vừa nêu, chúng ta sẽ chọn hook WH_GETMESSAGE.

    Tiến hành cài đặt DLL chứa Filter Function của hook

    Đây là hook toàn cục, do đó Filter Function phải là một hàm được “export” của một DLL nào đó. Đầu tiên, ta định nghĩa chỉ thị để “export” một hàm :

    #define EXPORT __declspec( dllexport)

    Từ đây, muốn “export” hàm nào, ta chỉ việc dùng lệnh : EXPORT Tên_Hàm(Tham_số_1, Tham_số_2,...)

    Trong DLL sẽ có 3 hàm chính cần phải được “export” :

    · Hàm cài đặt hook

    · Hàm gỡ bỏ hook

    · Filter Function của hook

    Hai hàm đầu được ứng dụng cài đặt hook gọi, còn Filter Function dĩ nhiên sẽ được gọi bởi HĐH Windows. Và sau đây là một phiên bản DLL khả dĩ cho bài toán trên :

    Visual C++ Code:
    1. #include "StdAfx.h"
    2.  
    3. // Định nghĩa chỉ thị EXPORT
    4. #define EXPORT __declspec(dllexport)
    5.  
    6. // Export 2 hàm cài đặt, gở bỏ hook
    7. EXPORT void WINAPI InstallHook();
    8. EXPORT void WINAPI RemoveHook();
    9.  
    10. // Export Filter Function
    11. EXPORT LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lPram);
    12.  
    13. // Handle của DLL
    14. HANDLE hDllInst = NULL;
    15. // Handle của hook được cài đặt
    16. HHOOK hGetMsgHook = NULL;
    17.  
    18. BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
    19. {
    20.     // Lấy handle của DLL
    21.     hDllInst = hModule;
    22.     return TRUE;
    23. }
    24.  
    25. /* Giả lập nhấn phím (Release=FALSE) hay nhả phím (Release = TRUE) phím có mã phím ảo vk */
    26.  
    27. void SendKey(BYTE vk, BOOL Release=FALSE)
    28. {
    29.     if (Release)
    30.         keybd_event(vk, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
    31.     else
    32.         keybd_event(vk, 0, KEYEVENTF_EXTENDEDKEY, 0);
    33. }
    34.  
    35. // Cài đặt hook
    36.  
    37. EXPORT void WINAPI InstallHook()
    38. {
    39.     if (hGetMsgHook == NULL)
    40.     hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE,
    41.     (HOOKPROC)GetMsgProc, (HINSTANCE)hDllInst, 0);
    42. }
    43.  
    44. // Gỡ bỏ hook
    45.  
    46. EXPORT void WINAPI RemoveHook()
    47. {
    48.     if (hGetMsgHook != NULL)
    49.     {
    50.         UnhookWindowsHookEx(hGetMsgHook);
    51.         hGetMsgHook = NULL;
    52.     }
    53. }
    54.  
    55. // Filter Function cho hook
    56.  
    57. EXPORT LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
    58. {
    59.     MSG* p = (MSG*)lParam;
    60.  
    61.     // Chỉ xử lý khi message đã được lấy ra khỏi hàng đợi
    62.     if ( (nCode >= 0) && (wParam == PM_REMOVE) )
    63.     {
    64.         // Thông điệp là WM_KEYDOWN
    65.         if (p->message == WM_KEYDOWN) // Phim được nhấn là phím ‘0’ ở góc phải bàn phím
    66.             if (p->wParam == VK_NUMPAD0)
    67.             {
    68.                 SendKey(0x44); // Gởi phím ‘d’
    69.                 SendKey(0x54); // Gởi phím ‘t’
    70.             }
    71.     }
    72.  
    73.     // Nếu nCode<0>
    74.     // kế tiếp
    75.     return (CallNextHookEx(hGetMsgHook, nCode, wParam, lParam));
    76. }

    Sử dụng DLL và hoàn chỉnh chương trình

    Đầu tiên là định nghĩa chỉ thị IMPORT để “import” một hàm trong DLL dùng cho ứng dụng.

    #define IMPORT __declspec( dllimport )

    Sau đó dùng chỉ thị này để “import” hai hàm : InstallHook và RemoveHook.

    Việc sử dụng hook bây giờ sẽ cực kỳ đơn giản, ta chỉ cần vào hàm xử lý message cho cửa sổ của ứng dụng, chặn message WM_CREATE và WM_DESTROY như đoạn code sau minh họa :

    Visual C++ Code:
    1. case WM_CREATE :
    2.     InstallHook();
    3.     // Các xử lý khác ...
    4.     break;
    5. case WM_DESTROY :
    6.     RemoveHook();
    7.     // Các xử lý khác ...
    8.     break;

    Sau khi cài đặt hook, việc gọi Filter Function là trách nhiệm của Windows, nhiệm vụ duy nhất của ta từ lúc này là chờ người dùng kết thúc ứng dụng thì gỡ bỏ hook.

    Đến đây xem như ta đã giải quyết được bài toán đặt ra. Qua ví dụ này, thiết nghĩ việc cài đặt một hook xem ra không phải là quá khó. Mấu chốt của vấn đề là phải chọn được loại hook phù hợp và nắm các thông tin liên quan đến sự kiện ta m
    ^_,^

    Tổng hợp các câu chuyện hài hước vui nhộn, sử dụng Speech Synthesis để đọc : https://www.youtube.com/channel/UCLk...Tjrg/playlists


    Bùi Tấn Quang

  5. #5
    Ngày gia nhập
    06 2007
    Nơi ở
    C:\WINDOWS\system32\dllcache\
    Bài viết
    3,006

    (Tiếp , Sưu tầm trên blog Benina )

    003: _http://rootbiez.blogspot.com/2009/11/programming-ung-dung-co-che-hook-phan-2.html


    [Programming] Ứng dụng cơ chế Hook - Phần 2

    Ứ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 2

    Phần II : Ứng Dụng Hook Để Xây Dựng Bộ Gõ Tiếng Việt

    I.Đặt vấn đề

    Nhúng tiếng Việt vào các ứng dụng chạy trên nền Windows luôn là vấn đề được nhiều người quan tâm. Và thực tế đã có khá nhiều tổ chức, cá nhân đưa ra các sản phẩm thuộc dạng này. Tính năng chủ yếu của những phần mềm này là cho phép gõ tiếng Việt trong các môi trường soạn thảo của ứng dụng. Bên cạnh đó, một số tác giả còn tích hợp thêm các chức năng khác như chuyển đổi font, kiểm tra chính tả,... Có thể thấy VietWare, VietKey là những phần mềm tiêu biểu cho lĩnh vực này, và trong thực tế, chúng đã đáp ứng được nhu cầu của phần lớn người sử dụng máy vi tính.

    II.Phương án giải quyết cho font ABC (.VnTime,…)

    II.1.Giới thiệu font ABC

    Đây là loại font 8 bit có các ký tự được thiết kế sẵn gồm cả dấu, không giống như loại font 2 byte có các ký tự chữ và ký tự dấu riêng. Bảng mã 8 bit có tất cả 256 ký tự, trong đó có 128 ký tự chuẩn không thể sửa đổi. Tuy nhiên, do tiếng Việt có 146 ký tự nên 128 ký tự còn lại của bảng mã này không đủ chỗ, đó là lý do làm cho font ABC có một số ký tự vi phạm các ký tự điều khiển chuẩn, và phải tách ra làm 2 file font cho chữ thường và chữ hoa của cùng một loại font. Điều này là một trong những điều bất tiện trong việc sử dụng loại font này.

    II.2. Ý tưởng chính

    Hãy bắt đầu bằng tình huống đơn giản sau : người dùng gõ phím ‘a’, tiếp theo là phím ‘1’ và ta đang dùng kiểu gõ VNI. Điều ta mong muốn lúc này là ký tự ‘a’ trên màn hình sẽ biến mất, thay vào đó sẽ là ký tự ‘á’.

    Một ý tưởng tự nhiên là cài đặt một hook WH_GETMESSAGE để hứng thông điệp WM_CHAR. Cách thực hiện như sau :

    · Hứng thông điệp WM_CHAR

    · Kiểm tra ký tự hiện tại là ‘1’, ký tự trước là ‘a’

    · Nếu đúng :

    · Dùng SendMessage gởi WM_CHAR với ký tự xoá

    · Sửa ký tự ‘1’ thành ký tự ‘á’

    · Nếu sai :

    · Không xử lý

    Ở đây phát sinh một vấn đề cần xem xét : có chắc là message WM_CHAR ta gởi sẽ đến được ứng dụng ? Với cách làm trên thì chỉ đúng với các ứng dụng đơn giản như Notepad, bởi thực chất Notepad chỉ là một EDIT_TEXT control được gắn vào một cửa sổ khung. Các thông điệp được gởi sẽ trực tiếp đến ứng dụng. Còn đối với các môi trường soạn thảo lớn và phức tạp hơn như WordPad hay Microsoft Word, chúng nhận thông điệp từ các sự kiện của người dùng qua nhiều cấp, do đó khi ta gởi message đến, không có gì đảm bảo message của chúng ta sẽ đến chính xác nơi cần đến.

    Còn bây giờ hãy xét cách tiếp cận sau đây : thay vì dùng các hàm để gởi thông điệp đến ứng dụng, ta sẽ làm phát sinh thông điệp một cách tự nhiên hơn, rồi sau đó thực hiện sự sửa đổi trên thông điệp để được ký tự có dấu mong muốn. Vẫn xét tình huống trên, cách làm bây giờ như sau :

    · Hứng message WM_KEYDOWN

    · Kiểm tra phím hiện tại là ‘1’, phím trước là ‘a’ (giá trị mã phím ảo)

    · Nếu đúng :

    · Sửa phím ‘1’ thành phím xóa (mã phím ảo VK_BACK).

    · Nếu sai :

    · Không xử lý.

    · Hứng message WM_CHAR

    · Kiểm tra ký tự hiện tại là ký tự xoá (có mã 8)

    · Nếu đúng:

    ·Dùng keybd_event để giả lập nhấn phím SpaceBar

    ·Nếu sai :

    · Không xử lý

    · Kiểm tra ký tự hiện tại là ký tự khoảng trắng (có mã 32)

    · Nếu đúng :

    · Sửa ký tự này thành ký tự ‘á’.

    · Nếu sai :

    ·Không xử lý

    Tất nhiên quá trình trên cần có một số thao tác với cờ hiệu để phân biệt khi nào phím BackSpace, SpaceBar được người dùng nhấn, khi nào 2 phím này là do chương trình của ta gởi đến. Tuy nhiên, để dễ thấy được ý tưởng chủ đạo của giải thuật vừa nêu, ta đã không đưa vào các thao tác này ở phần trên.

    Giải thích :

    Ý tưởng chính xuyên suốt giải thuật trên là thao tác giả lập gõ phím để phát sinh thông điệp trung gian, sau đó hứng lại thông điệp trung gian và thực hiện sửa đổi để có được ký tự mong muốn. Phím được chọn để giả lập trong trường hợp này là SpaceBar (Về nguyên tắc có thể chọn bất kỳ phím nào cho mục đích này, miễn là phím đó khi gõ có thể cho ta message WM_CHAR với mã ký tự hợp lệ).

    Có thể hình dung sự hoạt động của giải thuật trên như sau :

    · Khi người dùng gõ phím ’a’, Filter Function không làm gì cả.

    · Khi phím ‘1’ được gõ, điều kiện ở nhánh chặn message WM_KEYDOWN được thỏa, phím ‘1’ chuyển thành phím xóa (mã phím ảo VK_BACK) . Chính phím xóa này sẽ thực hiện thao tác xóa chữ ‘a’ trên màn hình soạn thảo.

    · WM_KEYDOWN đã được sửa đổi ở trên được chuyển thành WM_CHAR với mã ký tự 8, thoả điều kiện ở nhánh hứng WM_CHAR, SpaceBar được nhấn nhờ hàm keybd_event.

    · Thông điệp WM_KEYDOWN do phím SpaceBar sinh ra không bị chặn, tiếp tục chuyển thành WM_CHAR với mã ký tự 32. Lúc này, nó được Filter Function hứng lại, và sau khi kiểm tra các điều về cờ hiệu, Filter Function chuyển mã ký tự của message này thành mã ky tự của chữ ‘á’. Kết quả ta có là chữ ‘a’ bị xóa và chữ ‘á’ sẽ xuất hiện trên màn hình.

    Rõ ràng cách làm này an toàn hơn so với cách đầu tiên, bởi lẽ message được phát sinh y hệt như khi ta gõ phím thực sự (do được giả lập bằng hàm keybd_event), và chuyện lấy được các message này là chuyện của ứng dụng (bởi ta đã làm phát sinh message ở mức thấp), ta không cần phải can thiệp bằng lệnh SendMessage như phương án đầu tiên.

    Để thực hiện điều này, dĩ nhiên ta phải tổ chức Filter Function của hook WH_GETMESSAGE để có thể hứng được cả 2 thông điệp WM_KEYDOWN và WM_CHAR.

    II.3.Giải thuật chi tiết và cài đặt

    Dễ nhận thấy việc đầu tiên là phải ghi nhận các mã ký tự của các ký tự có dấu trong font ABC để thực hiện việc sửa đổi thông điệp sau này.

    Ta sẽ dùng tiện ích Character Map trong bộ System Tool của Windows để ghi nhận các ký tự và mã tương ứng của chúng, sau đó lưu các mã này vào một mảng toàn cục được khai báo trong DLL, lấy tên TCVN3 chẳng hạn. Nên tổ chức mảng này theo một thứ tự nhất định, ví dụ các mã của các ký tự có dấu của cùng một nguyên âm gốc nên đi liên tiếp nhau. Sau đó, phải ghi nhớ các chỉ số của các phần tử trong mảng tương ứng với từng ký tự, ví dụ phần tử mảng có giá trị là mã của chữ ‘á’ sẽ có chỉ số là bao nhiêu…

    Đến đây ta cần một biến toàn cục nữa, lấy tên Index chẳng hạn, để lưu các giá trị chỉ số vừa nêu khi người dùng nhấn phím, và các phím này nằm trong tầm vực bỏ dấu của chương trình. Ví dụ, khi họ gõ ‘a’, sau đó gõ ‘1’ thì giá trị của Index là chỉ số của phần tử mảng chứa mã ký tự của chữ ‘á’.

    Cách làm này sẽ tiện lợi khi ta thực hiện sửa đổi trên thông điệp WM_CHAR, rõ ràng lúc này giá trị wParam mới của message sẽ là TCVN3[Index].

    Khi cài đặt hook chặn message, nhánh chặn WM_KEYDOWN sẽ làm nhiệm vụ kiểm tra phím và xác định Index, chính nhánh hứng message WM_CHAR mới thực sự thi hành tác vụ sửa đổi thông tin trên thông điệp để sinh ra các ký tự có dấu.

    Sửa dấu trong khi gõ

    Với những thông tin trên , ta đã giải quyết được tình huống nêu ra ở phần đầu của giải thuật. Tất nhiên khi cài đặt ta sẽ áp dụng tổng quát cho các nguyên âm và các phím có thể sinh dấu từ ‘1’ đến ‘5’ (ta gọi phần này là Module_1) .

    Nhưng nếu người dùng muốn sửa dấu trong khi gõ thì sao ? Ví dụ, họ muốn có chữ ‘à’, nhưng các phím đã vô tình bị gõ là ‘a’ và ‘1’, lúc này trên màn hình xuất hiện chữ ‘á’, bây giờ khi họ gõ ‘2’, chữ ‘á’ phải bị xoá và thay vào đó là chữ ‘à’. Ta hãy xem giải pháp cho bài toán này :

    · Hứng message WM_KEYDOWN

    · Kiểm tra nếu phím hiện tại (CurrentKey) là các phím từ ‘1’ đến ‘5’, phím trước (Prev1Key) cũng cùng dạng này, và phím trước nữa (Prev2Key) là nguyên âm.

    · Nếu đúng :

    · Sửa phím hiện tại thành phím xóa (mã phím ảo VK_BACK)

    · Giả lập gõ 2 phím sau :

    · Prev2Key

    · CurrentKey

    · Nếu sai :

    · Không xử lý

    Giải thích (Xét trong ngữ cảnh tình huống nêu trên):

    Phím ‘2’ được sửa thành phím xóa sẽ thực hiện xóa ký tự ‘á’ trên màn hình. Sau đó 2 phím được giả lập sẽ tạo ra chữ ‘à’ như ta mong muốn. Ở dây ta thấy rõ hơn ý tưởng hứng lại sự kiện để xử lý : chỉ cần xoá ký tự sai, và giả lập gõ chính xác để tạo ký tự đúng. Lúc này chính Module_1 ta đã viết sẽ tiếp nhận các sự kiện giả lập và nó lại thi hành các tác vụ như ta đã mô tả : kiểm tra điều kiện để xác định Index, sửa message,… để cho ra ký tự mà người dùng muốn gõ.

    II.4. Áp dụng cho các luật bỏ dấu khác

    Với các ý tưởng trên, các luật bỏ dấu, sửa dấu trong khi gõ khác (bỏ dấu ô, ơ, ư, â, ă, ấ,…) được thực hiện với cách thức hoàn toàn tương tự. Có khác chăng là các điều kiện kiểm tra giá trị các phím trước phím hiện tại để xác định Index cho phù hợp.

    II.5. Ưu và nhược điểm của giải pháp

    Ta có thể nhận ra ưu điểm của giải pháp này ở tính đơn giản và an toàn của nó. Đơn giản trong quá trình cài đặt, mang tính tái sử dụng ; an toàn trong quá trình phát sinh các message cho ứng dụng.

    Tuy nhiên, cách làm này chỉ áp dụng được dễ dàng cho bài toán bỏ dấu, sửa dấu ngay sau nguyên âm mà thôi. Còn đối phó với vấn đề bỏ dấu tự do (bỏ dấu theo vần, theo từ), phương án này cần các thao tác kiểm tra rất phức tạp trên tập các giá trị phím trước, để xác định chính xác vị trí bỏ dấu. Bởi lẽ, nó phải duyệt trên giá trị của các mã phím ảo để xác định các nguyên âm có dấu, và điều này là rất khó khăn. Ta sẽ có một cách tiếp cận khác ưu việt hơn cho bài toán này ở phần kế sau đây, cách tiếp cận thao tác trực tiếp tên các ký tự.

    III.Phương án giải quyết cho font VNI-Win (VNI-Times,...)

    III.1.Cấu trúc font VNI-Win

    Khác với các font ABC(font 1 byte) có dấu và các nguyên âm được tổ hợp trong cùng một ký tự, font VNI-Win(font 2 byte) có dấu và các nguyên âm rời nhau. chẳng hạn ký tự ‘á’ là sự tổ hợp của hai ký tự ‘a’ và dấu sắc. Tuy nhiên, có một số ký tự do có cấu trúc đặt biệt nên dấu và ký tự được tổ hợp trong một ký tự như: đ, Đ, í, ì, ỉ, ĩ, ị, ỵ, Í, Ì, Ỉ, Ĩ, Ị, Ỵ, ư, ơ, Ư, Ơ.

    Chúng ta hãy bước vào phần sau để xem xét các phương án cho bộ gõ tiếng việt dùng cho font VNI-Win.

    III.2.Kiểu gõ dấu tại chổ

    Như đã đề cập ở phần trước, để xây dựng bộ gõ cho các phần mềm trong môi trường Windows ta sẽ cài đặt một hàm Hook có chức năng chặn ngắt bàn phím ở mức toàn cục. Và khi đó tất cả các phím nhấn từ bàn phím sẽ được chương trình gõ phím của chúng ta xử lý trước khi chúng được gởi tới ứng dụng. Như vậy để gõ được tiếng Việt trong các dụng dụng đang thi hành trong môi trường trong hàm Hook phải thực hiện thao tác thay đổi các ký tự khi người dùng gõ vào, nhờ đó ứng dụng sẽ nhận được các ký tự có dấu tiếng Việt.

    Giải thuật

    Ta sẽ cài đặt hàm Hook WH_GETMESSAGE hứng thông điệp WM_KEYDOWN và WM_CHAR để xử lý. Nhắc lại, khi người dùng nhấn phím ta sẽ lần lượt nhận được các thông điệp WM_KEYDOWN, WM_KEYUP, WM_CHAR. Chương trình ta sẽ xử lý ở thông điệp WM_KEYDOWN và WM_CHAR.

    Nguyên tắc khi người dùng nhấn một phím nằm trong phạm vi là phím bỏ dấu(có khả năng là phím dấu), 0-9 đối với kiểu gõ VNI và “s,f,r,x,j,a,o,e,w,d,z” đối với kiểu gõ TELEX. Ta xét ký tự trước đó, có 3 khả năng xảy ra:

    · Nếu là ký tự có thể bỏ dấu:(a,e,i,o,u,y,d) nếu dấu phù hợp với kí tự cần bỏ dấu (ký tự liền trước) ta tiến hành bỏ dấu.

    · Nếu là một ký tự dấu rời hoặc một ký tự nguyên âm có dấu( chẳng hạn như “í” chỉ là một ký tự còn “á” là hai ký tự) ta xét là nên sửa dấu hay lập dấu và sau đó điều chỉnh lại cho phù hợp.

    · Nếu là một phụ âm thì ta không xử lý gì cả.

    Lưu ý ở đây có điểm khác biệt giửa font 1 byte và font 2 byte, khi bỏ dấu chẳng hạn chử “á” font 1 byte sẽ xóa ký tự “a” trước đó bằng cách sửa ký tự “1” vừa nhận được thành ký tự xóa VK_BACK và sau đó gởi một ký tự “á” còn font 2 byte sẽ gởi 2 ký tự “a” và dấu sắc sau khi xóa ký tự “a” trước đó.

    Bài toán lúc này trở nên đơn giản hơn và vấn đề lúc này là chương trình sẽ xử lý dấu như thế nào cho đơn giản và chính xác. Trước tiên ta cần phân biệt xử lý đối với các lọai dấu khác nhau:

    - loại 1: gồm các dấu: sắc, huyền, hỏi, ngã, nặng.

    - loại 2: gồm các dấu: dấu “^” và dấu “ă”.

    - lọai 3: gồm các dấu: dấu “^” kết hợp với một dấu loại 1(ví dụ: “ấ”, “ồ”, “ể”...)

    - loại 4: gồm các dấu: dấu “ă” kết hợp với một dấu loại 1(ví dụ: “ắ”, “ẵ”, “ặ”...”)

    Bài toán xử lý gõ tiếng việt cho font chữ 2 byte theo kiểu gõ cho phép bỏ dấu ngay đã được giải quyết thành công.

    Hàm Hook xử lý cho bài toán gõ tiếng việt font 2 byte kiểu gõ bỏ dấu tại chổ.

    Visual C++ Code:
    1. EXPORT LRESULT CALLBACK HookProc2ByteNormalVNI(int nCode, WPARAM wParam, LPARAM lParam)
    2. {
    3.     tagMSG *pMsg;
    4.     if (nCode>=0 )
    5.     {
    6.         pMsg = (MSG*)lParam;
    7.         if (wParam == PM_REMOVE)
    8.         {
    9.             if (pMsg->message == WM_KEYDOWN)
    10.             {
    11.                 if (InScope(pMsg->wParam)||(pMsg- >wParam==VK_NUMPAD0&&DeleteTwo))
    12.                 {
    13.                     switch (pMsg->wParam)
    14.                     {
    15.                         case VK_1:
    16.                         case VK_2:
    17.                         case VK_3:
    18.                         case VK_4:
    19.                         case VK_5:
    20.                             ProcessSign1(pMsg->wParam-VK_0);
    21.                             if (Del) pMsg->wParam = 8;
    22.                             break;
    23.                         case VK_6:
    24.                         case VK_8:
    25.                             ProcessSign2(pMsg->wParam-VK_0);
    26.                             if (Del) pMsg->wParam = 8;
    27.                         break;
    28.                         case VK_7:
    29.                             if Prev1Key==VK_o||Prev1Key==VK_O||Prev1Key==VK_u||Prev1Key==VK_U)
    30.                             {
    31.                                 pMsg->wParam = 8;
    32.                                 Del = TRUE;
    33.                                 Flag = TRUE;
    34.                                 switch (Prev1Key)
    35.                                 {
    36.                                     case VK_o:
    37.                                         NextKey = o7;
    38.                                         break;
    39.                                     case VK_O:
    40.                                         NextKey = O7;
    41.                                         break;
    42.                                     case VK_u:
    43.                                         NextKey = u7;
    44.                                         break;
    45.                                     case VK_U:
    46.                                         NextKey = U7;
    47.                                         break;
    48.                                 }
    49.                             }
    50.  
    51.                         break;
    52.                         case VK_9:
    53.                             if (Prev1Key==VK_d||Prev1Key==VK_D)
    54.                             {
    55.                                 pMsg->wParam = 8;
    56.                                 Del = TRUE;
    57.                                 Flag = TRUE;
    58.                                 if (Prev1Key == VK_d) NextKey = a9;
    59.                                 else NextKey = A9;
    60.                             }
    61.                             break;
    62.                         case VK_NUMPAD0:
    63.                             if (DeleteTwo)
    64.                             {
    65.                                 pMsg->wParam = 8;
    66.                                 Del = TRUE;
    67.                                 Flag = TRUE;
    68.                                 DeleteTwo = FALSE;
    69.                             }
    70.                     }
    71.                 }
    72.             }
    73.             if (pMsg->message == WM_CHAR)
    74.             {
    75.                 if (Flag)
    76.                 {
    77.                     switch (pMsg->wParam)
    78.                     {
    79.                         case VK_1:
    80.                         case VK_2:
    81.                         case VK_3:
    82.                         case VK_4:
    83.                         case VK_5:
    84.                         case VK_6:
    85.                         case VK_7:
    86.                         case VK_8:
    87.                         case VK_9:
    88.                             if (!Del)
    89.                             {
    90.                                 pMsg->wParam = NextKey;
    91.                                 Flag =FALSE;
    92.                             }
    93.                             break;
    94.                         case VK_BACK:
    95.                             if (Del) keybd_event(VK_NUMPAD0, 0x45, KEYEVENTF_EXTENDEDKEY, 0);
    96.                             break;
    97.                         case VK_0:
    98.                             if (Del)
    99.                             {
    100.                                 pMsg->wParam = NextKey;
    101.                                 Flag = FALSE;
    102.                             }
    103.                             break;
    104.                     }//of switch
    105.                 }//of if (Flag)
    106.                
    107.                 if (!Del) // Normal
    108.                 {
    109.                     Prev2Key = Prev1Key;
    110.                     Prev1Key = pMsg->wParam;
    111.                 }
    112.                 else
    113.                 {
    114.                     Prev1Key = NextKey;
    115.                     Del=pMsg->wParam ==8;
    116.                 }
    117.             }//of if (pMsg->message==WM_CHAR)
    118.         }
    119.     } // of if (nCode >= 0)
    120.     return CallNextHookEx(hhook, nCode, wParam, lParam);
    121. }

    III.3.2.Kiểu gõ dấu cải tiến:

    Khi gõ theo kiểu bỏ dấu tại chổ có những mặt hạn chế: thứ nhất cách gõ không được tự nhiên thoải mái như khi viết và bỏ dấu bằng tay, thứ hai rất khó cho người dùng sửa dấu sau khi đã đánh sang ký tự khác, thứ ba có thể người dùng sẽ bỏ dấu không đúng chính tả hay không đúng vần. Vấn đề dặt ra ờ đây là làm thế nào để hỗ trợ được những tính năng khiếm khuyết trên.

    Giải pháp gõ dấu sau

    Một ví dụ cụ thể về gõ dấu sau, chẳng hạn như khi người dùng gõ từ “làng” thì họ có thể gõ theo nhiều cách khác nhau như: la2ng, lan2g, lang2.

    · Giải pháp ban đầu

    Giải pháp ban đầu được đưa ra là sử dụng Clipboard, sau khi người dùng gõ một phím có khả năng là phím dấu ta giả lập một phím Shitf và các phím mủi tên ß để đánh dấu khoảng 8 ký tự vừa gõ, sau đó giả lập tổ hợp phím Ctrl X để cắt chuổi 8 ký tự được gõ trước ký tự hiện hành vào Clipboard, tiếp theo ta sẽ lấy dữ liệu ra từ Clipboard và đổ vào mảng ký tự và xử lý dấu trên chúng, dữ liệu được xử lý xong được đưa vào Clipboard, cuối cùng ta sẽ giả lập một tổ hợp phím Ctrl V để dán chuổi vừa xử lý ra màn hình ứng dụng.

    Việc áp dụng Clipboard theo hương tiếp cận này dẫn đến nhiều khiếm khuyết và khó khăn khi xử lý. D không phải tất cả các ứng dụng đều dùng phím Shift và các phím mủi tên để đánh dấu, và dùng phím Ctrl X, Ctrl V để cắt và dán. Hơn nửa, mổi khi một phím trong phạm vi xử lý dấu được nhấn thì ứng dụng phải thực hiện đồng thời 2 thao tác cắt và dán, việc này làm cho chương trình trở chậm chạp, và màn hình của ứng dụng chớp, nhấp nháy trong vùng bị cắt và dán lại. Một ý kiến hay là chỉ nên áp dụng Clipboard cho trường hợp dùng Sửa dấu nhanh(Chức năng này hổ trợ cho người sử dụng khi họ đã hoàn tất văn bản và kiểm tra lại văn bản từ đầu đến cuối một lần và sửa lỗi).

    · Giải pháp khác

    Một giải pháp khác là dùng một buffer thay thế cho Clipboard, tại mỗi thời điểm buffer sẽ lưu giữ giá trị của 8 ký tự(từ dài nhất) trước đó. Khi người dùng gõ một ký tự trong phạm vi bỏ dấu, ta sẽ cập nhật lại buffer cho phù hợp, xóa một số ký tự cần thiết và xuất một số ký tự đã được cập nhật trong buffer.

    Ví dụ:

    Khi gõ : lang giá trị buffer sẽ là:

    l
    a
    n
    g


    Khi gõ thêm ký tự: 2, có nghĩa là bỏ dấu huyền. buffer sẽ được cập nhật lại như sau:

    l
    a
    \
    n
    g

    Và khi đó ta phải xóa ngược lại 3 ký tự(xóa “ang”) và xuất ra 4 ký tự cuối của buffer(xuất “àng”).

    Như vậy mổi lần người dùng gõ dấu ta phải hiệu chỉnh lại buffer và xác định số ký tự cần phải xóa và số ký tự cần xuất ra từ buffer(tính từ vị trí cuối cùng).

    Đối với các ký tự khác không phải bỏ dấu thì ta không xử lý nhưng vẫn phải cập nhật lại trong buffer.

    Ví dụ:

    Các trạng thái của buffer khi gõ chữ “làng”.


    Gõ “l”
    l

    Gõ “a”
    l
    a

    Gõ “n”
    l
    a
    n

    Gõ “g”
    l
    a
    n
    g

    Gõ “2”
    l
    a
    \
    n
    G

    Lúc này DelNum = 3(số ký tự cần xóa) và OutNum =4(số ký tự cần xuất).

    Đến đây thì vấn đề gõ dấu sau được giải quyết khá hoàn hảo. Do sử dụng buffer nên chương trình xử lý nhanh hơn, và tại mổi thời điểm chỉ xóa đi số ký tự tố thiểu cần thiết nên giúp cho màn hình ứng dụng không bị nhấp nháy.

    · Nẩy sinh vấn đề

    Tuy giải pháp trên giải quyết rất tốt vần đề đặt ra nhưng bên cạnh đó lại nẩy sinh ra một khó khăn khác là làm thế nào để xác định vị trí bỏ dấu trong một vần tiếng việt, đây là vấn đề khó khăn và phức tạp bởi vì trong tiếng việt không có quy luật bỏ dấu cụ thể, chuẩn mực. Cuối cùng đưa đến việc xây dựng bộ luật bỏ dấu dành cho tiếng Việt.

    · Bộ luật bỏ dấu tiếng Việt

    Do trong tiếng việt dấu chỉ được bỏ ở các vị trí của các nguyên âm nên bộ luật sẽ được xây dựng dựa trên các nguyên âm, chú ý rằng trong một từ tiếng việt các nguyên âm luôn đi liền nhau và số lượng nguyên âm trong một từ không thể quá 3 nguyên âm. Với hai nhận xét trên giúp ta có thể xây dựng bộ luật bỏ dấu tiếng Việt một cách dễ dàng hơn.

    Phân chia các từ theo số lượng nguyên âm:

    Các từ có 1 nguyên âm:

    Nếu ký tự đứng trước nguyên âm là ký tự “q” hoặc “p” thì không được phép bỏ dấu.

    Ngược lại dấu sẽ được bỏ tại vị trí nguyên âm duy nhất này.

    Các từ có 2 nguyên âm:

    Nguyên âm đầu: a

    A


    vị trí bỏ dấu

    AE


    không có

    AI

    A

    AO


    A

    AU

    A

    AY


    A

    Nguyên âm đầu: e

    E


    vị trí bỏ dấu

    EA


    không có

    EI


    không có

    EO


    E

    EU


    E

    EY


    không có

    Nguyên âm đầu: i

    I


    vị trí bỏ dấu

    IA


    I

    IE


    E

    IO


    không có

    IU


    I

    IY


    không có

    Nguyên âm đầu: o

    O


    vị trí bỏ dấu

    OA


    O

    OE


    O

    OI


    O

    OU


    không có

    OY


    không có

    Nguyên âm đầu: u

    U


    vị trí bỏ dấu

    UA


    U

    UE


    E

    UI


    U

    UO


    O

    UY


    U

    Nguyên âm đầu: y

    Y


    vị trí bỏ dấu

    YA


    Không có

    YE


    Không có

    YI


    Không có

    YO


    Không có

    YU


    Không có

    Theo sơ đồ xử lý bên dưới.

    Các từ có 3 nguyên âm:

    Có các tổ hợp 3 nguyên âm sau:

    ươu rượu

    ieu liễu

    oai hoài

    uay khuấy

    uoi muỗi

    uya khuya

    uye khuyên

    yeu* yêu

    (*Đây là dạng đặt biệt được sử lý riêng, vần yêu chỉ đúng trong trường hợp đứng riêng lẻ không đi cùng với phụ âm đứng trước)

    Thông thường các từ có 3 nguyên âm thường được bỏ dấu ở nguyên âm giữa, chỉ trừ trường hợp vần “uyê” được bỏ dấu ở nguyên âm cuối.

    Trường hợp đặc biệt nếu bắt đầu bằng “qu” hoặc “gi” thì dấu sẽ bỏ ở một trong hai nguyên âm sau được xử lý như từ có hai nguyên âm.

    SƠ ĐỒ XỬ LÝ DẤU CHO CÁC TỪ CÓ 2 NGUYÊN ÂM:

    · Nếu nguyên âm đầu là:

    “a” thì

    nếu nguyên âm sau là “a”,”e”:không bỏ dấu

    ngược lại:bỏ dấu tại nguyên âm đầu a.

    “e” thì

    nếu nguyên âm sau là “o”,”u”:bỏ dấu tại nguyên âm đầu e.

    ngược lại:không bỏ dấu

    “i” thì

    nếu trước “i” là phụ âm “g”:bỏ dấu ở nguyêm âm sau.

    ngược lại

    nếu nguyên âm sau là “i”,“o”,”y”:không bỏ dấu

    ngược lại

    nếu nguyên âm sau là “a”,”u”:bỏ dấu ở nguyên âm đầu.

    ngược lại:bỏ dấu ở nguyên âm sau

    “o” thì

    nếu nguyên âm sau là “o”,”u”,”y”:không bỏ dấu

    ngược lại:bỏ dấu ở nguyên âm đầu.

    “u” thì

    nếu trước “u” là phụ âm “q”:bỏ dấu ở nguyêm âm sau.

    ngược lại

    nếu nguyêm âm sau là “a”,”i”,”y”:bỏ dấu ở nguyên âm đầu

    ngược lại

    nếu nguyên âm sau là “e”,”o”:bỏ dấu ở nguyên âm sau

    ngược lại:không bỏ dấu.

    “y” thì không bỏ dấu.

    Vấn đề giải quyết được có vẻ phức tạp hơn nhưng với bộ luật xử lý như trên ta có thể cài đặt bộ gõ có chức năng kiểm tra lổi chính tả, không cho phép gõ những từ sai lỗi chính tả và có khả năng bỏ dấu sau rất tốt. Chắc bạn sẽ rất ngạc nhiên nếu như tôi đưa ra một vấn đề vướng mắc khác cho giải pháp gần như hoàn hảo này. Thật vậy, khi bạn gõ “hoa” bạn gõ thêm dấu huyền dĩ nhiên bộ gõ này sẽ bỏ dấu ngay nguyên âm “o” trở thành “hòa” hoàn toàn chính xác. Nhưng điều gì sẽ xảy ra nếu bạn gõ thêm “n” nó sẽ trở thành “hòan” đây là một điểm mà một số bộ gõ phím như VietWare, VietKey,... và các bộ gõ phím thông dụng khác chưa xử lý.

    Vì vậy để chương trình gõ phím dễ dùng, tiện dụng và thông minh hơn, bạn nên cài thêm chức năng sửa dấu để điều chỉnh dấu khi phát hiện dấu đã được bỏ sai vị trí.Điều này cũng được thực hiện dễ dàng nhờ vào bộ luật mà chúng ta đã xây dựng từ trước nhưng phải kết hợp thêm một số điều kiện ngữ cảnh, có nghĩa là việc xác định vị trí bỏ dấu không chỉ phụ thuộc vào các nguyên âm mà còn phụ thuộc vào các phụ âm trước hoặc sau các nguyên âm đó.
    ^_,^

    Tổng hợp các câu chuyện hài hước vui nhộn, sử dụng Speech Synthesis để đọc : https://www.youtube.com/channel/UCLk...Tjrg/playlists


    Bùi Tấn Quang

  6. #6
    Ngày gia nhập
    03 2011
    Bài viết
    67

    Mặc định Hook – Ví dụ nhỏ dành cho "người mù"

    hay wa. sao không có ai like thế này thank mấy anh mong chia sẽ nhiều cho đàn em học hỏi

  7. #7
    Ngày gia nhập
    08 2008
    Bài viết
    20

    Hi all,

    muốn hook multiple key trên keyboard thì làm sao vậy mọi người (Ctrl + .....)?

  8. #8
    Ngày gia nhập
    04 2012
    Nơi ở
    Hà Nội
    Bài viết
    1

    Bạn ơi nếu sử dụng hook trong xử lý chuột để khi mình kéo và giữ chuột trên màn hình thì sẽ xác lập hình ảnh trên vùng màn hình mình vừa kéo ấy thì phải làm thế nào hả bạn

Các đề tài tương tự

  1. nghĩa của 3 từ "principals", "artifacts", "securables" là như thế nào?
    Gửi bởi ntbao trong diễn đàn English for IT | Tiếng anh cho dân CNTT
    Trả lời: 2
    Bài viết cuối: 02-09-2017, 06:36 PM
  2. Lỗi"error C2275: 'T' : illegal use of this type as an expression" khi dùng "list<T>::iterator it"
    Gửi bởi doicanhden trong diễn đàn Thảo luận, góp ý code C/C++ của bạn
    Trả lời: 6
    Bài viết cuối: 19-01-2012, 01:59 AM
  3. Trả lời: 1
    Bài viết cuối: 01-12-2011, 07:32 PM
  4. Hook Hàm API OpenProcess #pragma data_seg(".shared") là gì
    Gửi bởi Cpro trong diễn đàn Windows API, Hooking, xử lý Windows Message
    Trả lời: 3
    Bài viết cuối: 14-04-2011, 08:32 AM
  5. Problems : " recover tree " with input as " preorder" and "inorder"
    Gửi bởi HoangManhHa1991 trong diễn đàn Thắc mắc lập trình C/C++/C++0x
    Trả lời: 16
    Bài viết cuối: 13-04-2011, 10:19 PM

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn