1:什么是JNI
Java JNI是Java Native Interface的缩写,中文可译为Java本地调用。Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。
-----------------------------------------------------------
2:JAVA调用C/C++的JNI
JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。
public class testJNI
{
static {
String libPath="cpluslib.so";
//向JVM加载指定相对路径的C++库。
System.loadLibrary(libPath);
}
//需要JNI的函数必须声明为 public native static
public native static int get();
public native static void set(int i);
public static void main(String[] args)
{
testJNI test = new testJNI();
test.set(10);
System.out.println(test.get());
}
}
用javac testJNI.java编译它,会生成testJNI.class。
再用javah testJNI,则会在当前目录下生成testJNI.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。 如下(此文件不能被修改)
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class testJNI */
#ifndef _Included_testJNI
#define _Included_testJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: testJNI
* Method: get
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_testJNI _get (JNIEnv *, jclass);
/*
* Class: testdll
* Method: set
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_testJNI _set (JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
在具体实现的时候,我们只关心两个函数原型
JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 和
JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);
这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数。
编译连接成库文件,本例是在Linux下做的,生成的是.so文件。并且名称要与JAVA中需要调用的一致,这里就是cpluslib.so 。把cpluslib.so 拷贝到testJNI.class的目录下,java testJNI运行它,就可以观察到结果了。
--------------------------------------------------------------
3:C/C++调用JAVA的JNI
C/C++要调用JAVA程序,必须先加载JAVA虚拟机,由JAVA虚拟机解释执行class文件。为了初始化JAVA虚拟机,JNI提供了一系列的接口函数,通过这些函数方便地加载虚拟机到内存中。
1.加载虚拟机:
函数:jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void args);
参数说明:JavaVM **pvm JAVA虚拟机指针,第二个参数JNIEnv *env是贯穿整个调用过程的一个参数,因为后面的所有函数都需要这个参数,需注意的是第三个参数,在jdk1.1与1.2版本有些不同,在JDK 1.1中第三个参数总是指向一个结构JDK1_ 1InitArgs,这个结构无法完全在所有版本的虚拟机中进行无缝移植。所以为了保证可移植性,建议使用jdk1.2的方法加载虚拟机。
2.获取指定对象的类定义:
有两种方法可获得类定义,一是在已知类名的情况使用FindClass来获取;二是通过对象直接得到类定义GetObjectClass
3.获取要调用的方法:
获得非静态方法:
jmethodID (JNICALL *GetMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig);
获得静态方法:
jmethodID (JNICALL *GetStaticMethodID)(JNIEnv *env, jclass class, const char *name, const char *sig);
参数说明:JNIEnv *env初始化是得到的JNI环境;jclass class前面已获取到的类定义;const char *name方法名;const char *sig方法的定义,我们知道JAVA支持多态,同名方法通过第四个参数来定位得到具体的方法,那么这个参数怎么填呢?我们如何能知道应该用什么样的字符 串来表示?在JDK已经准备好了一个反编译工具,通过这个工具我们就可察看类中的属性及方法的定义,如何做?很简单,假如我们在之前建立了一个 MyTest.java,通过javac已经编译好此程序,MyTest.java如下:
4.调用JAVA类方法:
函数:CallObjectMethod(JNIEnv *env, jobject obj, jmethodID mid);
函数:CallStaticObjectMethod((JNIEnv *env, jobject obj, jmethodID mid);
5.获得类属性的定义:
jfieldID (JNICALL *GetFieldID) (JNIEnv *env, jclass clazz, const char *name, const char *sig);
静态属性:
jfieldID (JNICALL *GetStaticFieldID) (JNIEnv *env, jclass clazz, const char *name, const char *sig);
6.数组处理:
要创建数组首先要知道类型及长度,JNI提供了一系列的数组类型及操作的函数如:
NewIntArray、NewLongArray、NewShortArray、NewFloatArray、NewDoubleArray、 NewBooleanArray、NewStringUTF、NewCharArray、NewByteArray、NewString,访问通过 GetBooleanArrayElements、GetIntArrayElements等函数。
7.异常:
由于调用了Java的方法,会产生异常。这些异常在C/C++中无法通过本身的异常处理机制来捕捉到,但可以通过JNI一些函数来获取Java中抛出的异常信息。
8.多线程调用
我们知道JAVA是非常消耗内存的,我们希望在多线程中能共享一个JVM虚拟机,真正消耗大量系统资源的是JAVA虚拟机jvm而不是虚拟机环境 env,jvm是允许多个线程访问的,但是虚拟机环境只能被创建它本身的线程所访问,而且每个线程必须创建自己的虚拟机环境env。JNI提供了两个函 数:AttachCurrentThread和DetachCurrentThread。便于子线程创建自己的虚拟机环境。
/**
* 类MyTest为了测试JNI使用C/C++调用JAVA
* @author liudong
*/
public class MyTest {
/*
* 测试如何访问静态的基本类型属性
*/
/*
*演示对象型属性
*/
public String helloword;
public MyTest() {
this("JNI演示类");
}
/**
* 构造函数
*/
public MyTest(String msg) {
System.out.println("构造函数:" + msg);
this.helloword = msg;
}
/**
* 该方法演示如何调用动态方
*/
public String HelloWord() {
System.out.println("JAVA-CLASS:MyTest method:HelloWord:" + helloword);
return helloword;
}
/**
* 演示异常的捕捉
*/
public void throwExcp() throws IllegalAccessException {
throw new IllegalAccessException("exception occur.");
}
}
那么在命令行运行:javap -s -p MyTest ,你将看到如下输出:
Compiled from "MyTest.java"
public class MyTest extends java.lang.Object{
public java.lang.String helloword;
Signature: Ljava/lang/String;
public MyTest();
Signature: ()V
public MyTest(java.lang.String);
Signature: (Ljava/lang/String;)V
public java.lang.String HelloWord();
Signature: ()Ljava/lang/String;
public void throwExcp() throws java.lang.IllegalAccessException;
Signature: ()V
}
C代码testjava.c:
#include <jni.h>
#include <assert.h>
/*C字符串转JNI字符串*/
jstring stoJstring(JNIEnv* env, const char* pat)
{
jclass strClass = (*env)->FindClass(env,"Ljava/lang/String;");
jmethodID ctorID = (*env)->GetMethodID(env,strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = (*env)->NewByteArray(env,strlen(pat));
(*env)->SetByteArrayRegion(env,bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = (*env)->NewStringUTF(env,"utf-8");
return (jstring)(*env)->NewObject(env,strClass, ctorID, bytes, encoding);
}
/*JNI字符串转C字符串*/
char* jstringTostring(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env,"java/lang/String");
jstring strencode = (*env)->NewStringUTF(env,"utf-8");
jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr, mid, strencode);
jsize alen = (*env)->GetArrayLength(env,barr);
jbyte* ba = (*env)->GetByteArrayElements(env,barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
(*env)->ReleaseByteArrayElements(env,barr, ba, 0);
return rtn;
}
int main() {
int res;
JavaVM *jvm;
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption options[3];
/*设置初始化参数*/
options[0].optionString = "-Djava.compiler=NONE";
options[1].optionString = "-Djava.class.path=.";
options[2].optionString = "-verbose:jni"; //用于跟踪运行时的信息
/*版本号设置不能漏*/
vm_args.version=JNI_VERSION_1_2;//jdk版本1.2
vm_args.nOptions = 3;
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_TRUE;
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
if (res < 0) {
fprintf(stderr, "Can't create Java VM\n");
exit(1);
}
/*获取实例的类定义*/
jmethodID mid;
jclass cls = (*env)->FindClass(env, "MyTest");
if (cls == 0)
{
fprintf(stderr, "FindClass failed\n");
(*jvm)->DestroyJavaVM(jvm);
fprintf(stdout, "Java VM destory.\n");
return;
}
/*获取构造函数,用于创建对象*/
/***1.1可用""作为构造函数, 1.2用"<init>"参数中不能有空格"(Ljava/lang/String;)V"*/
mid = (*env)->GetMethodID(env,cls,"<init>","(Ljava/lang/String;)V");
assert (0 != mid);
fprintf(stderr, "GetMethodID \n");
if (mid == 0)
{
fprintf(stderr, "GetMethodID failed\n");
(*jvm)->DestroyJavaVM(jvm);
fprintf(stdout, "Java VM destory.\n");
return;
}
fprintf(stderr, "GetMethodID OK\n");
/*创建对象*/
const char str[]="this is a test for c call java";
jobject obj = (*env)->NewObject (env, cls, mid, stoJstring(env, str));
//jobject obj = (*env)->NewObject(env, cls, mid, 0);
/*如果mid为0表示获取方法定义失败*/
fprintf(stderr, "NewObject OK\n");
/*获取方法ID*/
mid=(*env)->GetMethodID(env,cls,"HelloWord","()Ljava/lang/String;");
if (mid == 0)
{
fprintf(stderr, "GetMethodID 'HelloWord' failed\n");
(*jvm)->DestroyJavaVM(jvm);
fprintf(stdout, "Java VM destory.\n");
return;
}
fprintf(stderr, "GetMethodID 'HelloWord' OK\n");
/*调用动态方法*/
jstring msg = (*env)-> CallObjectMethod(env, obj, mid);
/*
如果该方法是静态的方法那只需要将最后一句代码改为以下写法即可:
jstring msg = (*env)-> CallStaticObjectMethod(env, cls, mid);
*/
/*捕捉异常*/
if ((*env)->ExceptionOccurred(env))
{
(*env)->ExceptionDescribe(env);
return ;
}
/*销毁JAVA虚拟机*/
(*jvm)->DestroyJavaVM(jvm);
fprintf(stdout, "Java VM destory.\n");
}
编译:
在linux下:cc -o testjava testjava.c -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -L${JAVA_HOME}/jre/lib/i386/client -ljvm
运行结果:
GetMethodID
GetMethodID OK
[Dynamic-linking native method java.io.FileOutputStream.writeBytes ... JNI]
MyTest:this is a test for c call java
NewObject OK
GetMethodID 'HelloWord' OK
JAVA-CLASS:MyTest method:HelloWord:this is a test for c call java
Java VM destory.