手把手教你實現Java監聽器全局監控

 (一) 動態鏈接庫初入門

    1.前段時間,閒來無聊,想做個像QQ截圖一樣的截圖工具,在卻在做的過程中發現java自帶API中的監聽器帶有很大的侷限性,java的鼠標監聽器只有在鼠標在程序窗口之上時纔會生效,而鍵盤監聽器則更加侷限,只有在當前窗口爲焦點窗口時纔會生效,這顯然是不符合我們需要效果的,我們所需要的是全局的鍵盤監聽,不管你在幹什麼,只要觸發我們設定的固定的組合鍵是,就會執行我們需要的功能,所以用java是不太好辦。

    2.那麼QQ是怎麼做到的呢?QQ是用C語言寫的,並不是java,而且windows也是C語言寫出來的,所以他能輕易的實現這種全局監聽。這也就是一個突破口,native,不知道你有沒有注意過這個關鍵字,這是用來調用本地代碼的一個關鍵字。我們可以這樣設想,假如我們將所有的代碼全部用java實現,卻用C語言去監聽全局鍵盤,然後返回爲我們所用。是不是很有意思。

     首先,我說一說整體的思路,由於java中監聽器的侷限性,我們將用C語言的HHOOK消息鉤子,來獲取到全局消息的監聽,然後通過jni技術,用java調用,於是就形成java監聽器的全局監控。

    3.下面我來說明這樣用java代碼調用C語言的方法(函數)。

package test;
public class HelloWorld{
         static{
                 System.loadLibrary("HelloDll");
         }  
         public static void main(String args[]){
                 HelloWorld hw = new HelloWorld();
                 hw.sayHello();
         }
         public native void sayHello();
}

       上面這個System.loadLibrary("HelloDll");此句爲引入一個叫做HelloDll的本地文件,而這個必定包含了sayHello();的實現代碼。java工作我們算是昨晚了,也就是一個簡單的打印HelloWorld的代碼,重要的是我們怎樣用C語言去實現這個本地方法,怎樣讓其經行工作。

       (1).編寫java文件。上一步已經實現

       (2).javac運行源文件生成class文件

       (3).javah test.HelloWorld 這裏注意不用上帶後綴,我在經行這一步驟時,路徑問題很麻煩。在包這一層運行javah test.HelloWorld就會生成一個叫test_HelloWorld.h的C語言頭文件。

    因爲我們的方法是要用C語言實現的,所以經行到這一步,我們得到了一個C的頭文件。如果你打開這個頭文件,你會發現裏面會有一個叫做JNIEXPORT void JNICALL Java_test_HelloWorld_sayHello(JNIEnv *, jobject);的方法,這個方法其實就是我們的java方法在C語言裏邊的映射了(我一般這麼稱呼,不知道對不對),我們只要將這個方法用C語言實現了,然後編譯成一個java能調用的DLL本地文件就OK了。

       (4)打開Microsoft Visual C++,File-->new-->Win32 Dynamic-Link Library,新建一個叫HelloDLL的文件動態鏈接工程,點擊左下角FileView,點開HelloDll files。然後File-->new-->C/C++ Header File,名字爲test_HelloWorld.h,然後點開Header Files裏的test_HelloWorld.h,將我們剛纔生成的test_HelloWorld.h文件內容複製進去,再File-->new-->C++ Source File,名字hello(隨便取),然後點開文件寫入內容:

#include "test_HelloWorld.h"
#include <iostream.h>
JNIEXPORT void JNICALL Java_test_HelloWorld_sayHello
    (JNIEnv *, jobject){
		cout<<"HelloWorld"<<endl;
}

       可以看出,我們生成頭文件的目的,無非就是爲了用C語言實現,因爲我們這裏引入了這個頭文件,而這裏邊唯一的一個方法,就是我們生成的頭文件,也就是我們的java代碼未實現的代碼在C中的映射,然後我在這個方法中打印了HelloWorld這句話。(如果看不懂C代碼,建議百度一下,就算看不懂,憑我文字描述應該也是能懂一些的,不過應該不會看不懂吧)。

 

       另外就是,這一個段落我寫的比較詳細,甚至比較囉嗦,主要是爲了沒用過Microsoft Visual C++或者沒學過C的人,能夠手把手的教他運行這個程序,大神可以略過。

       好了,言歸正傳,我們還需引入java的lib環境,Tools-->Options-->Directories在底下的方框欄中加入你C:\PROGRAM FILES\JAVA\JDK1.6.0_04\INCLUDE和C:\PROGRAMFILES\JAVA\JDK1.6.0_04\INCLUDE\WIN32也就是java的JDK的include和include中win32兩個文件夾。然後Build-->Rebuild All。

       你會發現,在你C++文件夾HelloDll中Debug中有一個HelloDll.dll文件。那麼這個文件就是我們需要的動態鏈接文件了。如果你將它打開,那麼你會發現大部分是亂碼。

    (5)將得到的HelloDll.dll複製到你的java工程的包這一層下,然後運行java test.eHelloWorld,就會打印HelloWorld這句話了,如果在沒有這個dll文件時你就引進運行了,會拋出一個找不到動態鏈接文件的異常。

    (6)這樣我們就實現了用java調用C語言的方法了。這就是動態鏈接了,在下一篇我將描述,怎樣用C語言的HHOOK鉤子去獲取windows底層鍵盤和鼠標的相應。

 (二)怎樣用C語言獲取全局消息HHOOK

     國際慣例,我先說一說這一章的思路,這一章主要是講Windows API中兩個鉤子函數,加上一些窗口處理。首先我利用API創建窗體,窗體消息交由WindowProc(自定義)去處理,在窗體創建時設置全局鉤子,在窗體銷燬時去除鉤子,而在鉤子內部,通過keyLisProc和mouseLisProc去處理鉤子獲取到的消息,

