【我的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, args);
    va_end(args);
    return result;
}

void callVoidMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...);

//call static method
template <typename K>
K callStatic2Result(JNIEnv *env, jclass objClass, jmethodID methodID, va_list args){
    return *(new K());
}

template <typename T>
T callStaticMethod(JNIEnv *env, const char *className, const char *methodName, const char *methodSig, ...){
    va_list args;
    jclass objClass = (*env).FindClass(className);
    jmethodID methodID = (*env).GetStaticMethodID(objClass, methodName, methodSig);
    va_start(args,methodSig);
    T result = callStatic2Result<T>(env, objClass, methodID, args);
    va_end(args);
    return result;
}

void callStaticVoidMethod(JNIEnv *env, const char *className, const char *methodName, const char *methodSig, ...);

bool throwSimpleException(JNIEnv *env);

#endif //OBJECT_TOOLS_H
  • 源文件object_tools.cpp
//
// Created by bennu on 2019/3/5.
//
#include <jni.h>
#include <string>
#include <string.h>
#include <typeinfo>
#include "object_tools.h"


jobject newObject(JNIEnv *env, const char *className, const char *methodSig, ...){
    va_list args;
    jclass objClass = (*env).FindClass(className);
    jmethodID newMethod = (*env).GetMethodID(objClass, "<init>", methodSig);
    va_start(args,methodSig);
    jobject obj =(*env).NewObjectV(objClass, newMethod, args);
    va_end(args);
    return obj;
}

//new array
jobjectArray newObjectArray(JNIEnv *env, const char *className, jsize size, ...){
    va_list args;
    va_start(args,size);
    jobjectArray array = env->NewObjectArray(size, env->FindClass(className), 0);
    for(int i = 0; i < size; i++){
        env->SetObjectArrayElement(array, i, va_arg(args, jobject));
    }
    va_end(args);
    return array;
}

template <>
jintArray point2Array(JNIEnv *env, jsize size, jint *items){
    jintArray array = env->NewIntArray(size);
    env->SetIntArrayRegion(array, 0, size, items);
    return array;
}

template <>
jdoubleArray point2Array(JNIEnv *env, jsize size, jdouble *items){
    jdoubleArray array = env->NewDoubleArray(size);
    env->SetDoubleArrayRegion(array, 0, size, items);
    return array;
}

template <>
jlongArray point2Array(JNIEnv *env, jsize size, jlong *items){
    jlongArray array = env->NewLongArray(size);
    env->SetLongArrayRegion(array, 0, size, items);
    return array;
}

template <>
jfloatArray point2Array(JNIEnv *env, jsize size, jfloat *items){
    jfloatArray array = env->NewFloatArray(size);
    env->SetFloatArrayRegion(array, 0, size, items);
    return array;
}

template <>
jbooleanArray point2Array(JNIEnv *env, jsize size, jboolean *items){
    jbooleanArray array = env->NewBooleanArray(size);
    env->SetBooleanArrayRegion(array, 0, size, items);
    return array;
}

//set field
template <>
void setField(JNIEnv *env, jobject obj, jfieldID fieldId, jlong value){
    (*env).SetLongField(obj, fieldId, value);
}

template <>
void setField(JNIEnv *env, jobject obj, jfieldID fieldId, jboolean value){
    (*env).SetBooleanField(obj, fieldId, value);
}

template <>
void setField(JNIEnv *env, jobject obj, jfieldID fieldId, jint value){
    (*env).SetIntField(obj, fieldId, value);
}

template <>
void setField(JNIEnv *env, jobject obj, jfieldID fieldId, jdouble value){
    (*env).SetDoubleField(obj, fieldId, value);
}

template <>
void setField(JNIEnv *env, jobject obj, jfieldID fieldId, jobject value){
    (*env).SetObjectField(obj, fieldId, value);
}

template <>
void setField(JNIEnv *env, jobject obj, jfieldID fieldId, jfloat value){
    (*env).SetFloatField(obj, fieldId, value);
}

//get field
template <>
jdouble getFieldResult(JNIEnv *env, jobject obj, jfieldID fieldId){
    return env->GetDoubleField(obj, fieldId);
}

template <>
jobject getFieldResult(JNIEnv *env, jobject obj, jfieldID fieldId){
    return env->GetObjectField(obj, fieldId);
}

template <>
jint getFieldResult(JNIEnv *env, jobject obj, jfieldID fieldId){
    return env->GetIntField(obj, fieldId);
}

template <>
jboolean getFieldResult(JNIEnv *env, jobject obj, jfieldID fieldId){
    return env->GetBooleanField(obj, fieldId);
}

template <>
jlong getFieldResult(JNIEnv *env, jobject obj, jfieldID fieldId){
    return env->GetLongField(obj, fieldId);
}

template <>
jfloat getFieldResult(JNIEnv *env, jobject obj, jfieldID fieldId){
    return env->GetFloatField(obj, fieldId);
}

/* set static field */
template <>
void setStaticField(JNIEnv *env, jclass objClass, jfieldID fieldId, jobject value){
    env->SetStaticObjectField(objClass, fieldId, value);
}

