使用JNA訪問WindowsAPI操作Windows窗口元素


問題背景:

我的畢業設計中需要在Windows平台上面跨進程操作窗口。實際上是獲取瀏覽器上面的網頁中的文本框元素,還有windows32窗體上面的編輯框。然后進行自動填值等的操作。

我能想到的一共有這么幾種方法:

  • 使用C#編寫窗體應用程序,然后使用WebBrowser瀏覽器控件或者嵌入其他應用程序窗口。如果使用WebBrowser控件,只能強制用戶使用該C#應用程序上網,影響用戶體驗,不切實際。如果使用嵌入其他應用程序窗口的方式,其實就轉化為了跨進程獲取窗口的方法了。

  • 使用瀏覽器插件的方式,針對不同瀏覽器編寫不同插件,然后讓用戶安裝。當瀏覽器頁面載入后,使用駐留程序(這是我畢設的核心進程)向瀏覽器發消息,執行瀏覽器插件中的JS代碼操作網頁DOM元素。但是缺點是需要編寫很多插件,且調試起來,真正執行起來很艱難。

  • 先使用遠程線程注入到目標進程的線程空間,創建一個虛擬線程,然后執行這個虛擬線程,向擁有這個窗口的界面線程發送消息。實際上這個方法和上面的方法大同小異。只不過進程注入行為會被用戶系統的安全機制檢測到,類似360安全衛士這種神經質的安全軟件會讓用戶把我們的程序查殺掉。另外需要針對各種瀏覽器,各種程序窗體做特定的分析處理,代價太大,而我只不過是完成一個畢設,沒必要用牛刀吧。

  • 使用模擬用戶操作方式。先拿簡單的方法說,很多腳本語言例如在Windows上面的VBS腳本執行時會啟動WScript駐留進程,使用VBS的 sendKey 命令可以模擬用戶的輸入,甚至VBS能模擬用戶鼠標的點擊。還可以使用Python,JS(需要先讓用戶下載python)等都可以。他們的核心其實都是調用Windows系統API來完成功能,從結構上來看都是要運行一個本地即時解釋器,它可以調用WindowsAPI,然后解釋腳本執行操作。再說深層次一點就是先獲取目標窗口的句柄,然后對該窗體的消息處理隊列發送WM_SET_TEXT,WM_GET_TEXT,WM_EXIT等各種消息。

  • 本文考慮到畢設需要具有跨平台的特性,並且最好能夠兼容各種不同版本的Windows。因此使用Java語言的JNA包提供的方便的功能調用WindowsAPI。而是用JNI也可以。只不過還要編寫DLL,編譯再加調試,會浪費很長時間。如果不是針對特定問題,使用成熟的JNA況且會幫助你解決低層調用的各種問題,何樂而不為呢。

摘取一些JNA簡介:

JNA提供Java程序輕松訪問本機共享庫,而不需要編寫任何Java代碼 - 不需要JNI或本機代碼。這個功能與Windows的Platform / Invoke和Python的ctypes類似。
JNA允許您使用Java的方法調用來直接調用本機函數。調用看起來就像本機代碼中的調用一樣。大多數的方法調用不需要特殊的處理或配置。
JNA使用一個小的JNI庫存根來動態調用本地代碼。開發人員使用Java接口描述目標本機庫中的函數和結構。這使得很容易利用本機平台功能,而不會導致為多個平台配置和構建JNI代碼的高開銷。
因此,JNA提供了相比較性能來說更關注平台適應以及便利性,節省使用者需要面對多版本,多平台開發程序的時間。

除了Windows, JNA還支持多種其他的平台。例如ARM,安卓,Linux等。
JNA可以通過Maven包管理下載。

如果不適用Maven管理包,可以自己下載下面的兩個包放到項目中:

http://repo1.maven.org/maven2/net/java/dev/jna/jna/4.4.0/jna-4.4.0.jar
http://repo1.maven.org/maven2/net/java/dev/jna/jna-platform/4.4.0/jna-platform-4.4.0.jar

這個是必備的參考文檔:

http://java-native-access.github.io/jna/4.4.0/javadoc/

JNA的GitHub地址:

https://github.com/java-native-access/jna#readme

為了示范其簡單性,看下面的代碼。


import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinUser;
/**
 * Created by lenovo on 2017/4/27.
 * 使用winID來獲得窗口的類型和標題,然后發送消息或者其他操作
 *
 */
