前言

我们知道在jni中执行一个java函数需要调用几行代码才行,如

jclass objClass = (*env).GetObjectClass(obj);
jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);
jobject result = (*env).CallObjectMethod(obj, methodID, ...);

这样使用起来很不方便,尤其当需要大量的调用java函数就会产生大量的上述代码,由此我产生了一个开发封装这些操作的工具类,以便大量简化我们的开发。

简单封装

其实可以看到整个过程基本是固定不变的:先获取Class,然后获取method,然后在执行call。所以可以简单的先封装成一系列工具函数,如:

jobject callObjMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);jobject result = (*env).CallObjectMethodV(obj, methodID, args);va_end(args);return result;
}jint callIntMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);jint result = (*env).CallIntMethodV(obj, methodID, args);va_end(args);return result;
}jboolean callBooleanMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);jboolean result = (*env).CallBooleanMethodV(obj, methodID, args);va_end(args);return result;
}

这样当我们要通过jni执行某个java函数的时候,就一行代码就可以搞定了,比如String.length()

jint len = callIntMethod(env, str, "length", "()I")

这样就可以大大减少了代码量,而且代码也更易读了。

优化

通过上面可以看到这些函数大部分代码都非常类似,只有一行代码和返回值有区别,所以我考虑使用函数模版来进行优化,如下:

template <typename T>
T callMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);T result;if(typeid(T) == typeid(jobject)){result = (*env).CallObjectMethodV(obj, methodID, args);}if(typeid(T) == typeid(jdouble)){result = (*env).CallDoubleMethodV(obj, methodID, args);}...va_end(args);return *result;
}

这样只要调用callMethod<return type>即可,愿望很美好,但是上面代码实际上是无法通过编译。

因为模版函数实际上是在编译时,根据调用的类型,拷贝生成多个具体类型的函数以便使用。

所以如果有这样的调用callMethod<jobject>(...),在编译时就会拷贝成一个如下的函数:

jobject callMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);jobject result;if(typeid(jobject) == typeid(jobject)){result = (*env).CallObjectMethodV(obj, methodID, args);}if(typeid(jobject) == typeid(jdouble)){result = (*env).CallDoubleMethodV(obj, methodID, args);}...va_end(args);return *result;
}

注意这行代码:

if(typeid(jobject) == typeid(jdouble)){result = (*env).CallDoubleMethodV(obj, methodID, args);
}

虽然实际上是无法执行的代码,但是编译时还是会进行检查,由于将jdouble类型的赋值给jobject类型的result,所以编译不通过,类型无法转换。而且这里用强转static_cast等方法都不行。

我考虑两种方法来解决这个问题,一种是保证编译不报错,因为运行时不会执行的代码,只要通过编译就可以。另外一种是不同的类型编译不同的代码。

void指针

在c++中void指针可以被赋值任何类型指针,且void指针强转为任何类型指针在编译时不会报错。代码如下:

template <typename T>
T callMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);T* result = new T();if(typeid(T) == typeid(jobject)){jobject objec = (*env).CallObjectMethodV(obj, methodID, args);void *p = &objec;result = (T*)p;}if(typeid(T) == typeid(jdouble)){jdouble doub = (*env).CallDoubleMethodV(obj, methodID, args);void *p = &doub;result = (T*)p;}va_end(args);return *result;
}

当然利用void指针很不安全,虽然可以通过编译,但是执行时如果类型不同会直接造成crash。所以并不建议这种方式。

模版函数特例化

将差异代码部分封装到另一个模版函数中,并且对每种类型进行特例化,这样还可以去掉if-else判断,代码如下:

template <typename K>
K call2Result(JNIEnv *env, jobject obj, jmethodID methodID, va_list args){return *(new K());
}template <>
jobject call2Result(JNIEnv *env, jobject obj, jmethodID methodID, va_list args){return (*env).CallObjectMethodV(obj, methodID, args);
}template <>
jdouble call2Result(JNIEnv *env, jobject obj, jmethodID methodID, va_list args){return (*env).CallDoubleMethodV(obj, methodID, args);
}
...template <typename T>
T callMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);T result = call2Result<T>(env, obj, methodID, args);va_end(args);return result;
}

这样在编译时,如果返回值是jobject类型的,当编译到call2Result时,就会直接调用jobject call2Result(...)这个函数,就不再涉及类型转换的问题。

这样去掉了if判断,但是由于没有通用的函数,所以所有使用的类型都需要特例化,如果某个类型未特例化,代码执行可能就会有问题。而在jni中,与java对应的类型其实就那么十几种,所以我们只要全部实现一遍call2Result即可。

undefined reference to

使用模版函数出现这个问题,是因为没有将模版函数的实现写在头文件中,只将模版函数的声明在头文件中,而在源文件中实现的。

所以我们应该将模版函数的实现也写进头文件中,而模版函数特例化则可以在源文件中实现,但是注意要include头文件。

返回值是void类型

因为void的特殊性,所以如果当成泛型来处理会有很多问题,这里把返回值是void类型的单独实现一个函数即可。

总结

上面我们仅仅是实现了调用普通函数的工具,根据这个思路我们还可以实现调用静态函数、获取成员变量、赋值成员变量等,这样当我们在进行jni开发的时候,如果需要对java对象或类进行操作,只需要一行代码就可以了。

