我的C/C++语言学习进阶之旅转载:实现一个在JNI中调用Java对象的工具类
Posted 字节卷动
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的C/C++语言学习进阶之旅转载:实现一个在JNI中调用Java对象的工具类相关的知识,希望对你有一定的参考价值。
一、原文地址
二、原文
2.1 前言
我们知道在jni中执行一个java函数需要调用几行代码才行,如
jclass objClass = (*env).GetObjectClass(obj);
jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);
jobject result = (*env).CallObjectMethod(obj, methodID, ...);
这样使用起来很不方便,尤其当需要大量的调用java函数就会产生大量的上述代码,由此我产生了一个开发封装这些操作的工具类,以便大量简化我们的开发。
2.2 简单封装
其实可以看到整个过程基本是固定不变的:先获取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")
这样就可以大大减少了代码量,而且代码也更易读了。
2.3 优化
通过上面可以看到这些函数大部分代码都非常类似,只有一行代码和返回值有区别,所以我考虑使用函数模版来进行优化,如下:
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
等方法都不行。
我考虑两种方法来解决这个问题,一种是保证编译不报错,因为运行时不会执行的代码,只要通过编译就可以。另外一种是不同的类型编译不同的代码。
2.3.1 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。所以并不建议这种方式。
2.3.2 模版函数特例化
将差异代码部分封装到另一个模版函数中,并且对每种类型进行特例化,这样还可以去掉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
即可。
2.3.2.1 undefined reference to
使用模版函数出现这个问题,是因为没有将模版函数的实现写在头文件中,只将模版函数的声明在头文件中,而在源文件中实现的。
所以我们应该将模版函数的实现也写进头文件中,而模版函数特例化则可以在源文件中实现,但是注意要include头文件。
2.3.2.2 返回值是void类型
因为void的特殊性,所以如果当成泛型来处理会有很多问题,这里把返回值是void类型的单独实现一个函数即可。
2.4 总结
上面我们仅仅是实现了调用普通函数的工具,根据这个思路我们还可以实现调用静态函数、获取成员变量、赋值成员变量等,这样当我们在进行jni开发的时候,如果需要对java对象或类进行操作,只需要一行代码就可以了。
2.5 源码
关注本公众号(BennuCTech),发送“JNIObjectTools”获取源码。
三、工具类代码
- 头文件object_tools.h
//
// Created by bennu on 2019/3/5.
//
#ifndef OBJECT_TOOLS_H
#define OBJECT_TOOLS_H
#include <jni.h>
#include <string>
#include <string.h>
#include <typeinfo>
jobject newObject(JNIEnv *env, const char *className, const char *methodSig, ...);
template <typename T>
const char* getBaseClassSig(const char *sig)
if(typeid(T) == typeid(jint))
return "I";
if(typeid(T) == typeid(jdouble))
return "D";
if(typeid(T) == typeid(jlong))
return "J";
if(typeid(T) == typeid(jboolean))
return "Z";
if(typeid(T) == typeid(jfloat))
return "F";
return sig;
//new array
jobjectArray newObjectArray(JNIEnv *env, const char *className, jsize size, ...);
template <typename T, typename K>
T point2Array(JNIEnv *env, jsize size, K *items)
return *(new T());
template <typename T, typename K>
T newArray(JNIEnv *env, jsize size, ...)
va_list args;
va_start(args,size);
K* items = new K[size];
for(int i = 0; i < size; i++)
items[i] = va_arg(args, K);
T array = point2Array<T, K>(env, size, items);
va_end(args);
return array;
//set field
template <typename K>
void setField(JNIEnv *env, jobject obj, jfieldID fieldId, K value)
template <typename T>
void setField(JNIEnv *env, jobject obj, const char *fieldName, T value, const char *fieldSig = "")
jclass objClass = (*env).GetObjectClass(obj);
jfieldID fieldId = (*env).GetFieldID(objClass, fieldName, getBaseClassSig<T>(fieldSig));
setField(env, obj, fieldId, value);
//get field
template <typename K>
K getFieldResult(JNIEnv *env, jobject obj, jfieldID fieldId)
return *(new K());
template <typename T>
T getField(JNIEnv *env, jobject obj, const char *fieldName, const char *fieldSig = "")
jclass objClass = (*env).GetObjectClass(obj);
jfieldID fieldId = (*env).GetFieldID(objClass, fieldName, getBaseClassSig<T>(fieldSig));
T result = getFieldResult<T>(env, obj, fieldId);
return result;
/* set static field */
template <typename K>
void setStaticField(JNIEnv *env, jclass objClass, jfieldID fieldId, K value)
template <typename T>
void setStaticField(JNIEnv *env, const char *className, const char *fieldName, T value, const char *fieldSig = "")
jclass objClass = (*env).FindClass(className);
jfieldID fieldId = (*env).GetStaticFieldID(objClass, fieldName, getBaseClassSig<T>(fieldSig));
setStaticField<T>(env, objClass, fieldId, value);
;
/* get static field */
template <typename K>
K getStaticFieldResult(JNIEnv *env, jclass objClass, jfieldID fieldId)
return *(new K());
template <typename T>
T getStaticField(JNIEnv *env, const char *className, const char *fieldName, const char *fieldSig = "")
jclass objClass = (*env).FindClass(className);
jfieldID fieldId = (*env).GetStaticFieldID(objClass, fieldName, getBaseClassSig<T>(fieldSig));
T result = getStaticFieldResult<T>(env, objClass, fieldId);
return result;
;
//call method
template <typename K>
K call2Result(JNIEnv *env, jobject obj, jmethodID methodID, va_list args)
return *(new K());
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<以上是关于我的C/C++语言学习进阶之旅转载:实现一个在JNI中调用Java对象的工具类的主要内容,如果未能解决你的问题,请参考以下文章
我的C语言学习进阶之旅关于C/C++内存对齐读取文件产生的问题以及解决方法
我的C/C++语言学习进阶之旅JNI开发之转换C层返回的结构体为Java实体Bean
我的C/C++语言学习进阶之旅JNI开发之转换C层返回的结构体为Java实体Bean