JNI系列入门之C语言与Java的双向通信(一)

JNI系列文章:
JNI系列之入门Hello JNI C(一)
JNI系列之入门Hello JNI C(二)
JNI系列入门之C语言与Java的双向通信(一)
JNI系列入门之C语言与Java的双向通信(二)
JNI系列入门之C语言中文字符串乱码问题


一、概述

  1. Java层向C层通信
  • 通过调用静态无参数、有参数的native方法
  • 通过调用非静态无参数、有参数的native方法
  1. C层向Java层通信
  • C层访问和修改Java层的属性
  • C层访问和修改Java层的静态属性
  • C层访问Java层的方法
  • C层访问Java层的静态方法
  • C层访问Java层的构造方法,并创建Java对象返回
  • java中传入数组
  • C中生成一个数组返回给java

二、实现

Java层向C层通信

  • 通过调用静态无参数、有参数的native方法
JniTest.java

// native的静态方法,生成的JNI函数参数是(JNIEnv *env, jclass jcls)
public native static String getStringFromC();
// 生成的JNI函数参数是(JNIEnv *env, jclass jcls, jstring jstr_input)
public native static String getNewString(String input);

在java中声明两个native方法,然后通过javah命令生成头文件,具体的头文件生成步骤,可以看JNI系列之入门Hello JNI C(一)

com_jerry_jnitest_JniTest.h

JNIEXPORT jstring JNICALL Java_com_jerry_jnitest_JniTest_getStringFromC
  (JNIEnv *, jclass);

JNIEXPORT jstring JNICALL Java_com_jerry_jnitest_JniTest_getNewString
(JNIEnv *, jclass, jstring);

具体实现:

// 静态无参函数实现,返回一个C的字符串
JNIEXPORT jstring JNICALL Java_com_jerry_jnitest_JniTest_getStringFromC
(JNIEnv *env, jclass jcls) {
    char *text = "Hi, Jerry! 动态链接库,调用起来了!";
    return (*env)->NewStringUTF(env, text);
}

// 静态有参函数实现,新建一个C字符串与java输入的字符串拼接后返回给java
JNIEXPORT jstring JNICALL Java_com_jerry_jnitest_JniTest_getNewString
(JNIEnv *env, jclass jcls, jstring jstr_input) {
        // 将java的输入参数jstring -> c字符串
    char *input = (*env)->GetStringUTFChars(env, jstr_input, NULL);
    char text[30] = "Jerry";
    // 拼接字符串
    char *new_text = strcat(text, input);
    printf("newText = %s\n", new_text);
    // 创建一个jstring返回给java
    jstring jstr_new = (*env)->NewStringUTF(env, new_text);
    return jstr_new;
}

getStringFromC函数中,我们使用(*env)来调用NewStringUTF函数通过传入C字符指针也就是字符串来创建一个jstring类型的字符串(对应java数据类型的String),很简单。也许你会问,为什么是(*env)来调用函数,因为JNIEnv本身就是一个结构体指针了,所以env就是结构体二级指针变量,而NewStringUTF函数又是定义在JNIEnv这个结构体指针的结构体里的函数(这是一个函数指针),所以需要用(*env)来取到结构体一级指针变量的地址,来获取其地址所指向的内容。具体可以看上一篇文章JNI系列之入门Hello JNI C(二)
getNewString函数中多了一个参数,jstr_input表示java中native方法的参数String input,在函数实现里,先将jstring转换成c字符串,然后引入c的函数库string.h头文件,使用strcat拼接出一个新的c字符串,然后再用NewStringUTF函数把新的C字符串->jstring,返回给java。

public static void main(String[] args) {
        // 在main方法中调用native方法
        System.out.println(getStringFromC());
        System.out.println(getNewString("真是太帅了..."));
}

输出结果:


Paste_Image.png

这是java中的native方法调用,看的出来,C的中文字符串乱码,而java传入的中文字符串不会乱码。关于C的中文字符串乱码问题,我将会在下一篇文章里说明解决方案。

  • 通过调用非静态无参数、有参数的native方法
// native的对象方法,生成的NI函数参数是(JNIEnv *env, jobject jobj)
    public native String getNameFromC();

C中的头文件实现:

