JNI对象传参与返回对象结果-C结构体参数【二】

一、背景

上一篇谈到如何创建JNI项目以及提供调用外部SO的思路,通过简单的例子实现调用C程序。
本篇继续在之前的话题做深入的探讨,主题就是对象传参。对应C语言就是结构体参数。


二、需求

做个一个简单的需求,然后实现并验证。
希望对象传参,同时能返回对象参数。
比如现在JAVA端有参数对象 Param 和结果接收对象参数ReturnParam

public class Param {
    private int a;
    private int b;
    //描述
    private String dsc;

    public Param(int a, int b, String dsc) {
        this.a = a;
        this.b = b;
        this.dsc = dsc;
    }
    // =============================================================
	// 为了节约篇幅,省掉 get set 方法  和 tostring 方法,搭建时自行补上
}
public class ReturnParam {
	// 消息
    private String msg;
    // 结果值存放
    private int c;
    // =============================================================
	// 为了节约篇幅,省掉 get set 方法  和 tostring 方法,搭建时自行补上
}

三、JAVA端

java端程序入口 HelloJNI8

public class HelloJNI8 {

    static {
        // 加载动态链接库
        System.load("/program/jni_struct/libHelloJNI.so");
    }

    /**
     * 声明Native Method,对应hello的方法
     * 对象传参,返回码对象结果
     */
    public static native ReturnParam hello(Param param);

    public static void main(String[] args) {

        HelloJNI8 helloJNI = new HelloJNI8();
        // 调用 add方法,完成计算
        Param param = new Param(6, 8, "add");
        System.out.println("param=" + param.toString());
        // 调用 hello方法
        ReturnParam hello = HelloJNI8.hello(param);
        System.out.println("add msg(hello.msg)=" + hello.getMsg());
        System.out.println("add result(hello.c)=" + hello.getC());
        return;
    }
}

说明:
这里的目的就是为了计算 6+8的值,然后顺便传个字符串 “add” 用于验证接收。
hello方法执行后返回结果存放在 ReturnParam 中,里面包括 c 参数用于存放a+b的结果,msg存放从c程序带来的字串。
好,接下来实现C程序的处理。


四、C程序端

现有C文件HelloJNI.c

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
#include "HelloJNI8.h"

/**
 * 提取对象中指定字段名称的字符串的值
 *
 * @param env
 * @param paramInClass
 * @param paramIn 对象
 * @param param 参数字段名
 * @return
 */
const char *getStrParam(JNIEnv *env, jclass paramInClass, jobject paramIn, char *param) {
    // 获取字段id
    jfieldID fieldID = (*env)->GetFieldID(env, paramInClass, param, "Ljava/lang/String;");
    // 获取对象的字段
    jstring fieldValue = (jstring)(*env)->GetObjectField(env, paramIn, fieldID);
    // 将jstring转char *
    const char *charValue = (*env)->GetStringUTFChars(env, fieldValue, 0);
    printf("==========param %s=%s\n", param, charValue);
    return charValue;
}

/**
 * 提取对象中指定字段名称的int的值
 *
 * @param env
 * @param jc
 * @param param
 * @return
 */
int getIntParam(JNIEnv *env, jclass paramInClass, jobject paramIn, char *param) {
    // 获取字段id
    jfieldID fieldID = (*env)->GetFieldID(env, paramInClass, param, "I");
    // 获取对象的字段值
    jint fieldValue = (int)(*env)->GetIntField(env, paramIn, fieldID);
    // 将jint转int
    int value = (int)fieldValue;
    printf("==========param %s=%d\n", param, value);
    return value;
}

/*
 * Class:     c_jni_3_HelloJNI
 * Method:    hello
 * Signature: ()Lc_jni_3/HelloJNI/ReturnParam;
 */
JNIEXPORT jobject JNICALL Java_HelloJNI8_hello (JNIEnv *env, jclass jc, jobject paramIn){

    printf("==========Java_HelloJNI8_hello start\n");

    // 获取参数类
    jclass paramInClass = (*env)->GetObjectClass(env, paramIn);
    // 字符串参数提取  msg对应对象字段
    const char *dsc = getStrParam(env, paramInClass, paramIn, "dsc");
    // int参数提取  a,b对应对象字段
    int a = getIntParam(env, paramInClass, paramIn, "a");
    int b = getIntParam(env, paramInClass, paramIn, "b");

    // 定义返回参数 ReturnParam
    jclass cls = (*env)->FindClass(env, "ReturnParam");
    jmethodID methodID = (*env)->GetMethodID(env, cls, "<init>", "()V");
    // 生成返回结果对象
    jobject returnResult = (*env)->NewObjectA(env, cls, methodID, 0);

    // 获取参数字段id,不同类型用不同方式
    jfieldID resultInt = (*env)->GetFieldID(env, cls, "c", "I");
    jfieldID resultStr = (*env)->GetFieldID(env, cls, "msg", "Ljava/lang/String;");

    // 填充返回结果参数
    // 这里是将 a + b 赋值给 ReturnParam的 c; 将 ReturnParam的的msg填充指定字串
    (*env)->SetIntField(env, returnResult, resultInt, a + b);
    (*env)->SetObjectField(env, returnResult, resultStr, (*env)->NewStringUTF(env, "call hello method succeeded!!!"));

    return returnResult;

}

说明:
其实最复杂的就是C这边的编码,因为没有对象的概念,所有的东西都要自己生成,其中关键的点有
①所有jni参数的处理都离不开 JNIEnv *env
②所有字段取值前都需要取得对应jfieldID ,jfieldID 需要明确数据类型,需要符号对应如下
在这里插入图片描述
③取字段id用GetFieldID,取值用GetIntField,GetObjectField
④返回填充如果是字符串需要 (*env)->NewStringUTF(env, “call hello method succeeded!!!”) 转化成jstring


五、运行程序

部署与搭建详情请回看 上一篇

本次文件目录结构:
在这里插入图片描述
shell命令合集

javac -encoding utf-8  HelloJNI8.java;
javah -jni HelloJNI8;
gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libHelloJNI.so HelloJNI.c;
java HelloJNI8

也可以将HelloJNI8打包成 HelloJNI8.jar,然后 java -jar HelloJNI8.jar来执行


六、总结

JVAA–>C程序的动态库so,参数示意图
在这里插入图片描述


重点是采用对象传参,jni用对象参数接收。如果放到上一篇的外部环境SO的场景下,我们就可以变成
在这里插入图片描述
因为标准C和普通C都是C语言,内部不存在转换,都是C语言编写,直接用就行。

.上 一 篇.:JNI入门与进阶,JNI调用外部非标准程序SO【一】
.下 一 篇.:JNI系列最终篇-springbootJNI/springbootJNA项目搭建【三】
JNA方式:JNI便捷开发框架JNA框架之入门(一)

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cy谭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值