1 jni简介
JavaNative Interface(JNI)是Java语言的本地编程接口,是J2SDK的一部分。在java程序中我们可以通过JNI实现一些用java语言不便实现的功能。通常有以下几种情况我们需要使用JNI来实现。
标准的java类库没有提供你的应用程序所需要的功能,通常这些功能是平台相关的你希望使用一些已经有的类库或者应用程序,而他们并非用java语言编写的程序的某些部分对速度要求比较苛刻,你选择用汇编或者c语言来实现并在java语言中调用他们。
JNI既可以实现Java调用本地库, 也可以实现C/C++调用Java代码, 从而实现了两种语言的互通,可以让我们更加灵活的使用。
本博客主要介绍的是用jni实现C/C++调用Java代码。
2 jni环境配置
2.1安装jdk
我的linux是RedHat Enterprise linux 5,内核版本2.6.18。在Linux系统中安装Java比较简单。可以访问Java download网站或自由软件库等,选择你所有安装的操作系统类型(Linux,Linux AMD64,Solaris等)。一旦你已经选择下载文件──要么是自解压缩执行文件,要么是自解压缩的RPM文件,你都可以安装它。我下载的是jdk-1_5_0_06-linux-i586.bin。下面是安装示例:
# mkdir /usr/local/java # cd /usr/local/java # cp /home/soft/jdk-1_5_0_06-linux-i586.bin ./ # chmod u+x jdk-1_5_0_06-linux-i586.bin # ./jdk-1_5_0_06-linux-i586.bin |
运行完后生成jdk1.5.0_06目录,jdk被安装在/usr/local/java/jdk1.5.0_06/。
2.2设置环境变量
配置示例如下:
JAVA_HOME=/usr/local/java/jdk1.5.0_06 PATH=$PATH:/usr/local/java/jre1.5.0_05/bin export JAVA_HOME PATH export JRE_HOME=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JRE_HOME/lib/i386:$JRE_HOME/lib/i386/client |
注意JRE_HOME的配置,若机器上没有jre环境,则安装jre,安装方法类似安装jdk
在/etc/profile文件中增加以上配置后,执行source /etc/profile命令。
3 jni实现C语言调用Java程序样例
l 编写java程序
public class Sample {
publicString name;
publicstatic String sayHello(String name) {
return"Hello, " + name + "!";
}
publicString sayHello() {
return"Hello, " + name + "!";
}
}
l 编译java程序
执行javac Sample.java
l 编写C语言程序
Sample.c
#include
#include
#include
// 环境变量PATH在windows下和linux下的分割符定义
#ifdef _WIN32
#define PATH_SEPARATOR ';'
#else
#define PATH_SEPARATOR ':'
#endif
int main(void)
{
JavaVMOptionoptions[1];
JNIEnv*env;
JavaVM*jvm;
JavaVMInitArgsvm_args;
longstatus;
jclasscls;
jmethodIDmid;
jfieldIDfid;
jobjectobj;
options[0].optionString= "-Djava.class.path=.";
memset(&vm_args,0, sizeof(vm_args));
vm_args.version= JNI_VERSION_1_4;
vm_args.nOptions= 1;
vm_args.options= options;
// 启动虚拟机
status= JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
if(status != JNI_ERR)
{
//先获得class对象
cls= (*env)->FindClass(env, "Sample");
if(cls != 0)
{
//获取方法ID, 通过方法名和签名, 调用静态方法
mid= (*env)->GetStaticMethodID(env, cls, "sayHello","(Ljava/lang/String;)Ljava/lang/String;");
if(mid != 0)
{
constchar* name = "World";
jstringarg = (*env)->NewStringUTF(env, name);
jstringresult = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);
constchar* str = (*env)->GetStringUTFChars(env, result, 0);
printf("Resultof sayHello: %s\n", str);
(*env)->ReleaseStringUTFChars(env,result, 0);
}
/***新建一个对象 ***/
//调用默认构造函数
//obj= (*env)->AllocObjdect(env, cls);
//调用指定的构造函数, 构造函数的名字叫做
mid= (*env)->GetMethodID(env, cls, "
", "()V"); obj= (*env)->NewObject(env, cls, mid); if(obj == 0) { printf("Createobject failed!\n"); } /***新建一个对象 ***/ //获取属性ID, 通过属性名和签名 fid= (*env)->GetFieldID(env, cls, "name","Ljava/lang/String;"); if(fid != 0) { constchar* name = "icejoywoo"; jstringarg = (*env)->NewStringUTF(env, name); (*env)->SetObjectField(env,obj, fid, arg); // 修改属性 } //调用成员方法 mid= (*env)->GetMethodID(env, cls, "sayHello","()Ljava/lang/String;"); if(mid != 0) { jstringresult = (jstring)(*env)->CallObjectMethod(env, obj, mid); constchar* str = (*env)->GetStringUTFChars(env, result, 0); printf("Resultof sayHello: %s\n", str); (*env)->ReleaseStringUTFChars(env,result, 0); } } (*jvm)->DestroyJavaVM(jvm); return0; } else { printf("JVMCreated failed!\n"); return-1; } }
l 编译C语言程序
执行gcc -o sample Sample.c -I$JAVA_HOME/include -I$JAVA_HOME/include/linux-L$JRE_HOME/lib/i386/client -ljvm
l 执行程序
./sample
此时出现结果:
Result of sayHello: Hello, World!
Result of sayHello: Hello,icejoywoo!
4 jni实现C语言调用Java程序过程详解
上面样例这段代码大概做了这几件事
l 创建虚拟机JVM, 在程序结束的时候销毁虚拟机JVM
与虚拟机创建相关的有这样几个变量:
1) JavaVMOption options[1];
2) JNIEnv *env;
3) JavaVM *jvm;
4) JavaVMInitArgs vm_args;
JavaVM就是我们需要创建的虚拟机实例;
avaVMOption相当于在命令行里传入的参数;
JNIEnv在Java调用C/C++中每个方法都会有的一个参数, 拥有一个JNI的环境
JavaVMInitArgs就是虚拟机创建的初始化参数, 这个参数里面会包含JavaVMOption
下面就是创建虚拟机
options[0].optionString ="-Djava.class.path=.";
memset(&vm_args, 0, sizeof(vm_args));
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = 1;
vm_args.options = options;
// 启动虚拟机
status = JNI_CreateJavaVM(&jvm,(void**)&env, &vm_args);
"-Djava.class.path=."就是传入当前路径, 作为JVM寻找class的用户自定义路径, 我们的Sample.class就在当前路径(用户根据实际情况进行修改).
vm_args.version是Java的版本, 这个应该是为了兼容以前的JDK, 可以使用旧版的JDK, 这个宏定义是在jni.h中, 有以下四种
#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#define JNI_VERSION_1_6 0x00010006
vm_args.nOptions的含义是, 你传入的options有多长, 我们这里就一个, 所以是1。
vm_args.options = options把JavaVMOption传给JavaVMInitArgs里面去。
然后就是启动虚拟机了status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args).。
可以通过这个返回值status , 知道虚拟机是否启动成功,返回值有以下几种:
#define JNI_OK 0 /* success */
#define JNI_ERR (-1) /* unknown error */
#define JNI_EDETACHED (-2) /* thread detached from the VM */
#define JNI_EVERSION (-3) /* JNI version error */
#define JNI_ENOMEM (-4) /* not enough memory */
#define JNI_EEXIST (-5) /* VM already created */
#define JNI_EINVAL (-6) /* invalid arguments */
l 寻找class对象
JVM在Java中都是自己启动的, 在C/C++中只能自己来启动了, 启动完之后的事情就和在Java中一样了, 不过要使用C/C++的语法.
获取class对象比较简单, FindClass(env, className).
cls = (*env)->FindClass(env,"Sample");
l 创建class对象的实例
在Java中的类名格式是java.lang.String, 但是className的格式有点不同, 不是使用'.'作为分割, 而是'/', 即java/lang/String.
我们知道Java中构造函数有两种, 一种是默认的没有参数的, 一种是自定义的带有参数的. 对应的在C/C++中, 有两种调用构造函数的方法.
调用默认构造函数
// 调用默认构造函数
obj = (*env)->AllocObjdect(env,cls);
构造函数也是方法, 类似调用方法的方式.
// 调用指定的构造函数, 构造函数的名字叫做<init>
mid = (*env)->GetMethodID(env, cls,"<init>", "()V");
obj = (*env)->NewObject(env, cls, mid);
调用方法和修改属性
关于方法和属性是有两个ID与之对应, 这两个ID用来标识方法和属性.
jmethodID mid;
jfieldID fid;
方法分为静态和非静态的, 所以对应的有
mid =(*env)->GetStaticMethodID(env, cls, "sayHello","(Ljava/lang/String;)Ljava/lang/String;");
mid =(*env)->GetMethodID(env, cls, "sayHello","()Ljava/lang/String;");
上面两个方法是同名的, 都叫sayHello, 但是签名不同,所以可以区分两个方法。签名可以通过执行下面的命令查看:
javap-s -private Sample
l 调用方法和修改属性
JNI的函数都是有一定规律的, Static就表示是静态, 没有表示非静态。
方法的调用如下
jstring result =(jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);
jstring result =(jstring)(*env)->CallObjectMethod(env, obj, mid);
我们可以看到静态方法是只需要class对象, 不需要实例的, 而非静态方法需要使用我们之前实例化的对象.
属性也有静态和非静态, 示例中只有非静态的。
获取属性ID
fid =(*env)->GetFieldID(env, cls, "name","Ljava/lang/String;");
修改属性的值
(*env)->SetObjectField(env, obj, fid,arg); // 修改属性
关于jstring的说明
java的String都是使用了unicode, 是双字节的字符, 而C/C++中使用的单字节的字符。
从C转换为java的字符, 使用NewStringUTF方法
jstringarg = (*env)->NewStringUTF(env, name);
从java转换为C的字符, 使用GetStringUTFChars
constchar* str = (*env)->GetStringUTFChars(env, result, 0);
5 java与c之间的数据交互
5.1 java向c传递基本数据类型
对于基本数据类型,java和c是相互对应的,所以可以直接使用。它们的对应关系为;
JNI类型映射 | ||
Java类型 | 本地类型 | 描述 |
boolean | jboolean | C/C++8位整型 |
byte | jbyte | C/C++带符号的8位整型 |
char | jchar | C/C++无符号的16位整型 |
short | jshort | C/C++带符号的16位整型 |
int | jint | C/C++带符号的32位整型 |
long | jlong | C/C++带符号的64位整型e |
float | jfloat | C/C++32位浮点型 |
double | jdouble | C/C++64位浮点型 |
Object | jobject | 任何Java对象,或者没有对应java类型的对象 |
Class | jclass | Class对象 |
String | jstring | 字符串对象 |
Object[] | jobjectArray | 任何对象的数组 |
boolean[] | jbooleanArray | 布尔型数组 |
byte[] | jbyteArray | 比特型数组 |
char[] | jcharArray | 字符型数组 |
short[] | jshortArray | 短整型数组 |
int[] | jintArray | 整型数组 |
long[] | jlongArray | 长整型数组 |
float[] | jfloatArray | 浮点型数组 |
double[] | jdoubleArray | 双浮点型数组 |
void | void | n/a |
5.2 C中返回一个字符串
...................
(*env)->NewStringUTF(env,"Huazi华仔");
...................
5.3 C中返回一个数组
.....................
int i = 0;
jintArray array;
array = (*env)->NewIntArray(env,8);
for(;i<8;i )
{
// 赋值成 0 ~ 7
(*env)->SetObjectArrayElement(env,array,i,i);
}
return array;
5.4 C中使用调用传入的参数是数组array
.........
int sum =0, i;
int len = (*env)->GetArrayLength(env,array);
jint *element =(*env)->GetIntArrayElement(env,array,0);
for(i=0;i<len;i )
{
sum = *(element i);
}
return sum;
5.5 C中调用java中类的方法 没有参数 只有返回值String
@" ()Ljava/lang/String;" 表示参数为空 返回值是String类型
JNIEXPORT jstring JNICALLJava_com_huazi_Demo_getCallBack(JNIENV env,jobject object){
jmethodID mid;
jclass cls =(*env)->FindClass(env,"com/huazi/Demo"); //后面是包名 类名
mid =(*env)->GetMethodID(env,cls,"TestMethod","()Ljava/lang/String;");//TestMethod java中的方法名
jstring msg =(*env)->CallObjectMethod(env,object,mid); //object 注意下是jni传过来的jobject
return msg;
}
5.6 C中调用java中类的静态方法 没有参数 只有返回值String
@"()Ljava/lang/String;" 表示参数为空返回值是String类型
JNIEXPORT jstring JNICALL Java_com_huazi_Demo_getCallBack(JNIENVenv,jobject object){
jmethodID mid;
jclass cls =(*env)->FindClass(env,"com/huazi/Demo"); //后面是包名 类名
mid =(*env)->GeStatictMethodID(env,cls,"TestMethod","()Ljava/lang/String;");// TestMethod java中的方法名
jstring msg =(*env)->CallStaticObjectMethod(env,cls,mid); //object 注意下是jni传过来的jobject
return msg;
}
5.7 C中调用java中类的方法 二个参数 第一个参数是int 第二个参数是String 返回值是String
"(ILjava/lang/String;)Ljava/lang/String" 表示参数是第一个参数是整形,第二个参数是String ,返回值是String
JNIEXPORT jstring JNICALLJava_com_huazi_Demo_getCallBack(JNIENV env,jobject object){
jmethodID mid;
jclass cls =(*env)->FindClass(env,"com/huazi/Demo"); //后面是包名 类名
mid = (*env)->GeStatictMethodID(env,cls,
"TestMethod","(ILjava/lang/String;)Ljava/lang/String;");// TestMethod java中的方法名
jstring param =(*env)->NewStringUTF(env,"huazi");
jstring msg =(*env)->CallStaticObjectMethod(env,cls,mid,25,param); //object 注意下是jni传过来的jobject
return msg;
}
5.8 C中调用java中的全局变量
jclass cls = (*env)->FindClass(env,"com/huazi/Demo");
jfieldID id =(*env)->GetFieldID(env,cls,"num","I"); //num 为java中的变量 I表示这个变量的类型是整形
jint param =(*env)->GetIntField(env,object,id);
jfieldID id2 =(*env)->GetFieldID(env,cls,"num2","Ljava/lang/String");//num2 为java中的变量 Ljava/lang/String表示这个变量的类型是String
jstring param2 =(*env)->GetObjectField(env,object,id2);
5.9 C中调用java中的静态的变量
jclass cls =(*env)->FindClass(env,"com/huazi/Demo");
jfieldID id =(*env)->GetStaticFieldID(env,cls,"num","Ljava/lang/String");//num 为java中的静态变量 Ljava/lang/String 表示这个变量的类型是String
jstring param =(*env)->GeStaticObjectField(env,cls,id);