获得有效的JNIEnv指针

16

我有一个C++动态链接库,我想通过将其导出到C#中的函数来在Unity中使用。Unity项目运行在安卓设备上,而C++代码则利用Java。为了初始化C++,我需要首先调用以下函数:

void api_initialize(JNIEnv* env, jobject* app_context, jobject* class_loader) {

    JavaVM* vm = nullptr;
    env->GetJavaVM(&vm);
    if (!vm) {
      return;
    }

    //Do other proprietary things
}

在Unity中,我有以下导出的Dll函数

    [DllImport (dllName)]
    private static extern void api_initialize (IntPtr java_env, IntPtr app_context, IntPtr class_loader);

我的问题是如何在我的C#类中获取一个JNIEnv指针,并将其作为参数传递给这个函数?

我不是这个API的创建者,也没有修改它的权限,所以我需要从JNIEnv获取JavaVM,而不是相反。


我相信你可以在这里找到答案-> https://dev59.com/cmEh5IYBdhLWcg3w12il 或者使用Oracle文档创建自定义的Java调用 -> situationhttp://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/design.html#wp715无论如何,这是一个非常好的问题。 - Cabrra
你能用 JNI_GetCreatedJavaVMs 来做这件事吗? - fuzzyTew
3个回答

5

我猜你可能无法做到这一点(可能有,但我还没看到),因为这种类型的 NDK 调用 需要 Java 包装,所以我想提出另一个解决方案,使用 Java 作为 C# 调用的 中间层,然后你可以通过 重定向 这个调用到 NDK 代码

using UnityEngine;
using System.Collections;
using System.IO;

#if UNITY_ANDROID
public class JavaCallPlugin {

  // Avoid invalid calls
  public static void initiateNDK() {
    if (Application.platform != RuntimePlatform.Android)
          return;

    var pluginClass = 
        new AndroidJavaClass("com.package.class.UnityJavaDelegate");
    AndroidJavaObject plugin = 
        pluginClass.CallStatic<AndroidJavaObject>("obj");
    return plugin.Call("javaCallToNDK");
  } 
}
#endif

在您的Java文件中,执行以下操作:

package com.package.class;

import android.content.ContentValues;
import android.content.Intent;
import android.os.Environment;

public class UnityJavaDelegate{
  private static UnityJavaDelegate obj;

  // define function call to your NDK method with native keyword
  private native void api_initialize(); 

  static {
    System.loadLibrary("yourlibraryname"); // setup your ndk lib to use 
  }

  public static UnityJavaDelegate getObj() {
    if(m_instance == null)
       obj= new UnityJavaDelegate();
    return obj;
  }

  private UnityJavaDelegate(){
  }

  public void javaCallToNDK(){
    // this call may not work because i haven't passed class 
    // loader TO NDK before, if not then you should try links 
    // at the bottom of the post to know how to pass customize 
    // as parameter to NDK.

    // put your call to NDK function here 
    api_initialize(this.getClass().getClassLoader()); 
  }
}

当您声明本地方法时,javah会自动生成一个带有javaEnv和jobject作为参数的ndk调用定义,因此我猜您只需要在这里传递类加载器。您可以尝试使用此链接此链接以获得更多澄清信息。
祝你好运。希望这有所帮助。

1
这很有帮助,而且有效!我通过实现进行了确认。 - Maestro

4

我认为你可以创建另一个本地插件,从而获得JNIEnv。

Android的插件

总结本文,你应该尝试类似以下未经测试的伪代码:

JavaVM* g_JVM; // JavaVM is valid for all threads, so just save it globally 

extern "C" {
    JNIEnv* GetJniEnv();
}

// The VM calls JNI_OnLoad when the native library is loaded
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    g_JVM = vm;
    return JNI_VERSION_1_6;
}

// The JNI interface pointer (JNIEnv) is valid only in the current thread.
JNIEnv* GetJniEnv() {
    JNIEnv* jni_env = 0;
    g_JVM->AttachCurrentThread(&jni_env, 0);
    return jni_env;
}

然后在 C# 中。
[DllImport ("PluginName")]
private static extern IntPtr GetJniEnv();

1
我认为只有当Java模块显式地请求加载库(System.loadLibrary)时,才会调用JNI_OnLoad。 - Maestro
在这里使用GetJniEnv作为函数名是令人困惑的,因为在C++世界中需要指针。然而,我刚刚从Unity3d C#脚本中测试了这个解决方案[DllImport("mynative")] private static extern float helloWorld();,并且它在Unity 2021.1.25f中完美地工作。 - Crog

3

继续Vyacheslav Gerasimov的回答,此代码已经过测试,可以直接使用而无需进行额外的工作:

JNIEnv* jni_env = NULL;
JavaVM* JVM;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JVM = vm;
    if (vm->GetEnv((void**)&jni_env, JNI_VERSION_1_6) != JNI_OK)
        __android_log_print(ANDROID_LOG_ERROR, "Unity", "ERROR: GetEnv failed")

    return JNI_VERSION_1_6;
}

JNI_OnLoad会被Unity/Java自动调用。

不需要C#,因为在C++中可以传递jni_env或JVM变量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接