欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > JNI开发流程

JNI开发流程

2025/9/26 14:25:20 来源:https://blog.csdn.net/CHALLENG_EVERYTHING/article/details/148343498  浏览:    关键词:JNI开发流程

一. 引言

        最近在做一个自己的项目,就是基于FastDDS封装一套JAVA库,让android和java应用可以使用dds的功能。

        由于FastDDS是使用C++编写的开源库,因此java的类库想要调用FastDDS的接口,需要额外编写一个JNI层的动态库对FastDDS的接口进行封装,并且通过注册函数的方式将JNI中的接口函数注册给上层的JAVA类库或者java应用,这样,JAVA就可以通过jni动态库中的函数间接调用到FastDDS中的接口了。

二. 流程图

        大致的流程就是jni层的so需要实现JNI_OnLoad函数,这个函数是JAVA导入jni的so后会调用的,在该函数中可以获取到JAVA层的JVM以及JNIENV指针,有必要在JNI蹭保存这个JVM指针,后面用的上,JNIEnv指针不需要保存,因为该指针和java线程绑定的。

        JNI_OnLoad中还有一个重要的操作需要用户自己实现的,就是向JAVA层注册jni中的native函数,这些可以被看做是导出函数,jni层会维护一个函数对应表格,格式大致如下:

// 导出函数表
// 第一项  jni函数在java中的函数名
// 第二项  jni函数的函数签名
// 第三项  jni函数在jni动态库中的函数名
static const JNINativeMethod methods[] = {{ "nativeCreate", "(Ljava/lang/String;Lcom/test/dds/lcbtest/Participant)Z", (void*)(fydds::jni::nativeCreate) },{ "nativeDestroySubscriber", "()V", (void*)(fydds::jni::nativeDestroySubscriber) },
};

        可以把jni中的函数理解为JAVA中有一个函数,然后进过java编译器编译出来的c++实现,只是jni中的这个JAVA函数需要用于自己用c++实现,然后注册到JAVA里面去。

        因此,在这个表格中,我们才需要申明这个函数的java签名,以便让JVM能够识别并且调用到这个jni函数。

        此外,在调用jni的JAVA代码中,还需要用public native void nativeCreate(); 这样的申明来表示这个函数是JNI中的函数,然后才可以在java代码中调用nativeCreate,如下:

        

三. 需要注意的地方

1. jni导出函数的格式

        jni导出函数有两种,一种属于是给类对象调用的,可以理解为是某个JAVA类的普通成员方法,另一种属于是可以全局调用的,可以理解为JAVA类的静态方法。

        第一种函数的函数申明中,前两个参数为JNIEnv*和jobject

        JNIEnv*可以理解为Jvm在当前调用线程的上下文,可以使用JNIEnv创建java对象,查找并且反射JVM已经加载的java类,方法,或者调用JAVA层的方法。

        jobjcet参数是调用该JNI函数的java层对象的引用。

        后面的参数就是有JAVA层和JNI层协商好,JNI层定义这些参数并且使用这些参数,而JAVA层调用该函数时传递这些参数。

        这种函数,在JAVA层申明的时候函数名前面没有static修饰符:

public class TestJNIBean{...public native String testCallMethod();  //非静态
}

        第二种函数的函数申明和前者一样前两个参数也是JNIEnv*和jobject,但是,第二个jobject参数代表的是JAVA的类本身(例如Myclass.class),而不是类对象(例如Myclass cla),其在JAVA层申明的时候函数名前面有static修饰符:

public class TestJNIBean{...public static native String testStaticCallMethod();//静态
}

2. JNIEnv的用途

        JNIEnv代表了JAVA层的运行环境,通过JNIEnv指针就可以对JAVA端代码进行操作了,例如如下操作:

  • NewObject: 创建Java类中的对象。
  • NewString: 创建Java类中的String对象。
  • NewArray: 创建类型为Type的数组对象。
  • GetField: 获取类型为Type的字段。
  • SetField: 设置类型为Type的字段的值。
  • GetStaticField: 获取类型为Type的static的字段。
  • SetStaticField: 设置类型为Type的static的字段的值。
  • CallMethod: 调用返回类型为Type的方法。
  • CallStaticMethod: 调用返回值类型为Type的static 方法。

        

3. GlobalRef和LocalRef

        前面说过JNI函数的第二个参数是个jobject,但是得注意这个jobject引用是在java栈上面的,也就是临时的,这个jobject就是一个LocalRef,当jni函数调用结束后,该引用就出栈变得无效了,因此不能直接保存,需要通过JNIEnv::NewGlobalRef将该jobject代表的栈上的JAVA对象引用变成全局的JAVA对象引用,JNIEnv::NewGlobalRef返回的就是一个全局的JAVA对象引用。

4. jni中调用JAVA方法

        JNI中调用JAVA方法很简单,就是通过反射,只要能有JNIEnv和反射到的JAVA方法的MethodID,就可以在JNI的native方法中反过来调用JAVA的方法。

        如果在JNI的本地方法(不是导出给JAVA用的,例如注册给FastDDS的回调函数)中,因为没有JNIEnv,没法调用JAVA方法,该怎么办?

        在JNI_OnLoad的时候我们保存了JVM指针的话,这里就可以将当前本地方法所在的本地线程挂到JVM上,就可以获得JNIEnv的指针了。

int status = _javaVM->AttachCurrentThread(&env, NULL);
if (status >= 0) {jobject j_message = env->NewDirectByteBuffer(const_cast<char *>(msg_str->data()), msg_str->size());

5. IsSameObject

        这个函数在JNIEnv中,用来比对两个java引用是否指向同一个java对象,例如我们在第一次JNI函数调用中保存了调用方java class的引用(通过NewGlobalRef),在第二次我们想比较是不是同一个JAVA对象调用了该方法,就可以用IsSameObject来比对这两个jobject是否引用了同一个java对象。

6. 静态注册和动态注册

        JNI层向JAVA层注册native函数的方式有静态和动态两种,静态的方式不需要在JNI层配置native函数映射表格,动态的方式需要。

        当使用静态注册的时候,我们一般在编写JAVA代码之前,就将这个java class中依赖的native函数申明好,在没有多余外部类依赖的情况下,用javac -h aa.java生成对应的.h文件,这个.h文件中包含了JNI层需要实现的native函数。

        接下去我们就有两种选择:

        1. 在.cpp中实现这个.h中的文件,编译成so文件,然后在java class中System.load这个动态库,jni中不需要手动注册native函数。

        2. 不直接使用.h中的函数名,参照.h中的函数申明和函数签名自己编写.h和.cpp中的native以及native函数映射表,jni中手动注册native函数

        可以看下生成的.h文件:

#ifndef _Included_com_nio_ad_dds_lcbtest_Participant
#define _Included_com_nio_ad_dds_lcbtest_Participant
#ifdef __cplusplus
extern "C" {
#endif
/** Class:     com_nio_ad_dds_lcbtest_Participant* Method:    nativePublishMessage* Signature: (Ljava/nio/ByteBuffer;I)Z*/
JNIEXPORT jboolean JNICALL Java_com_nio_ad_dds_lcbtest_Participant_nativePublishMessage(JNIEnv *, jobject, jobject, jint);/** Class:     com_nio_ad_dds_lcbtest_Participant* Method:    nativeInit* Signature: (Ljava/lang/String;)Z*/
JNIEXPORT jboolean JNICALL Java_com_nio_ad_dds_lcbtest_Participant_nativeInit(JNIEnv *, jobject, jstring);

        可以看到,native函数的函数签名在注释中已经生成好了。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词