如何在C++中使用JNI_GetCreatedJavaVMs调用Java代码。

huangapple go评论73阅读模式
英文:

How to use JNI_GetCreatedJavaVMs in C++ to call Java Code

问题

我有一个包含Java和C++代码的Android应用程序。我有的C++代码被捆绑到不同的.so文件中,可以从Java或其他.so文件中调用。从一些.so文件中,我需要调用一些Java代码。

要调用Java代码,我看到我们需要JNIEnv。这可以在加载库时调用JNI_OnLoad方法时获得,或者当Java调用C++代码时,JNIEnv会在方法参数中传递。要调用Java代码,推荐的方法似乎是保存JNIEnv,并在以后调用Java代码时使用它。

但在我的情况下,我希望保持我的.so文件通用,以便它们可以在不同的操作系统上使用。所以我不希望JNIEnv在多个依赖项之间传递。我想要的是根据需要获取JNIEnv

我看到可以通过使用JNI_GetCreatedJavaVMs方法获取JVM,然后使用JVM实例来获取JNIEnv。问题是这个方法似乎没有在jni.h中定义,只提供了声明,类似于这样:

    /*
     * VM initialization functions.
     *
     * Note these are the only symbols exported for JNI by the VM.
     */
    jint JNI_GetDefaultJavaVMInitArgs(void*);
    jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*);
    jint JNI_GetCreatedJavaVMs(JavaVM**, jsize, jsize*);

我理解这些应该从其他库加载,但找不到任何关于需要导入哪些库、在C++文件中需要导入哪些头文件/源文件以及如何使用该方法的文档。有人能帮助解决这个问题吗?

我正在使用NDK的r18b版本来构建我的代码,当我尝试使用JNI_GetCreatedJavaVMs时,它抛出以下错误:

  [x86_64] SharedLibrary  : libJniPoc.so
  /workplace/alias/workspace/src/JniPoc/src/main/cpp/core/InfoProvider.cpp:16: error: undefined reference to 'JNI_GetCreatedJavaVMs'
  clang++: error: linker command failed with exit code 1 (use -v to see invocation)
  make: *** [/workplace/alias/workspace/build/JniPoc/JniPoc-1.0/AL2_x86_64/DEV.STD.PTHREAD/build/intermediates/ndkBuild/debug/obj/local/x86_64/libJniPoc.so] Error 1

那么我需要在编译时做什么工作,以及在运行时使其正常工作需要做什么?

英文:

I have an Android application which contains the Java and C++ code. The C++ code I have is bundled into different .so files and can be invoked either from Java or other .so files. From some of the .so files, I need to call some Java code.

To call Java code, I see that we need JNIEnv. This can be received either when JNI_OnLoad method is called when library is loaded or when Java is calling C++ code, the JNIEnv is passed in method arguments. To call Java code, the recommended approach seems to be to save JNIEnv and use it later when calling Java code.

But in my case, I would like to keep my .so files generic so that they can be used across different OSs. So I don't want JNIEnv to be passed across multiple dependencies. What I am looking for is to get JNIEnv on demand.

I see that I can do this by getting JVM using JNI_GetCreatedJavaVMs method and then use the JVM instance to get the JNIEnv. The problem is this method doesn't seem to be defined in jni.h but only declaration was provided something like this:

    /*
     * VM initialization functions.
     *
     * Note these are the only symbols exported for JNI by the VM.
     */
    jint JNI_GetDefaultJavaVMInitArgs(void*);
    jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*);
    jint JNI_GetCreatedJavaVMs(JavaVM**, jsize, jsize*);

I understand that these should be loaded from other libraries but couldn't find any documentation on what libraries I need to import, what header/sources files I need to import in my C++ files and how I can use that method. Can anyone help with this?

I found this GitHub issue which says that these methods are not exported earlier and now exported only from API level 31. But it's not clear to me on how these exported methods should be accessed.

I am using r18b of NDK to build my code and when I try to use JNI_GetCreatedJavaVMs, it throws the following error:

  [x86_64] SharedLibrary  : libJniPoc.so
  /workplace/alias/workspace/src/JniPoc/src/main/cpp/core/InfoProvider.cpp:16: error: undefined reference to 'JNI_GetCreatedJavaVMs'
  clang++: error: linker command failed with exit code 1 (use -v to see invocation)
  make: *** [/workplace/alias/workspace/build/JniPoc/JniPoc-1.0/AL2_x86_64/DEV.STD.PTHREAD/build/intermediates/ndkBuild/debug/obj/local/x86_64/libJniPoc.so] Error 1

So what do I need to do compilation work and what do I need to make it work during runtime?

答案1

得分: 1

如果您的目标API级别为31或更高,那么该功能已经存在,因此您无需采取任何措施。根据apilevels.com,如果您想在应用商店上发布您的应用程序,无论如何都需要将目标版本设置为31。

您唯一需要做的事情是链接libnativehelper,可以通过以下方式完成:

target_link_libraries(JniPoc PUBLIC -lnativehelper)

如果由于某种原因这不是一个选项,您可以创建自己的定义。正如您所注意到的,JNI_OnLoad 函数接收一个 JavaVM 参数,因此您可以将其安全地存储在某个位置:

static JavaVM *g_vm = nullptr;
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
  g_vm = vm;
  // 其他代码
}

JNIEXPORT jint JNICALL
JNI_GetCreatedJavaVMs(JavaVM** vmBuf, jsize bufLen, jsize* nVMs) {
  if (bufLen < 1) {
    return JNI_ERR;
  }

  if (!g_vm) {
    *nVMs = 0;
  } else {
    *nVMs = 1;
    vmBuf[0] = g_vm;
  }
  return JNI_OK;
}

请注意,这仅是部分解决方案,我不确定它在31及更高版本的设备上会产生什么作用。您可以尝试在定义中添加__attribute__((weak)),但这只是一个猜测。

英文:

If you target API level 31 or up, the function is there so you do not need to do anything. According to apilevels.com you need to target version 31 anyway if you want to ship your app on the app store.
The only thing you need to do is link with libnativehelper, which you can do by:

target_link_libraries(JniPoc PUBLIC -lnativehelper)

If that is not an option for whatever reason, you can create your own definition. As you noted, the JNI_Onload function receives a JavaVM argument, so you can stash it somewhere safe:

static JavaVM *g_vm = nullptr;
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
  g_vm = vm;
  // other code
}

JNIEXPORT jint JNICALL
JNI_GetCreatedJavaVMs(JavaVM** vmBuf, jsize bufLen, jsize* nVMs) {
  if (bufLen &lt; 1) {
    return JNI_ERR;
  }

  if (!g_vm) {
    *nVMs = 0;
  } else {
    *nVMs = 1;
    vmBuf[0] = g_vm;
  }
  return JNI_OK;
}

Note that this is only a partial solution, I am not sure what it will do on devices that are 31 and up. You could try adding __attribute__((weak)) to the definition but that is only a guess.

huangapple
  • 本文由 发表于 2023年7月13日 15:51:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76677079.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定