源码

关注公众号:BennuCTech,发送“JNIObjectTools”获取源码。

实现一个在JNI中调用Java对象的工具类,从此只需一行代码相关推荐

  1. Java 对象深拷贝工具类

    目录 1. 使用场景 1.1 场景一 1.2 场景二 2. Spring 中的对象拷贝 3. 本工具类中的对象拷贝 3.1 拷贝对象本身(单个) 3.2 拷贝对象本身(批量) 3.3 拷贝对象属性至其 ...

  2. php导航代码在线编辑器,只需一行代码,轻松实现一个在线编辑器

    在大部分人眼里,技术宅给人的印象是沉默寡言,总摸不透他心里想些什么,彼此都保持距离.作为半个程序员,我觉得真正的技术宅大部分时间都在找乐子,鼓捣各种想法,和大部分人的极客心理是一样的,程序员也还爱讲笑 ...

  3. java 对象 转换 工具类_Java中excel与对象的互相转换的通用工具类编写与使用(基于apache-poi-ooxml)...

    通用excel与对象相互转换的工具类 前言:最近开发需要一个Excel批量导入或者导出的功能,之前用过poi-ooxml开发过一个导入的工具类,正好蹭着这次机会,把工具类的功能进行完善. 使用说明: ...

  4. 自定义java对象转换工具类

    背景 项目中经常有VO.PO.DTO等之间转换,由于apache工具类中BeanUtils.copyProperties及Json序列化反序列化方式转换性能比较低(阿里巴巴规范检查有提示不建议采用). ...

  5. java 对象序列化工具类

    使用oss开发过程中涉及到了上传.下载的断点续传,需要将对象序列化为文件保存,于是写个工具类方便调用 import java.io.File; import java.io.FileInputStre ...

  6. 又一个神器!只需一行代码,纯文本秒变Markdown

    机器之心报道 只要你有纯文本编辑器,加上一条语句,瞬间它就可以成为 Markdown 编辑器. Markdeep 是一个用来写纯文本的插件,它能以 Markdown 的语法与渲染方式纯文本,并在网页上 ...

  7. PIE-engine 教程 ——Landsat 8 TOA数据常见的4中去云方式,其中一种比GEE简单只需一行代码

    本文的主要目的是在PIE中使用代码完成Landsat 8的去云工作, 这里我们首先查看一下我们所需要的影像数据: QA_PIXEL -- -- -- QA Bitmask Bitmask for BQ ...

  8. android jni 调用java对象_Android NDK开发之Jni调用Java对象

    本地代码中使用Java对象 通过使用合适的JNI函数,你可以创建Java对象,get.set 静态(static)和 实例(instance)的域,调用静态(static)和实例(instance)函 ...

  9. Python利用JPype调用Java对象方法【实现在Python中调用JAVA】

    一.JPype简述 1.JPype是什么? JPype是一个能够让 python 代码方便地调用 Java 代码的工具,从而克服了 python 在某些领域(如服务器端编程)中的不足. 2.JPype ...

最新文章

  1. 阎王爷让我给他做个生死簿后台管理系统
  2. Kafka消息序列化和反序列化(下)
  3. 深度学习:在图像上找到手势_使用深度学习的人类情绪和手势检测器:第1部分
  4. fanuc机器人与视觉通信_要说工厂干起活来:工业机器人和数控机床才是真正的一对!...
  5. python从小到大的顺序输出_「小白专栏」Python中使用for循环,为什么输出结果不是按顺序?...
  6. 重磅公开!阿里语音识别模型端核心技术,让你“听”见未来
  7. 目标检测——如何处理任意输入尺寸的图片
  8. Linux开机加载新内核,linux-kernel – 为什么加载内核地址,ramdisk在启动时很重要?...
  9. 五大地形等高线特征_【新微专题】从等高线地形图的实际应用分析如何培养图表判读能力?...
  10. 人脸识别及数据流处理
  11. Unity 之 自定义编辑器布局
  12. 为什么 BI 软件都搞不定关联分析?带你分析分析
  13. 嵌入式工程师必须知道的一些好网站
  14. 【导数术】10.导数数列不等式
  15. cs与msf的联合使用
  16. RRDtool 中文攻略
  17. Swift5.1 语言参考(三) 类型
  18. Android 曝光采集(商品view曝光量的统计)
  19. 【百金轻】:雄关漫道真如铁,而今迈步从头越。
  20. 区间问题,Huffman树,排序不等式,绝对值不等式,推公式

热门文章

  1. checkedListBox使用例子
  2. 高创新出GoTVbox多路电视解调器
  3. [Ynoi2018]末日时在做什么?有没有空?可以来拯救吗?
  4. JavaScript 对引擎、运行时、调用堆栈的概述理解
  5. 客户端页面不更新CSS样式或JS脚本的方法 (2018-08-17 17:33)
  6. first-软件工程
  7. C语言头文件组织与包含原则
  8. Windows Phone(三)WP7版 记账本 开发(使用SQLite数据库)
  9. Hadoop作业提交分析(三)
  10. (三十三)设计模式之混合模式