继续上一篇博文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
#endif
main.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\n", 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\n");
//获取成员变量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\n", 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\n");
}
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语言实现
*
* @date 2016年3月22日 下午4:23:00
*/
public static native void print();
/**
* 本地方法,用C语言实现
*
* @date 2016年3月22日 下午6:10:43
* @param str
*/
public static native void print(String str);
/**
* 拼接字符传并返回
*
* @date 2016年3月22日 下午6:12:03
* @param str
* @return
*/
public static native String append(String str);
/**
* 测试操作Test类
*
* @date 2016年3月22日 下午6:16:06
* @param test
*/
public native void test(String test);
/**
* 测试操作Test
*
* @date 2016年3月22日 下午6:16:59
* @param test
*/
public native void test(Test test);
/**
* 将test 转16进制
*
* @date 2016年3月22日 下午6:25:06
* @param test
* @return
*/
public native String toHex(byte[] test);
/**
* 将test转字节
*
* @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;
}
/**
* 测试调用静态方法,多参数
*
* @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,可以根据实际情况转成以上类型。