实现一个在JNI中调用Java对象的工具类,从此一行代码就搞定!

简介: 我们知道在jni中执行一个java函数需要调用几行代码才行,如jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);jobject result = (*env).CallObjectMethod(obj, methodID, ...);这样使用起来很不方便,尤其当需要大量的调用java函数就会产生大量的上述代码,由此我产生了一个开发封装这些操作的工具类,以便大量简化我们的开发。作者:Benn

前言


我们知道在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对象或类进行操作,只需要一行代码就可以了。



目录
相关文章
|
2天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
15天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
16天前
|
XML 安全 Java
Java反射机制:解锁代码的无限可能
Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。 反射机制能够使程序具备更大的灵活性和扩展性
26 5
Java反射机制:解锁代码的无限可能
|
12天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
42 3
|
18天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
50 10
|
13天前
|
分布式计算 Java MaxCompute
ODPS MR节点跑graph连通分量计算代码报错java heap space如何解决
任务启动命令:jar -resources odps-graph-connect-family-2.0-SNAPSHOT.jar -classpath ./odps-graph-connect-family-2.0-SNAPSHOT.jar ConnectFamily 若是设置参数该如何设置
|
12天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
java类模拟客户端调用servlet (httpClient)
最近做项目,用到JAVA普通类调用一个servlet应用,所以把部分代码也贴上来了。
143 0
java类模拟客户端调用servlet (httpClient)
java类模拟客户端调用servlet (httpClient)
198 0
|
6天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。