上一篇谈到如何创建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"); } 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
#include "jni.h" #include "HelloJNI8.h" 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=%sn", param, charValue); return charValue; } 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=%dn", param, value); return value; } JNIEXPORT jobject JNICALL Java_HelloJNI8_hello (JNIEnv *env, jclass jc, jobject paramIn){ printf("==========Java_HelloJNI8_hello startn"); // 获取参数类 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, "", "()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【一】
JNA方式:JNI便捷开发框架JNA框架之入门(一)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)