// 非静态无参函数
JNIEXPORT jstring JNICALL Java_com_jerry_jnitest_JniTest_getNameFromC
(JNIEnv *env, jobject jobj) {
    
    return (*env)->NewStringUTF(env, "call getNameFromC");
}

对于非静态native方法,就是函数的参数变成了jobject,这个jobj就是在java中调用非静态native方法的那个对象,比如:

JniTest  jt = new JniTest();
jt.getNameFromC();

jobj就表示对象jt。而静态的jclass就表示native方法声明的那个类类型比如JniTest.class。

C层向Java层通信

  • C层访问和修改Java层的属性
    我们在java中定义一个属性name:
public String name = "Jerry";

为了方便编写使用C层来调用Java层的内容,接下来都会先在Java层创建一个native方法,然后在C层native函数实现里来调用访问Java层的内容。

/**
  * @return 修改后的属性内容
  */
    public native String accessField();

C中的实现:

// 1. 访问java非属性
// 修改java中属性name的值并返回
JNIEXPORT jstring JNICALL Java_com_jerry_jnitest_JniTest_accessField
(JNIEnv *env, jobject jobj) {

    // 获取java中name属性所在对象的class类类型
    jclass jcls = (*env)->GetObjectClass(env, jobj);

    // 获取name属性的fieldID
    // (参数:name表示Java中属性的名字,最后一个参数表示属性的类型签名)
    jfieldID fid = (*env)->GetFieldID(env, jcls, "name", "Ljava/lang/String;");
    if (fid == NULL) {
        printf("fid is NULL!");
    }
    
    // 通过属性的fieldId获取属性的值
    jstring jstr_fvalue = (*env)->GetObjectField(env, jobj, fid);

    // 要想操作修改属性值,得先把jstring -> 转换成 c的字符串
    // 最后一个参数:JNI_TRUE是一个宏定义值是1,表示true需要拷贝(拷贝过一份内存地址),
    // JNI_FALSE表示不需要拷贝(和java使用同一份字符串内存地址),官方建议使用NULL
    char *ch_fvalue = (*env)->GetStringUTFChars(env, jstr_fvalue, NULL);

    // 使用string.h的字符串操作库修改属性值
    char text[20] = "hello ";
    char *new_fvalue = strcat(text, ch_fvalue);
    // c -> java
    jstring new_jstr = (*env)->NewStringUTF(env, new_fvalue);
    // 修改属性的值
    (*env)->SetObjectField(env, jobj, fid, new_jstr);

    return new_jstr;
}

代码中注释写的很清楚了,这里还需要注意的是关于属性和方法的签名规则:


官方文档

官方文档
数据类型 签名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V
Object L开头,然后以/分隔包的完整类型名,后面再加";"(分号),比如说 String 签名就是Ljava/lang/String;
int[]
Object[]
以[开头,在加上数组元素类型的签名,比如int[] 签名就是[I , 再比如 int[][] 签名就是 [[I ,object数组签名就是 [Ljava/lang/Object;
  • C层访问和修改Java层的静态属性

在java中定义一个静态属性size:

private static int size = 26;

在C中实现一个函数用来修改java的这个静态属性:

// 2. 访问java静态属性
// 修改java中静态属性size的值并返回
JNIEXPORT void JNICALL Java_com_jerry_jnitest_JniTest_accessStaticField
(JNIEnv *env, jobject jobj) {

    // 获取jclass
    jclass cls = (*env)->GetObjectClass(env, jobj);
    // 获取jfieldID
    jfieldID fID = (*env)->GetStaticFieldID(env, cls, "size", "I");
    // 获取属性值
    jint size= (*env)->GetStaticIntField(env, cls, fID);
    size += 12;
    // 修改属性值
    (*env)->SetStaticIntField(env, cls, fID, size);
}

经过上面非静态属性的访问,静态的属性就很简单了,一样的套路:获取jclass -> 获取属性的jfieldID -> 获取属性值 ->设置属性值。还有一个套路:获取属性值 一般都是GetStatic<Type>Field,设置属性值一般都是SetStatic<Type>Field。


JNI系列文章:
JNI系列之入门Hello JNI C(一)
JNI系列之入门Hello JNI C(二)
JNI系列入门之C语言与Java的双向通信(一)
JNI系列入门之C语言与Java的双向通信(二)
JNI系列入门之C语言中文字符串乱码问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容