Unreal开发中使用JNI调用语法

使用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;

    // 获取JNIEnv
    if (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()) {
        // 因为函数没有返回值,所以函数签名最后是V
        jmethodID UninitSDKID = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "uinit",  "()V", false);
        if (UninitSDKID != nullptr) {
            UE_LOG(LogTemp, Warning, TEXT("Success to find method uninit"));
            // 对于没有返回值的Java方法,调用的是CallVoidMethod
            FJavaWrapper::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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值