问题背景:
我的毕业设计中需要在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
这个是必备的参考文档:
JNA的GitHub地址:
为了示范其简单性,看下面的代码。
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方法的使用
感谢强大的谷歌