Unreal开发中使用JNI调用语法

embedded/2025/3/1 16:42:50/

使用Unreal引擎做Android端的应用开发,不可避免的会用到第三方Java接口,这就涉及到了JNI调用。Unreal自己有封装一套接口来调用,这里做个总结,主要是在一些自定义的数据类型转换上。其实可以跟到引擎源码的位置具体看实现。

从上一篇文章中我们知道,c++端调用的方法其实是我们定义在xml中的方法,而xml中的方法中真正调用了第三方库的Java接口。

首先假设我们的Java端接口是这么定义的,具体的实现部分我们不关心,它的包名暂定为com.example.test

public class JNITest{public static int initSDK(int var0, int var1) {}public static void uninit() {}public static Result process() {}}

这里的CustomBuff类和Result类分别如下定义,细节不要纠结,我们只是为了突出各种各样自定的结构体成员


public class Result {public int m1;public int m2;public Point position;public boolean m3;}public class Point {public float x;public float y;public Point () {}
}

好,有了Java端的定义,我们在Unreal中调用库函数的时候就要把相应的内容做转换。

首先我们在plugin的xml文件中定义好真正调用第三方Java接口的实例

<gameActivityImportAdditions><insert>import com.example.test.*;</insert>
</gameActivityImportAdditions>gameActivityClassAdditions><insert>public int init(int v1, int v2) {return JNITest.init(v1, v2);}public void uinit() {JNITest.uninit();}public Result process() {return JNITest.process();}</insert>
</gameActivityClassAdditions>

接下来,我们通过JNI调用的就是上面定义好的init、uinit、process三个方法,大致流程分3步:

  1. 获取JNIEnv,这是一个指向Java VM的指针,通过它可以获取到所有Java元素。Unreal中通过封装好的FAndroidApplication::GetJavaEnv()获取。相关源码位置:Engine\Source\Runtime\ApplicationCore\Public\Android\AndroidApplication.h
  2. 找到对应的Java方法,通过方法名、函数签名。Unreal中通过封装好的FJavaWrapper::FindMethod(...)获取。相关源码位置:Engine\Source\Runtime\Launch\Public\Android\AndroidJNI.h
  3. 调用Java方法,Unreal中通过封装好的FJavaWrapper::CallIntMethod(...)、CallVoidMethod、CallObjectMethod等几个方法。相关源码位置:Engine\Source\Runtime\Launch\Public\Android\AndroidJNI.h

下面以我们定义好的Java接口为例,调用init接口

int32 JNITest::InitSDK()
{UE_LOG(LogTemp, Warning, TEXT("JNITest InitSDK"));int32 initResult = -1;// 获取JNIEnvif (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) {// 获取Java方法,第一个参数固定,第二个参数是函数名,第三个是函数签名,第四个是可选项jmethodID InitSDKID = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "init",  "()I", false);if (InitSDKID != nullptr){UE_LOG(LogTemp, Warning, TEXT("Success to find method init"));// 通过找到的函数ID进行方法调用initResult = FJavaWrapper::CallIntMethod(Env, FJavaWrapper::GameActivityThis, InitSDKID);}}else {UE_LOG(LogTemp, Error, TEXT("Failed to get env"));}UE_LOG(LogTemp, Warning, TEXT("JNITest InitSDK result: %d"), initResult);return initResult;
}

这里会麻烦的是获取Java方法时的函数签名问题。括号里是输入参数,这里init函数没有输入参数所以是空的。括号后面跟的是函数返回值,init函数返回int类型,所以这里是“I”。关于基本类型的函数参数可以参考JNI方法签名 | 以梦为码

如果Java中有我们自己定义的类型,采用”L+包名+类名+;”,注意最后的分号。

有了这个基础,再看下面unit函数的调用就清楚多了。

void JNITest::UnitSDK()
{UE_LOG(LogTemp, Warning, TEXT("JNITestUnitSDK"));if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) {// 因为函数没有返回值,所以函数签名最后是VjmethodID UninitSDKID = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "uinit",  "()V", false);if (UninitSDKID != nullptr) {UE_LOG(LogTemp, Warning, TEXT("Success to find method uninit"));// 对于没有返回值的Java方法,调用的是CallVoidMethodFJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, UninitSDKID);}}else{UE_LOG(LogTemp, Error, TEXT("Failed to get env"));}
}

最后看下process函数的调用,这个函数返回值为Result类型,没有输入参数