當鉤子獲取到鼠標或鍵盤時,賦予全局變量keyCode或mouseCode,最後通過javah生成的函數返回給java動態鏈接,實現java的jni全局監控

    (1)在Windows的API中,有這麼一個函數

HHOOK WINAPI SetWindowsHookEx( int idHook, HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThread)
這個函數的第一個參數是代表鉤子的類型,例如WH_KEYBOARD_LL代表的是鍵盤鍵監聽,WH_MOUSE_LL代表的是鼠標的監聽,第二個參數代表的是鉤子函數的地址指針,也就是將交由那個函數去處理這個事件,類似於監聽器的作用(但不是),第三個代表應用程序實例的句柄,第四個爲與程序相關的線程的ID,如果爲0,即爲全局監控
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
    switch ( msg ) {
	//當窗體創建的時候
         case WM_CREATE:
	//將系統鉤子設置
             hKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL, keyLisProc, all_hInst, 0);
             hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, mouseLisProc, all_hInst, 0);
           break;
          //當窗體清除的時候
        case WM_DESTROY:
	//如果消息鉤子已經設置
             if ( hKeyHook != NULL && hMouseHook != NULL ) {
	 //清除消息鉤子
                 UnhookWindowsHookEx(hKeyHook);
                        UnhookWindowsHookEx(hMouseHook);
	}
	//向系統發送關閉信息
             PostQuitMessage(0);
             break;
       default:
             return DefWindowProc(hwnd, msg, wparam, lparam);
   }
    return 0;
}
 這就是windows在窗口消息中的響應方法,他規定了在窗體創建時設置鉤子,窗體消失時取消鉤子。而鉤子設置成功後,可以看到是由一個叫keyLisProc的函數去處理消息的
LRESULT CALLBACK keyLisProc(int nCode, WPARAM wParam, LPARAM lParam) {
   //存儲按鍵信息的結構體
   KBDLLHOOKSTRUCT* KeyInfo = NULL;
   //如果按下了
   if ( nCode >= 0 && wParam == WM_KEYDOWN ) {
    KeyInfo = (KBDLLHOOKSTRUCT*)lParam;
	//獲取到結構體中按鍵的KeyCode值
    keyCode = KeyInfo->vkCode;
   }
   return CallNextHookEx(NULL, nCode, wParam, lParam);
}
 這個就是那個消息處理,首先將獲取到鍵盤信息,存儲到KeyInfo結構體中,然後從中取出來付給KeyCode,而keyCode是一個全局變量,我們只需要在javah時生成的函數中,返回給java代碼就可以了,是不是相對來說,有點複雜了,但事實上思路很清晰,只要多分析,就不會太難理解。    
     注意:在VC6.0中引入winuser.h是沒有用的,需要用的結構體和宏定義得自己添加
#define WH_KEYBOARD_LL     13
#define WH_MOUSE_LL        14
typedef struct tagMSLLHOOKSTRUCT {
    POINT   pt;
    DWORD   mouseData;
    DWORD   flags;
    DWORD   time;
    DWORD   dwExtraInfo;
} MSLLHOOKSTRUCT, FAR *LPMSLLHOOKSTRUCT, *PMSLLHOOKSTRUCT;
typedef struct tagKBDLLHOOKSTRUCT {
    DWORD   vkCode;
    DWORD   scanCode;
    DWORD   flags;
    DWORD   time;
    DWORD   dwExtraInfo;
} KBDLLHOOKSTRUCT, FAR *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
 在C語言這一塊,我只是講解了很多思路,因爲你如果不會這門 語言,我真的沒有辦法在這裏教會你,這是一個比較難以表達的事情,如果你真的很想搞懂,987706386是我的QQ,其實我思路已經說得比較明瞭,加上下載我上傳的文檔看一看,那就很容易理解。
        好了,把我上傳的代碼試一試,然後自己研究研究,加上自己的理解,我想不難理解這種思路,這樣一來就解決的java全局監控的問題,是不是?(可以先看文檔,比較容易,因爲博客上文檔不齊全。)
那麼其實我所要解說的內容,基本完畢,這一篇博客也就基本說清楚了,還有不清楚,上面的QQ,還有就是QQ截圖,我還在慢慢更新界面,也許一時半會,還木有成品,如果做出來了,將會在另一篇博客中給大家呈現。謝謝
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章