template <>
void setStaticField(JNIEnv *env, jclass objClass, jfieldID fieldId, jlong value){
    env->SetStaticLongField(objClass, fieldId, value);
}

template <>
void setStaticField(JNIEnv *env, jclass objClass, jfieldID fieldId, jdouble value){
    env->SetStaticDoubleField(objClass, fieldId, value);
}

template <>
void setStaticField(JNIEnv *env, jclass objClass, jfieldID fieldId, jint value){
    env->SetStaticIntField(objClass, fieldId, value);
}

template <>
void setStaticField(JNIEnv *env, jclass objClass, jfieldID fieldId, jboolean value){
    env->SetStaticBooleanField(objClass, fieldId, value);
}

template <>
void setStaticField(JNIEnv *env, jclass objClass, jfieldID fieldId, jfloat value){
    env->SetStaticFloatField(objClass, fieldId, value);
}

/* get static field */
template <>
jobject getStaticFieldResult(JNIEnv *env, jclass objClass, jfieldID fieldId){
    return (*env).GetStaticObjectField(objClass, fieldId);
}

template <>
jlong getStaticFieldResult(JNIEnv *env, jclass objClass, jfieldID fieldId){
    return (*env).GetStaticLongField(objClass, fieldId);
}

template <>
jint getStaticFieldResult(JNIEnv *env, jclass objClass, jfieldID fieldId){
    return (*env).GetStaticIntField(objClass, fieldId);
}

template <>
jdouble getStaticFieldResult(JNIEnv *env, jclass objClass, jfieldID fieldId){
    return (*env).GetStaticDoubleField(objClass, fieldId);
}

template <>
jboolean getStaticFieldResult(JNIEnv *env, jclass objClass, jfieldID fieldId){
    return (*env).GetStaticBooleanField(objClass, fieldId);
}

template <>
jfloat getStaticFieldResult(JNIEnv *env, jclass objClass, jfieldID fieldId){
    return (*env).GetStaticFloatField(objClass, fieldId);
}

//call method
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 <>
jint call2Result(JNIEnv *env, jobject obj, jmethodID methodID, va_list args){
    return (*env).CallIntMethodV(obj, methodID, args);
}

template <>
jfloat call2Result(JNIEnv *env, jobject obj, jmethodID methodID, va_list args){
    return (*env).CallFloatMethodV(obj, methodID, args);
}

template <>
jboolean call2Result(JNIEnv *env, jobject obj, jmethodID methodID, va_list args){
    return (*env).CallBooleanMethodV(obj, methodID, args);
}

template <>
jlong call2Result(JNIEnv *env, jobject obj, jmethodID methodID, va_list args){
    return (*env).CallLongMethodV(obj, methodID, args);
}

void callVoidMethod(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);
    (*env).CallVoidMethodV(obj, methodID, args);
    va_end(args);
}

//call static method
template <>
jobject callStatic2Result(JNIEnv *env, jclass objClass, jmethodID methodID, va_list args){
    return (*env).CallStaticObjectMethodV(objClass, methodID, args);
}

template <>
jdouble callStatic2Result(JNIEnv *env, jclass objClass, jmethodID methodID, va_list args){
    return (*env).CallStaticDoubleMethodV(objClass, methodID, args);
}

template <>
jint callStatic2Result(JNIEnv *env, jclass objClass, jmethodID methodID, va_list args){
    return (*env).CallStaticIntMethodV(objClass, methodID, args);
}

template <>
jlong callStatic2Result(JNIEnv *env, jclass objClass, jmethodID methodID, va_list args){
    return (*env).CallStaticLongMethodV(objClass, methodID, args);
}

template <>
jfloat callStatic2Result(JNIEnv *env, jclass objClass, jmethodID methodID, va_list args){
    return (*env).CallStaticFloatMethodV(objClass, methodID, args);
}

template <>
jboolean callStatic2Result(JNIEnv *env, jclass objClass, jmethodID methodID, va_list args){
    return (*env).CallStaticBooleanMethodV(objClass, methodID, args);
}

void callStaticVoidMethod(JNIEnv *env, const char *className, const char *methodName, const char *methodSig, ...){
    va_list args;
    jclass objClass = (*env).FindClass(className);
    jmethodID methodID = (*env).GetStaticMethodID(objClass, methodName, methodSig);
    va_start(args,methodSig);
    (*env).CallStaticVoidMethodV(objClass, methodID, args);
    va_end(args);
}


bool throwSimpleException(JNIEnv *env){
    jthrowable ex = NULL;
    ex = (*env).ExceptionOccurred();
    if(ex){
        //(*env).ExceptionDescribe();  //在java层打印堆栈信息
        (*env).ExceptionClear();     //清除引发的异常
        //注意:在清除异常之前不能做任何操作(比如将FindClass移至函数开始)否则会crash
        jstring msg = (jstring)callMethod<jobject>(env, ex, "getMessage", "()Ljava/lang/String;");
        jclass newExClass = (*env).FindClass("java/lang/Exception");
        const char* result = env->GetStringUTFChars(msg, JNI_FALSE);
        (*env).ThrowNew(newExClass, result);
        env->ReleaseStringUTFChars(msg, result);
        return true;
    }
    return false;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值