JNA全称Java Native Access,是一个建立在经典的JNI技术之上的Java开源框架(https://github.com/twall/jna)。JNA提供一组Java工具类用于在运行期动态访问系统本地库(native library:如Window的dll)而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。
JNA包:
https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna/4.0.0/jna-4.0.0.jar
https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna-platform/4.0.0/jna-platform-4.0.0.jar
JNA在线帮助文档:http://twall.github.io/jna/4.0/javadoc/
JNA入门示例:https://github.com/twall/jna/blob/master/www/GettingStarted.md
1,dll和so是C函数的集合和容器,这与Java中的接口概念吻合,所以JNA把dll文件和so文件看成一个个接口。在JNA中定义一个接口就是相当于了定义一个DLL/SO文件的描述文件,该接口代表了动态链接库中发布的所有函数。而且,对于程序不需要的函数,可以不在接口中声明。
2,JNA定义的接口一般继承com.sun.jna.Library接口,如果dll文件中的函数是以stdcall方式输出函数,那么,该接口就应该继承com.sun.jna.win32.StdCallLibrary接口。
3,Jna难点:编程语言之间的数据类型不一致。
Java和C的数据类型对照表
Java 类型 |
C 类型 |
原生表现 |
|
boolean |
int |
32位整数(可定制) |
|
byte |
char |
8位整数 |
|
char |
wchar_t |
平台依赖 |
|
short |
short |
16位整数 |
|
int |
int |
32位整数 |
|
long |
long long, __int64 |
64位整数 |
|
float |
float |
32位浮点数 |
|
double |
double |
64位浮点数 |
|
Buffer/Pointer |
pointer |
平台依赖(32或64位指针) |
|
|
pointer/array |
32或64位指针(参数/返回值) 邻接内存(结构体成员) |
|
String |
char* |
/0结束的数组 (native encoding or jna.encoding) |
|
WString |
wchar_t* |
/0结束的数组(unicode) |
|
String[] |
char** |
/0结束的数组的数组 |
|
WString[] |
wchar_t** |
/0结束的宽字符数组的数组 |
|
Structure |
struct*/struct |
指向结构体的指针(参数或返回值) (或者明确指定是结构体指针)结构体(结构体的成员) (或者明确指定是结构体) |
|
Union |
union |
等同于结构体 |
|
Structure[] |
struct[] |
结构体的数组,邻接内存 |
|
Callback |
|
Java函数指针或原生函数指针 |
|
NativeMapped |
varies |
依赖于定义 |
|
NativeLong |
long |
平台依赖(32或64位整数) |
|
PointerType |
pointer |
和Pointer相同 |
通用入门案例:
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;
/** Simple example of JNA interface mapping and usage. */
public class HelloWorld {
// This is the standard, stable way of mapping, which supports extensive
// customization and mapping of Java to native types.
public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)
Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
CLibrary.class);
void printf(String format, Object... args);
}
public static void main(String[] args) {
CLibrary.INSTANCE.printf("Hello, World\n");
for (int i=0;i < args.length;i++) {
CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);
}
}
}
运行程序,如果没有带参数则只打印出“Hello, World”,如果带了参数,则会打印出所有的参数。
很简单,不需要写一行C代码,就可以直接在Java中调用外部动态链接库中的函数!
下面来解释下这个程序。
Library
或StdCallLibrary
默认的是继承
Library
,如果动态链接库里的函数是以stdcall方式输出的,那么就继承StdCallLibrary
,比如众所周知的kernel32库。比如上例中的接口定义:
public interface CLibrary extends Library {
}
接口内部需要一个公共静态常量:INSTANCE,
通过这个常量,就可以获得这个接口的实例,从而使用接口的方法,也就是调用外部dll/so的函数。
该常量通过Native.loadLibrary()这个API函数获得,该函数有2个参数:
CLibrary INSTANCE = (CLibrary)
Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
CLibrary.class);
接口中只需要定义你要用到的函数或者公共变量,不需要的可以不定义,如上例只定义printf函数:
void printf(String format, Object... args);
注意参数和返回值的类型,应该和链接库中的函数类型保持一致。
定义好接口后,就可以使用接口中的函数即相应dll/so中的函数了,前面说过调用方法就是通过接口中的实例进行调用,非常简单,如上例中:
CLibrary.INSTANCE.printf("Hello, World\n");
for (int i=0;i < args.length;i++) {
CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);
}
这就是JNA使用的简单例子,可能有人认为这个例子太简单了,因为使用的是系统自带的动态链接库,应该还给出一个自己实现的库函数例子。其实我觉得这个完全没有必要,这也是JNA的方便之处,不像JNI使用用户自定义库时还得定义一大堆配置信息,对于JNA来说,使用用户自定义库与使用系统自带的库是完全一样的方法,不需要额外配置什么信息。比如我在Windows下建立一个动态库程序:
#include "stdafx.h"
extern "C"_declspec(dllexport) int add(int a, int b);
int add(int a, int b) {
return a + b;
}
public class DllTest {
public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)Native.loadLibrary("CDLL", CLibrary.class);
int add(int a, int b);
}
public static void main(String[] args) {
int sum = CLibrary.INSTANCE.add(3, 6);
System.out.println(sum);
}
}
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;
interface HelloInter extends Library{
int toupper(int ch);
double pow(double x,double y);
void printf(String format,Object... args);
}
public class HelloWorld {
public static void main(String [] args){
HelloInter INSTANCE =
(HelloInter)Native.loadLibrary(
Platform.isWindows()?"msvcrt":"c",
HelloInter.class);
INSTANCE.printf("Hello, Worldn");
String [] strs = new String[]{"芙蓉","如花","凤姐"};
for (int i=0;i < strs.length;i++) {
INSTANCE.printf("人物 %d: %sn", i, strs[i]);
}
System.out.println("pow(2d,3d)=="+INSTANCE.pow(2d, 3d));
System.out.println("toupper('a')=="+(char)INSTANCE.toupper((int)'a'));
}
}
pow(2d,3d)==8.0
toupper('a')==A
Hello, World
人物 0: 芙蓉
人物 1: 如花
人物 2: 凤姐
说明:
HelloInter接口中定义的3个函数全是C语言函数库中的函数,其定义格式如下:
int toupper(int ch)
double pow( double x, double y )
int printf(const char* format, ...)
C语言函数库中有很多个函数,但是我们只用到了这3个函数,所以其他的函数不需要声明在接口中。
JNA模拟结构体
例:使用 JNA调用使用 Struct的 C函数
假设我们现在有这样一个C 语言结构体
struct UserStruct{
long id;
wchar_t* name;
int age;
};
使用上述结构体的函数
#define MYLIBAPI extern "C" __declspec( dllexport )
MYLIBAPI void sayUser(UserStruct* pUserStruct);
对应的Java 程序中,在例1 的接口中添加下列代码:
public static class UserStruct extends Structure{
public NativeLong id;
public WString name;
public int age;
public static class ByReference extends UserStruct
implements Structure.ByReference { }
public static class ByValue extends UserStruct implements
Structure.ByValue
{ }
}
public void sayUser(UserStruct.ByReference struct);
UserStruct userStruct=new UserStruct ();
userStruct.id=new NativeLong(100);
userStruct.age=30;
userStruct.name=new WString("奥巴马");
TestDll1.INSTANCE.sayUser(userStruct);
现在,我们就在Java 中实现了对C 语言的结构体的模拟。这里,我们继承了Structure 类,用这个类来模拟C 语言的结构体。
必须注意,Structure 子类中的公共字段的顺序,必须与C 语言中的结构的顺序一致。否则会报错!因为,Java 调用动态链接库中的C 函数,实际上就是一段内存作为函数的参数传递给C函数。动态链接库以为这个参数就是C 语言传过来的参数。同时,C 语言的结构体是一个严格的规范,它定义了内存的次序。因此,JNA 中模拟的结构体的变量顺序绝对不能错。如果一个Struct 有2 个int 变量。Int a, int b如果JNA 中的次序和C 中的次序相反,那么不会报错,但是数据将会被传递到错误的字段中去。里直接使用了UserStruct userStruct=new UserStruct ();
也可以使用UserStruct userStruct=new UserStruct.ByReference ();
明确指出userStruct 对象是结构体指针而不是结构体本身。
JNA模拟复杂结构体
C 语言最主要的数据类型就是结构体。结构体可以内部可以嵌套结构体,这使它可以模
拟任何类型的对象。
JNA 也可以模拟这类复杂的结构体。
struct CompanyStruct{
long id;
wchar_t* name;
UserStruct users[100];
int count;
};
public static class CompanyStruct extends Structure{
public NativeLong id;
public WString name;
public UserStruct.ByValue[] users=new UserStruct.ByValue[100];
public int count;
}
例:结构体内部可以包含结构体对象的指针的数组
struct CompanyStruct2{
long id;
wchar_t* name;
UserStruct* users[100];
int count;
};
public static class CompanyStruct2 extends Structure{
public NativeLong id;
public WString name;
public UserStruct.ByReference[] users=new
UserStruct.ByReference[100];
public int count;
}
CompanyStruct2.ByReference companyStruct2=new CompanyStruct2.ByReference();
companyStruct2.id=new NativeLong(2);
companyStruct2.name=new WString("Yahoo");
companyStruct2.count=10;
UserStruct.ByReference pUserStruct=new
UserStruct.ByReference();
pUserStruct.id=new NativeLong(90);
pUserStruct.age=99;
pUserStruct.name=new WString("杨致远");
// pUserStruct.write();
for(int i=0;i
执行测试代码,报错了。这是怎么回事?typedef struct
{
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
}
SYSTEMTIME, *LPSYSTEMTIME;
VOID GetLocalTime(LPSYSTEMTIME lpst);
Listing 2. Kernel32.java
// Kernel32.java
import com.sun.jna.*;
import com.sun.jna.win32.*;
public interface Kernel32 extends StdCallLibrary
{
public static class SYSTEMTIME extends Structure
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
void GetLocalTime (SYSTEMTIME result);
}
Listing 3. LocalTime.java
// LocalTime.java
import com.sun.jna.*;
public class LocalTime
{
public static void main (String [] args)
{
Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32",
Kernel32.class);
Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME ();
lib.GetLocalTime (time);
System.out.println ("Year is "+time.wYear);
System.out.println ("Month is "+time.wMonth);
System.out.println ("Day of Week is "+time.wDayOfWeek);
System.out.println ("Day is "+time.wDay);
System.out.println ("Hour is "+time.wHour);
System.out.println ("Minute is "+time.wMinute);
System.out.println ("Second is "+time.wSecond);
System.out.println ("Milliseconds are "+time.wMilliseconds);
}
}
public class LockWorkStation {
public static void main(String[] args) {
User32 user32 = (User32) Native.loadLibrary("user32", User32.class);
user32.LockWorkStation();
}
}
class HANDLE extends PointerType {
private boolean immutable;
public HANDLE() { }
public HANDLE(Pointer p) { setPointer(p); immutable = true; }
public Object fromNative(Object nativeValue, FromNativeContext context) {
Object o = super.fromNative(nativeValue, context);
if (INVALID_HANDLE_VALUE.equals(o))
return INVALID_HANDLE_VALUE;
return o;
}
public void setPointer(Pointer p) {
if (immutable)
throw new UnsupportedOperationException("immutable reference");
super.setPointer(p);
}
}
class POINT extends Structure {
public int x, y;
public POINT() { }
public POINT(int x, int y) { this.x = x; this.y = y; }
}
JNA也提供了一种保护机制.比如防止JNA出现异常不会导致JVM异常退出,默认是开启这个功能的,开启方式为 System.setProperty(“jna.protected”,”true”); 记得要在JNA加载dll文件之前调用,然后try {...} catch(Throwable e)异常,不过你也不要期望过高,不要以为加上这个就万事大吉,出现”非法内存访问”的时候还是会束手无策。JNA也提供了一种保护机制.比如防止JNA 出现异常不会导致JVM异常退出,默认是开启这个功能的,开启方式为 System.setProperty(“jna.protected”,”true”); 记得要在JNA加载dll文件之前调用,然后try {...} catch(Throwable e)异常,不过你也不要期望过高,不要以为加上这个就万事大吉,出现”非法内存访问”的时候还是会束手无策。
参考文章:
深入浅出JNA—快速调用原生函数:http://www.doc88.com/p-31975835542.html
JNA—JNI终结者:http://blog.csdn.net/shendl/article/details/3589676
JNA的使用:http://xbgd.iteye.com/blog/1044864
深入理解JNA—模拟C语言结构体:http://blog.csdn.net/shendl/article/details/3599849