一、背景
上一篇谈到如何创建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框架之入门(一)