首先我们要在c++端定义和Java同样类型的结构体,不管是作为函数的输入赋值,还是获取函数返回值都要定义struct CppPoint
{float x;float y;
};struct CppResult
{int32 m1;int32 m2;Point position;bool m3;
};
CppResult JNITest::process() {UE_LOG(LogTemp, Warning, TEXT("JNITest process"));CppResult ret = new CppResult();if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) {// 通过函数签名查找process方法jmethodID processID = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "process",  "()Lcom/example/test/Result;", false);if (processID != nullptr) {// 找到Java类型的Result类jclass resultClass = FAndroidApplication::FindJavaClass("Lcom/example/test/Result");if (!resultClass) {UE_LOG(LogTemp, Error, TEXT("Failed to find class Result"));return ret;}// 找到类的构造函数jmethodID resultConstructor = Env->GetMethodID(resultClass, "<init>", "()V");if (!resultConstructor) {UE_LOG(LogTemp, Error, TEXT("Failed to find method Result()"));return ret;}// 构造类对象jobject resultObj = Env->NewObject(resultClass, resultConstructor);if (!resultObj){UE_LOG(LogTemp, Error, TEXT("Failed to create resultObj"));return ret;}// 调用process方法获取结果resultObj = FJavaWrapper::CallObjectMethod(Env, FJavaWrapper::GameActivityThis, processID);// 将获取到的jobject对象转换到c++类型,这样我们在c++中就能拿到函数返回值ret = convertJObjectTCppResult(Env, resultObj);}}else {UE_LOG(LogTemp, Error, TEXT("Failed to get env"));}return ret;
}CPPResult JNITest::convertJObjectTCppResult(JNIEnv *env, jobject resultObj)
{UE_LOG(LogTemp, Warning, TEXT("JNITest convertJObjectToCppResult"));CPPResult result;jclass detectResultClass = env->GetObjectClass(resultObj);// 找到Java类中Result类的各个成员并取值jfieldID m1ID = FJavaWrapper::FindField(env, detectResultClass, "m3", "Z", false);result.m3 = env->GetBooleanField(resultObj, m1ID);jfieldID m2ID = FJavaWrapper::FindField(env, detectResultClass, "m1", "I", false);result.m1 = env->GetIntField(resultObj, m2ID);jfieldID m3ID = FJavaWrapper::FindField(env, detectResultClass, "m2", "I", false);result.m2 = env->GetIntField(resultObj, m3ID);// 因为position成员又是一个自定义的类,所以这里还要进行一次转换jclass pointClass = FAndroidApplication::FindJavaClass("com/example/test/Point");if (!pointClass) {UE_LOG(LogTemp, Error, TEXT("Failed to find class pointClass"));return result;}jfieldID posID = FJavaWrapper::FindField(env, detectResultClass, "position", "Lcom/example/test/Point;", false);result.position = convertJobjectToPoint(env, env->GetObjectField(resultObj, posID));return result;
}Point JNITest::convertJobjectToPoint(JNIEnv *env, jobject posObj) {CppPoint result;// 获取 Point 类jclass PointClass = env->GetObjectClass(posObj);// 获取 获取 x 和 y 的值jfieldID xID = FJavaWrapper::FindField(env, PointClass, "x", "F", false);result.x = env->GetFloatField(arcPointObj, xID);jfieldID yID = FJavaWrapper::FindField(env, PointClass, "y", "F", false);result.y = env->GetFloatField(arcPointObj, yID);return result;
}


http://www.ppmy.cn/embedded/169094.html

相关文章

什么是标记 PDF(Tagged PDF)?

什么是标记 PDF&#xff08;Tagged PDF&#xff09;&#xff1f; 标记 PDF 是一种包含额外信息的 PDF 文件&#xff0c;这些信息用于定义文档的结构&#xff08;如文本流、标题、表格、段落等&#xff09;。这非常有用&#xff0c;因为它可以使内容更加可访问&#xff08;文本…

短跑怎么训练提高最快·棒球1号位

棒球运动员的短跑能力直接影响跑垒、防守和进攻效率&#xff0c;提升短跑速度需结合专项需求&#xff08;如爆发力、加速度、变向能力&#xff09;进行系统训练。以下为针对性训练方案&#xff1a; 一、专项爆发力训练&#xff08;提升起跑速度&#xff09; 抗阻冲刺 用弹力带…

Angular Superresolution with Antenna Pattern Errors论文阅读

Angular Superresolution with Antenna Pattern Errors 1. 论文的研究目标与实际问题意义1.1 研究目标1.2 实际问题与产业意义2. 论文的创新方法与模型2.1 天线方向图误差建模2.2 改进算法:CID + 重加权 l 1 l_1 l1​最小化2.2.1 传统CID算法局限性2.2.2 新算法核心步骤2.2.3 …

单片机死机跑飞的原因

单片机死机跑飞的原因 硬件问题一.电源问题二.时钟异常三.复位电路故障四.电磁干扰&#xff08;EMI&#xff09;五.外设负载过重六.温度影响 软件原因一.内存管理问题二.中断处理错误三.看门狗配置不当四.多任务冲突 记录一下导致单片机跑飞可能出现的原因。 硬件问题 一.电源…

属性的设置

笔记 class Student:def __init__(self, name, gender):self.name nameself.__gender gender # self.__gender 是私有的实例属性# 使用property 修改方法&#xff0c;将方法转成属性使用propertydef gender(self):return self.__gender# 将我们的gender这个属性设置为可写属…

Linux学习笔记1

root用户 进入方式 su - root 可以使用exit退回到上一个用户,或者ctrl d 可以使用sudo命令为普通用户授权 但需要为普通用户配置sudo认证 执行visudo命令会自动打开/etc/sudoers 在文件的最后添加:用户名 ALL(ALL) NOPASSWD: ALL 最后wq保存 用户和用户组 Linux可以 配置多个用…

微信小程序换行符真机不生效问题

标签必须使用text包裹 <text>你好你好{{"\n"}}你好你好</text>上面代码在模拟器正常&#xff0c;在真机上面原样显示/n文本了&#xff0c;没实现换行效果 <text>你好你好{{"\n"}}你好你好</text>改成这种格式的话&#xff0c;模…

rust web框架actix和axum比较

在选择 Actix Web 和 Axum 时&#xff0c;可以根据项目需求、开发习惯以及对框架生态的要求来判断。以下是它们的比较和适用场景分析&#xff1a; 1. 核心特点对比 特性 Actix Web Axum 性能 极高性能&#xff0c;使用 Actor 模型优化异步任务。 性能也很好&#xff0c;基…