JNA(Java Native Access )提供一组Java工具类用于在运行期动态访问系统本地库(native library:如Window的dll)而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。
JNA可以让你像调用一般java方法一样直接调用本地方法。就和直接执行本地方法差不多,而且调用本地方法还不用额外的其他处理或者配置什么的,也不需要多余的引用或者编码,使用很方便。
JNA类库使用一个很小的本地类库sub 动态的调用本地代码。程序员只需要使用一个特定的java接口描述一下将要调用的本地代码的方法的结构和一些基本属性。这样就省了为了适配多个平台而大量的配置和编译代码。因为调用的都是JNA提供的公用jar 包中的接口。
JNA是为了程序员调用本地代码更方便,跑的更快以及减少出错。
JNA中还包括了一些已经和某些特定平台的常用方法映射好的接口以及一些有用的可以简化本地代码访问的方法。
JNA编程基本语法解析
1. 自定义一个接口,该接口继承(扩展)自com.sun.jna.Library接口,
上面示例代码中的CLibrary接口。
2 .在自定义的接口中声明将要调用的函数原型,这些函数原型必须要在即将
加载的动态库中有对应的实例。如上面示例代码中的printf函数声明。如果是在微软的Windows环境下,
该函数存放在msvcrt.dll动态库中;如果是在Linux环境下,该函数存放在libc.so动态库中。
3. 在调用函数之前,先将接口实例化,并调用Native.loadLibrary对接口的实例初始化。
如上面示例代码中的Instance实例初始化。
4. 在上面的操作都正常完成后,即可在有需要的地方调用对应的C/C++函数。
package com.etrip.jna.win;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;
/**
*
* @Title: TODO
* @Description: 实现TODO
* @Copyright:Copyright (c) 2011
* @Company:
* @Date:2012-12-14
* @author longgangbai
* @version 1.0
*/
public class HelloJNA
{
//定义接口CLibrary,继承自com.sun.jna.Library
public interface CLibrary extends Library
{
//定义并初始化接口的静态变量
CLibrary Instance=(CLibrary)Native.loadLibrary((Platform.isWindows()?"msvcrt":"c"),CLibrary.class);
//printf函数声明
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]);
}
}
}
1、编写需要使用Jni的Java类文件
public class JniCall { static { System.loadLibrary("testJNA"); } public native static int add(int first, int second); public static void main(String[] args) { int first = 3; int second = 4; System.out.printf("print in java : %d + %d = %d", first, second, add(first, second)); } }
本地方法必须声明为native。
2、编译出class文件,用javah从class文件生成C的头文件JniCall.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class JniCall */ #ifndef _Included_JniCall #define _Included_JniCall #ifdef __cplusplus extern "C" { #endif /* * Class: JniCall * Method: add * Signature: (II)I */ JNIEXPORT jint JNICALL Java_JniCall_add (JNIEnv *, jclass, jint, jint); typedef struct Student { char * name; int age; int height; }StudentObj; #ifdef __cplusplus } #endif #endif
3、在VC下建立一个动态链接库项目testJNA
倒数第二个。
4、把生成的JniCall.h和$JAVA_HOME/include/jni.h、$JAVA_HOME/include/win32jni_md.h拷贝到vc项目testJNA的目录下
5、编写C的本地实现
#include "stdafx.h" BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { return TRUE; } JNIEXPORT jint JNICALL Java_JniCall_add(JNIEnv *, jclass, jint first, jint second) { printf("print in c : %d + %d = %d \n", first, second, first + second); return first + second; }
6、构建testJNA项目,生成testJNA.dll文件
7、把testJNA.dll拷贝到$JAVA_HOME/jre/bin目录下
8、运行Java类,调用本地方法
D:\Java\jdk1.6.0_02\bin>java JniCall
print in c : 3 + 4 = 7
print in java : 3 + 4 = 7
D:\Java\jdk1.6.0_02\bin>
注意char *与string的映射问题,如果对编码又要求的话,最好采用以下这种方式将string传给本地方法。
比如要以gbk传字符串s给本地方法:call(char *)
byte[] bytes = (s+'\0').getBytes("gbk"); Dll.INSTANCE.call(bytes);
_cdecl,是C语言缺省的调用约定,参数采用从左到右的压栈方式,函数本身不清理堆栈,调用者负责清理堆栈。对于这种库,只要直接继承Library。
_stdcall,是Pascal程序的缺省调用方式,WIN32 Api都采用_stdcall调用方式。参数采用从右到左的压栈方式,被调函数自身在返回前清空堆栈。这种需要继承StdCallLibrary。