Java Native Access(JNA)

LINUX平台下的mplayer一般被封装成smplayer,其实只是个外壳而已,内在的还是mplayer,与其同样著名的还有一个开源的播放器,总体的功能比mplayer要强大的多,关键是其发布的程序中附带的有头文件。有了头文件就可以直接使用JNA绕过外壳直接调用播放器的核心了。当然谷歌代码上有一个专门封装这个的项目,并附带demo,有兴趣的可以去找一下,此项目命名遵循我以前说的java封装的通用命名方式。

新的开始,JNA的话现在的版本是3.2.7。好像更新比较快的样子,项目主页http://java.net/projects/jna/,官方的代码和API全部都放在SVN上,于是我把3.2.7的HTML格式的API拿出来供查询和索引使用,地址如下http://api.suwish.com/#jna

正文,说道JNA就要说一下JNI了,JNI貌似是最早的JAVA本地接口解决方法,在JDK1.1的时候正式加入java平台,它以native关键字标识本地的方法,JDK的源码已有很多native的方法,甚至是一些简单的加减运算也是调用本地的C代码执行的。JNI的大致写法如下。比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Date;
public class JNativeInterface {
static {
System.loadLibrary("jnative");
}
public native String callJNative(String from);
public static void main(String[] args){
System.out.println(new JNativeInterface().callJNative("Call Method from Java! " + new Date()));
}
}

main方法不是必要的,关键的是 public native String callJNative(String from);。其标明此方法使用本地方法实现。然后再编译后的class目录中执行javah方法如下图:

此测试程序使用缺省package,如果使用存在自己的package则需要在前面以java的类搜索规范来添加包名如com.suwish.core等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h";
/* Header for class JNativeInterface */
#ifndef _Included_JNativeInterface
#define _Included_JNativeInterface
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNativeInterface
* Method: callWin32Info
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_JNativeInterface_callWin32Info
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif

javah生成头文件就是这个样子,然后就是C/C++的问题了。已vs2010为例。如下图

当然jni必要的头文件需要引入,JDK自带的头文件位于JDK目录的include文件夹下。现在简单的吧java传入的字符串在返回给Java。即直接将jstring返回。编译之后生成jnative.dll,当然这个名字是随便的你改成任何一个都可以。

现在需要在调用public native String callJNative(String from);之前先使用System.loadLibrary(“jnative”);加载类库,之后就可以调用到jnative.dll里面的方法,如下图

过程还是很简单的,但是也只能实现这种简单的功能,比如先本人这种C语言Hello World的程度,也就能写出简答的算数运算什么的,开始的想法是想返回windows稍微系统信息,但是不知道怎么写。于是在对CPP菜鸟来说这种方法简直是难上加难。也许正是考虑到这种问题JNA的时代到来了。

###JNA的时代
JNA的时代有多给力?非常的给力。有个DLL,你你甚至拿到了他的头文件,但是你不懂CPP或者C,关键的问题还有java的对象和C/C++对象之间的转换,想我等菜鸟如何是好。但是有了JNA就没问题了,直接把jar引用过来调用即可。例如有如下功能待实现,JFrame的闪烁问题,就是在系统的的任务栏闪烁窗口标题栏的颜色,java怎么做,可以做但是效果非常不好,相反调用win32系统方法就显得非常简单了。

哦,要先说一下基本的调用方法,比如一个名为uer32.dll的文件,你知道里里面有个FlashWindowEx的方法,怎么办?抽象起来(当然,这只是一个比较浅显的理解方式,可能存在诸多的不恰当之处)定义一个JAVA的接口,将这个接口指向user32.dll,我们不管里面有多少方法,只知道有一个FlashWindowEx方法,同样在java的interface里面写这个方法,参数尽量保持一致,如指针、句柄等也保持一致(JNA文档有各种转换和对应关系,可查询指针、结构体等对应到JNA里面的那些具体类)。比如我们刚刚写的这个接口叫User32.java。现在要做的就是实例化这个接口。使其继承com.sun.jna.Library,然后User32 INSTANCE = (User32)Native.loadLibrary(Platform.isWindows() ? "user32" : "user32", User32.class);当然判断平台的目的是在不同平台调用不同的本地库,然后在外面直接引用INSTANC调用 FlashWindowEx方法,即可调用到uer32.dll里面相应的方法。

这样貌似说不清楚,于是再加点JNA的实例吧,还拿上面的JNative来说,如果换成JNA的话,就可以完全不考虑JAVA如何如何了,直接按照CPP写就可以了,例如

1
2
3
4
/* JNative.h*/
#define DllExport extern "C" __declspec( dllexport )
DllExport int callJNative(int i , int j);
1
2
3
4
5
6
7
8
9
/* JNative.cpp*/
// jnative.cpp : 定义 DLL 应用程序的导出函数。
//
#include "stdafx.h"
#include "jnative.h"
int callJNative(int i , int j){
return i + j;
}

同样编译生成jnative.dll,然后JNA直接调用,如下

1
2
3
4
5
6
7
8
9
10
11
package org.color.code;
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface JNative extends Library {
JNative INSTANCE = (JNative)Native.loadLibrary("jnative", JNative.class);
public int callJNative(int i , int j);
}

可以理解为Java中的public int callJNative(int i , int j);映射到dll中,然后在另外的类中即class中调用JNative INSTANCE.callJNative即可。当然只是简单的实现一个加法运算而已。

现在回归正题,JFrame的任务栏闪烁问题,在查询win32API之后发现存在FlashWindowEx这个方法,经过测试基本满足需求,于是在Java里面调用即可。现在一直此方法存在于user32中,而user32为win32体系的重要类库,于是JNA针对windows平台封装了com.sun.jna.platform.win32.User32,但是并没有封装里面的所有方法。

当然,如果之中没有你需要的方法,那么你大可继承User32继续补充方法即可。查询MSDN可知FlashWindowEx方法需要一个结构体参数,结构体中包括闪烁频率、时间,窗体句柄等等,查阅JNA文档可知JIN同样封装了这个结构体。于是代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public void startFlashWin32Window(){
if(isFlashRun) return;
ChatFrame frame = //省略
if(frame == null) return;
if(frame.isInFocus()) return;
HWND frameHw = new HWND();
try{
frameHw.setPointer(Native.getComponentPointer(frame)); //聊天窗口句柄。
}catch(Exception e){
//
}
final FLASHWINFO info = new FLASHWINFO();
info.cbSize = info.size();
info.hWnd = frameHw;
info.uCount = 5;
info.dwFlags = User32.FLASHW_ALL;
info.dwTimeout = 10;
isFlashRun = true;
chatFrameFlashThread = new Thread(){
public void run(){
while(isFlashRun){
ChatFrame temp = //省略
if(temp == null) break;
if(temp.isInFocus()) break;
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
User32.INSTANCE.FlashWindowEx(info);
}
isFlashRun = false;
}
};
chatFrameFlashThread.start();
}

其中包括了获得句柄的方法,Java中代替指针的方法等等,FLASHWINFO中变量的含义请参看MSDN,以上。

我说这个文章真的花费了不少时间写道这里才发现已经过了几个小时了。就先写到这里吧,什么时候想起来要补充什么的再说。