public class jnaTest {
    public static void main(String[] args) {

        HWND hwnd = User32.INSTANCE.FindWindow
                (null, "QQ"); // 第一個參數是Windows窗體的窗體類,第二個參數是窗體的標題。不熟悉windows編程的需要先找一些Windows窗體數據結構的知識來看看,還有windows消息循環處理,其他的東西不用看太多。
        if (hwnd == null) {
            System.out.println("QQ is not running");
        }
        else{
            User32.INSTANCE.ShowWindow(hwnd, 9 );        // SW_RESTORE
            User32.INSTANCE.SetForegroundWindow(hwnd);   // bring to front

            //User32.INSTANCE.GetForegroundWindow() //獲取現在前台窗口
            WinDef.RECT qqwin_rect = new  WinDef.RECT();
            User32.INSTANCE.GetWindowRect(hwnd, qqwin_rect);
            int qqwin_width = qqwin_rect.right-qqwin_rect.left;
            int qqwin_height = qqwin_rect.bottom-qqwin_rect.top;

            User32.INSTANCE.MoveWindow(hwnd, 700, 100, qqwin_width, qqwin_height, true);
            for(int i = 700; i > 100; i -=10) {
                User32.INSTANCE.MoveWindow(hwnd, i, 100, qqwin_width, qqwin_height, true);   // bring to front
                try {
                    Thread.sleep(80);
                }catch(Exception e){}
            }
            //User32.INSTANCE.PostMessage(hwnd, WinUser.WM_CLOSE, null, null);  // can be WM_QUIT in some occasio
        }

//在Windows中,User32.dll文件擁有大量的操作用戶界面的API。可以看到JNA在包命名上也遵照了DLL的命名規律。

如果我們事先打開QQ程序的登陸界面,當我們運行上面的程序時,就會將QQ登陸窗體置於前台顯示同時將他從屏幕的右邊移動到屏幕的左面。
另外,學過windows編程的都知道,一個windows32程序一般都會有自己獨有的窗體類,即叫做 Window Class,例如 windows下的圖片查看器的主窗口類為"Photo_lightweight_Viewer", 記事本窗口的窗體類叫做"Notepad"。一個窗口類是一個窗體風格,程序中可以定義多個窗體類。當然,WIndows32程序也可以使用其他程序的窗體類。上面的 FindWindow 函數的第一個參數可以傳入一個窗體類名,這樣可以縮小低層JNA調用 FindWindowEX 函數查找的范圍。對於Windows窗體的信息,可以使用 WinID 這個軟件來查詢。VS編程的同學可以使用Spy++工具查看。

下面來解決我上面說的主要問題:


import com.sun.jna.platform.win32.BaseTSD;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinUser;

/**
 * Created by lenovo on 2017/4/27.
 * 使用winID來獲得窗口的類型和標題,然后發送消息或者其他操作
 *
 */
public class jnaTest {
    public static void main(String[] args) {

        WinDef.HWND hwnd = User32.INSTANCE.FindWindow
                (null, "QQ"); // 第一個參數是Windows窗體的窗體類,第二個參數是窗體的標題。不熟悉windows編程的需要先找一些Windows窗體數據結構的知識來看看,還有windows消息循環處理,其他的東西不用看太多。
        if (hwnd == null) {
            System.out.println("Excel is not running");
        }
        else{
            User32.INSTANCE.ShowWindow(hwnd, 9 );        // SW_RESTORE
            User32.INSTANCE.SetForegroundWindow(hwnd);   // bring to front

            String username = "yourQQnumber";
            for(Character c: username.toCharArray())
            sendChar(c);
        }
    }

    static WinUser.INPUT input = new WinUser.INPUT(  );
    static void  sendChar(char ch){

        input.type = new WinDef.DWORD( WinUser.INPUT.INPUT_KEYBOARD );
        input.input.setType("ki"); // Because setting INPUT_INPUT_KEYBOARD is not enough: https://groups.google.com/d/msg/jna-users/NDBGwC1VZbU/cjYCQ1CjBwAJ
        input.input.ki.wScan = new WinDef.WORD( 0 );
        input.input.ki.time = new WinDef.DWORD( 0 );
        input.input.ki.dwExtraInfo = new BaseTSD.ULONG_PTR( 0 );
        // Press
        input.input.ki.wVk = new WinDef.WORD( Character.toUpperCase(ch) ); // 0x41
        input.input.ki.dwFlags = new WinDef.DWORD( 0 );  // keydown

        User32.INSTANCE.SendInput( new WinDef.DWORD( 1 ), ( WinUser.INPUT[] ) input.toArray( 1 ), input.size() );

        // Release
        input.input.ki.wVk = new WinDef.WORD( Character.toUpperCase(ch) ); // 0x41
        input.input.ki.dwFlags = new WinDef.DWORD( 2 );  // keyup

        User32.INSTANCE.SendInput( new WinDef.DWORD( 1 ), ( WinUser.INPUT[] ) input.toArray( 1 ), input.size() );

    }
}

注意,使用前需要先選定目標焦點。

參考網站:
http://www.rgagnon.com/topics/java-jni.html 這個網站上有幾個JNA的實例,熟悉Windows窗體編程的朋友們看起來應該很容易。
https://github.com/java-native-access/jna#readme
http://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html 這個是新加坡理工大學的網站,想入門JNI的可以去看看。
http://stackoverflow.com/questions/28538234/sending-a-keyboard-input-with-java-jna-and-sendinput 包含sendkey方法的使用
https://coderanch.com/t/635463/java/JNA-SendInput-function 包含sendkey方法的使用

感謝強大的谷歌


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM