zoukankan      html  css  js  c++  java
  • 使用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方法的使用

    感谢强大的谷歌

  • 相关阅读:
    4、redis之使用commons-pool
    好用的eclipse properties插件
    1、redis之安装与配置
    谷歌浏览器禁止window.close的问题
    在浏览器中使用JS打开并展示PDF文件
    JAVA遍历Map的方法
    GEOS库学习之三:空间关系、DE-9IM和谓词
    GEOS库的学习之二:简单几何图形的创建
    GEOS库的学习之一:介绍和编译
    GEOS库在windows中的编译和测试(vs2012)
  • 原文地址:https://www.cnblogs.com/yumingle/p/6776258.html
Copyright © 2011-2022 走看看