一个例子掌握JNI开发
继续上一篇博文eclipse搭建JNI开发环境,现在我们从代码角度分析,C和Java混合编程时能实现的功能。
使用javah命令,编译生成.h头文件时,每个函数,至少都会有两个参数。JNIEnv 和jclass/jobject。其中,当native方法是静态方法(类方法)时,第二个参数是jclass,当native方法是成员方法时,第二个参数是jobject。其余的参数,会根据你在java文件中声明的方法参数类型,生成具体的签名。jni中类型在jni头文件中定义规则如下:
typedef union jvalue { jboolean z; jbyte b; jchar c; jshort s; jint i; jlong j; jfloat f; jdouble d; jobject l; } jvalue;对应签名:
java类型 | jni类型 | 类型签名 |
char | jchar | C |
int | jint | I |
long | jlong | J |
float | jfloat | F |
double | jdouble | D |
boolean | jboolean | Z |
byte | jbyte | B |
short | jshort | S |
void | V | |
类 | L全限定名;,比如String, 其签名为Ljava/lang/util/String; | |
数组 | [类型签名, 比如 [B |
Jni.java 文件中,对应7个native方法。
1.调用C语言的printf函数,输出固定内容。
public static native void print();
2.转入指定字符串,用printf函数输出。
public static native void print(String str);
3.用C语言实现拼接字符串的功能,并返回给java。
public static native String append(String str);
4.传入字符串,作为Test类构造函数的函数,C语言调用Java类的构造函数,生成jobject,操纵Test类的所有方法和属性。
public native void test(String test);
5.传入Test类的对象,操纵操纵Test类的所有方法和属性。
public native void test(Test test);
6.将传入的字节数组转16进制字符串返回。
public native String toHex(byte[] test);
7.将传入的字符串转成16进制字节数组返回。
public native byte[] toBytes(String test);
完整示例代码如下:
com_flueky_jni_Jni.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_flueky_jni_Jni */ #ifndef _Included_com_flueky_jni_Jni #define _Included_com_flueky_jni_Jni #ifdef __cplusplus extern "C" { #endif /* * Class: com_flueky_jni_Jni * Method: print * Signature: ()V */ JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_print__ (JNIEnv *, jclass); /* * Class: com_flueky_jni_Jni * Method: print * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_print__Ljava_lang_String_2 (JNIEnv *, jclass, jstring); /* * Class: com_flueky_jni_Jni * Method: append * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_flueky_jni_Jni_append (JNIEnv *, jclass, jstring); /* * Class: com_flueky_jni_Jni * Method: test * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_test__Ljava_lang_String_2 (JNIEnv *, jobject, jstring); /* * Class: com_flueky_jni_Jni * Method: test * Signature: (Lcom/flueky/jni/Test;)V */ JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_test__Lcom_flueky_jni_Test_2 (JNIEnv *, jobject, jobject); /* * Class: com_flueky_jni_Jni * Method: toHex * Signature: ([B)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_flueky_jni_Jni_toHex (JNIEnv *, jobject, jbyteArray); /* * Class: com_flueky_jni_Jni * Method: toBytes * Signature: (Ljava/lang/String;)[B */ JNIEXPORT jbyteArray JNICALL Java_com_flueky_jni_Jni_toBytes (JNIEnv *, jobject, jstring); #ifdef __cplusplus } #endif #endifmain.cpp
/* * main.cpp * * Created on: 2016年3月22日 * Author: flueky */ #include <stdio.h> #include "com_flueky_jni_Jni.h" #include <jni.h> #include <stdlib.h> #include <string.h> /** * 操作test类的对象 */ void operate_test(JNIEnv *env, jobject obj) { //根据对象,获取到jclass jclass test_cls = env->GetObjectClass(obj); //获取成员方法id jmethodID get_mid = env->GetMethodID(test_cls, "getTest", "()Ljava/lang/String;"); //回调成员方法 jstring test = (jstring) env->CallObjectMethod(obj, get_mid); jsize len = env->GetStringUTFLength(test); const char* str = env->GetStringUTFChars(test, JNI_FALSE); char* result = (char*) malloc(sizeof(char) * (len + 1)); strcpy(result, str); //标志结束 *(result + len) = 0; printf("getTest 输出:%s ", result); //获取append方法id,调用append方法 jmethodID append_mid = env->GetMethodID(test_cls, "append", "(Ljava/lang/String;)V"); env->CallVoidMethod(obj, append_mid, env->NewStringUTF("append test")); printf("append: append test "); //获取成员变量id,类变量id GetStaticFieldID jfieldID test_fid = env->GetFieldID(test_cls, "test", "Ljava/lang/String;"); //获取成员变量值 test = (jstring) env->GetObjectField(obj, test_fid); len = env->GetStringUTFLength(test); str = env->GetStringUTFChars(test, JNI_FALSE); result = (char*) malloc(sizeof(char) * (len + 1)); strcpy(result, str); //标志结束 *(result + len) = 0; printf("append 结果:%s ", result); //获取静态方法id jmethodID print_mid = env->GetStaticMethodID(test_cls, "print", "(ICFZLjava/lang/String;)V"); //调用静态方法 env->CallStaticVoidMethod(test_cls, print_mid, 1, "c", 1.2f, true, test); //删除obj对象 env->DeleteLocalRef(obj); env->DeleteLocalRef(test); } JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_print__(JNIEnv *env, jclass cls) { printf("小飞哥0217 "); } JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_print__Ljava_lang_String_2( JNIEnv *env, jclass cls, jstring jstr) { jsize len = env->GetStringUTFLength(jstr); const char* str = env->GetStringUTFChars(jstr, JNI_FALSE); char* result = (char*) malloc(sizeof(char) * (len + 1)); strcpy(result, str); //标志结束 *(result + len) = 0; printf("本地输出:%s", result); } JNIEXPORT jstring JNICALL Java_com_flueky_jni_Jni_append(JNIEnv *env, jclass, jstring jstr) { //获取jstring 的长度 jsize len = env->GetStringUTFLength(jstr); //jstring 转字符串数组 const char* str = env->GetStringUTFChars(jstr, JNI_FALSE); //分配结果字符串空间 char* result = (char*) malloc(sizeof(char) * (len + 7 + 1)); //字符串函数处理 strcpy(result, "append "); strcpy(result + 7, str); //标志结束 *(result + 7 + len) = 0; return env->NewStringUTF(result); } /** * 操作test类 */ JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_test__Ljava_lang_String_2( JNIEnv *env, jobject obj, jstring jstr) { //Test类 jclass test_cls = env->FindClass("com/flueky/jni/Test"); //Test类的构造方法id,构造方法名固定<init>,返回类型void jmethodID init_mid = env->GetMethodID(test_cls, "<init>", "(Ljava/lang/String;)V"); //创建Test对象 jobject test_obj = env->NewObject(test_cls, init_mid, jstr); operate_test(env, test_obj); } JNIEXPORT void JNICALL Java_com_flueky_jni_Jni_test__Lcom_flueky_jni_Test_2( JNIEnv *env, jobject obj, jobject test_obj) { operate_test(env, test_obj); } JNIEXPORT jstring JNICALL Java_com_flueky_jni_Jni_toHex(JNIEnv *env, jobject jobj, jbyteArray jbytes) { //获取字符串长度 jsize len = env->GetArrayLength(jbytes); //分配结果的内存 char *result = (char *) malloc(sizeof(char) * (len * 2 + 1)); //分配缓存的内存 jbyte* temp = (jbyte *) malloc(sizeof(jbyte) * len); //从字节数组中取字符 env->GetByteArrayRegion(jbytes, 0, len, temp); //转16进制 for (int i = 0; i < len; i++) { *(result + i * 2) = ((*(temp + i) >> 4) & 0xf) + "0"; *(result + i * 2 + 1) = (*(temp + i) & 0xf) + "0"; } //释放缓存的内存 free(temp); *(result + len * 2) = 0; //生成jstring jstring str = env->NewStringUTF(result); free(result); return str; } JNIEXPORT jbyteArray JNICALL Java_com_flueky_jni_Jni_toBytes(JNIEnv *env, jobject jobj, jstring jstr) { //获取字符串长度 jsize len = env->GetStringUTFLength(jstr); //分配字节数组空间 jbyteArray jbytes = env->NewByteArray(len * 2); //将jstring转成字符数组 const jchar * temp = env->GetStringChars(jstr, JNI_FALSE); //分配结果的内存 char *result = (char *) malloc(sizeof(char) * (len * 2)); //转16进制 for (int i = 0; i < len; i++) { *(result + i * 2) = ((*(temp + i) >> 4) & 0xf) + "0"; *(result + i * 2 + 1) = (*(temp + i) & 0xf) + "0"; } //将字符存到字节数组里 env->SetByteArrayRegion(jbytes, 0, len * 2, (const jbyte *) result); free(result); return jbytes; }Jni.java
package com.flueky.jni; public class Jni { static { System.loadLibrary("JNI_CPP"); } /** * 本地方法,用C语言实现 * * @author flueky zkf@yitong.com.cn * @date 2016年3月22日 下午4:23:00 */ public static native void print(); /** * 本地方法,用C语言实现 * * @author flueky zkf@yitong.com.cn * @date 2016年3月22日 下午6:10:43 * @param str */ public static native void print(String str); /** * 拼接字符传并返回 * * @author flueky zkf@yitong.com.cn * @date 2016年3月22日 下午6:12:03 * @param str * @return */ public static native String append(String str); /** * 测试操作Test类 * * @author flueky zkf@yitong.com.cn * @date 2016年3月22日 下午6:16:06 * @param test */ public native void test(String test); /** * 测试操作Test * * @author flueky zkf@yitong.com.cn * @date 2016年3月22日 下午6:16:59 * @param test */ public native void test(Test test); /** * 将test 转16进制 * * @author flueky zkf@yitong.com.cn * @date 2016年3月22日 下午6:25:06 * @param test * @return */ public native String toHex(byte[] test); /** * 将test转字节 * * @author flueky zkf@yitong.com.cn * @date 2016年3月22日 下午6:25:17 * @param test * @return */ public native byte[] toBytes(String test); }Test.java
package com.flueky.jni; public class Test { private String test; public Test(String test) { super(); this.test = test; } public String getTest() { return test; } public void append(String str) { this.test = test + " " + str; } /** * 测试调用静态方法,多参数 * * @author flueky zkf@yitong.com.cn * @date 2016年3月22日 下午6:19:13 * @param str */ public static void print(int i, char c, float f, boolean z, String test) { System.out.println(String.format("Test printf:int = %d,char = %c,float = %.2f,boolean = %s,test = %s", i, c, f, z + "", test)); } }
main.java
package com.flueky.jni; public class Main { public static void main(String[] args) { Jni.print();// 小飞哥0217 Jni.print("csdn 测试");// 本地输出:csdn 测试 System.out.println(Jni.append("flueky"));// append flueky Jni jni = new Jni(); jni.test(new Test("小飞哥0217")); jni.test("CSCN 测试"); System.out.println(new String(jni.toBytes("ABCDE"))); System.out.println(jni.toHex("12345".getBytes())); } }
Jni方法说明:
1.获取jclass对象:
a.env->FindClass("com/flueky/jni/Test");注意,这里不是类的签名。
b.env->GetObjectClass(obj);
2.获取方法id:
a.env->GetMethodID(test_cls, "getTest","()Ljava/lang/String;");//获取成员方法id
b.env->GetStaticMethodID(test_cls, "print","(ICFZLjava/lang/String;)V");//获取静态方法id
第一个参数,jclass对象,第二个参数方法名称,第三个参数,方法签名
3.调用方法:
a.env->CallVoidMethod(obj, append_mid, env->NewStringUTF("append test"));//调用成员方法
第一个参数jobject,第二个参数方法id,后面参数,依次是Java方法中的参数。
b.env->CallStaticVoidMethod(test_cls, print_mid, 1, "c", 1.2f, true, test);//调用静态方法
第一个参数jclass,第二个参数方法id,后面参数,依次是Java方法中的参数。
4.获取属性id:
a.env->GetFieldID(test_cls, "test", "Ljava/lang/String;");//获取成员属性id
b.env->GetStaticFieldID(test_cls, "test", "Ljava/lang/String;");//获取静态属性id,程序里没用到。
第一个参数jclass,第二个参数属性名称,第三个参数属性签名
5.获取属性值:
a.env->GetObjectField(obj, test_fid);
第一个参数,jobject,第二个参数,属性id
b.env->GetStaticObjectField(test_cls, test_fid);
第一个参数,jclass,第二个参数,属性id
6.生成jobject对象,通常都是从Java方法中传递过来,还有一种情况是调用java的构造方法来生成jobject对象。
获取构造方法id,env->GetMethodID(test_cls, "<init>","(Ljava/lang/String;)V");
第一个参数jclass,第二个参数构造方法名称(固定<init>),第三个参数构造方法签名(返回类型固定void签名V)
生成jobject,env->NewObject(test_cls, init_mid, jstr);
第一个参数jclass,第二个参数构造方法id,后面的参数依次是Java中构造函数的参数。
上述3 和 5 调用的jni函数名称中,Char、Boolean、Byte、Int、Long、Short、Float、Double、Object、Void,可以相互替换,除了Void,其他类型的函数均有返回值。
typedef jobject jstring; typedef jobject jarray; typedef jarray jbooleanArray; typedef jarray jbyteArray; typedef jarray jcharArray; typedef jarray jshortArray; typedef jarray jintArray; typedef jarray jlongArray; typedef jarray jfloatArray; typedef jarray jdoubleArray; typedef jarray jobjectArray;参照在Jni头文件中的定义,Object类型的函数,返回值是jobject,可以根据实际情况转成以上类型。