【问题标题】:Attempt to call JNI_CreateJavaVM from libart.so fails尝试从 libart.so 调用 JNI_CreateJavaVM 失败
【发布时间】:2019-10-10 21:52:26
【问题描述】:

我正在开发带有 C++ 部分的 Xamarin.Android 应用程序。现在我需要从 C++ 库中直接调用 Android Java 接口。

我从Caleb Fenton's detailed and very helpful blog post 复制了代码,它使用JNI 将从C++ 调用到Java。但是我无法像他那样获取指向 JVM 的指针。

(顺便说一句,我主要是 C# 程序员,所以我完全有可能在 C++ 中犯了一个基本错误。

在头文件中:

 #pragma once
class MyJniClass
{
    //Create this once and cache it.
    JavaVM *m_jvm;                      // Pointer to the JVM (Java Virtual Machine)
    JNIEnv *m_env;                      // Pointer to native interface
    bool init_jvm();
}

.cpp 文件中:

    #include <jni.h>
#include <dlfcn.h>
#include "MyJniClass.h"

typedef int(*JNI_CreateJavaVM_t)(void *, void *, void *);


/**Code is based on https://github.com/rednaga/native-shim/blob/master/vm.c  
*/
bool MyJniClass::init_jvm() 
{
    // https://android.googlesource.com/platform/frameworks/native/+/ce3a0a5/services/surfaceflinger/DdmConnection.cpp
    JavaVMOption opt[1];
    opt[0].optionString = "-Djava.class.path=."; // I added a small java class to the dll to which this C++ class is linked, 
                                                 //so that there would be a java class in the current directory.  

    //opt/*[1]*/.optionString = "-agentlib:jdwp=transport=dt_android_adb,suspend=n,server=y";


    JavaVMInitArgs args;
    args.version = JNI_VERSION_1_6;
    args.options = opt;
    args.nOptions = 1;
    args.ignoreUnrecognized = JNI_FALSE;

    void *libart_dso = dlopen("libart.so", RTLD_NOW); //libdvm.so is outdated,  libnativehelper.so doesn't work

    if (!libart_dso ) 
    {
        //Execution doesn't pass through here 
        return false;
    }

    //Try to get the JNI_CreateJavaVM function pointer
    JNI_CreateJavaVM_t JNI_CreateJavaVM;
    JNI_CreateJavaVM = (JNI_CreateJavaVM_t)dlsym(libart_dso, "JNI_CreateJavaVM");
    if (!JNI_CreateJavaVM) 
    {
        //Execution doesn't pass through here 
        return false;
    }

    signed int result = JNI_CreateJavaVM(&(m_jvm), &(m_env), &args);

    if ( result != 0)
    {
        ostringstream os;
        os << "Call to JNI_CreateJavaVM returned ";
        os << result;
        m_logger->writeEntry(Loglevel::debug, os.str()); // ===> Here, I can see that result is always -1
        return false;
    }

    return true;
}

我尝试在ART源代码here中找到函数JNI_CreateJavaVM,但是找不到。但它肯定应该在那里,以便 dlsym 可以找到该功能?我想我必须进一步寻找 libart.so 的源代码。

我做错了什么,我无法获得对 JNI_CreateJavaVM 的有效调用?

【问题讨论】:

  • 您为什么不直接保存传递给JNI_OnLoadJavaVM* 并继续使用它?我不明白您为什么需要尝试创建 JVM。
  • @Michael 因为它是一个 Xamarin 应用程序,所以 C++ 库永远不会从 Java 加载,JNI_OnLoad 处理程序永远不会运行。
  • 我通过另一种策略解决了这个问题,没有使用JNI_CreateJavaVM或上面的代码。

标签: android c++ xamarin.android java-native-interface dlsym


【解决方案1】:

这里的第一个修改是添加诊断选项Xcheck:jni。 这将在出现错误时提供详细信息。 通过将JavaVMOption opt[1]; 修改为JavaVMOption opt[2]; 添加另一个选项 然后,添加以下选项: opt[1].optionString = "-Xcheck:jni".

此外,dll 必须从其原始位置加载(因为涉及其他 DLL),而不是从您的项目目录中加载。 Mor详细信息在以下帖子中提供: JNI_CreateJavaVM() terminates with exit code 1

最后,您应该通过修改将指针转换为本地接口 JNIEnv:

signed int result = JNI_CreateJavaVM(&(m_jvm), &(m_env), &args);

signed int result = JNI_CreateJavaVM(&(m_jvm), (void**)&(m_env), &args);

这应该可以解决问题。

【讨论】:

  • 感谢您的回答! dll加载可能是错误的,我现在正在阅读您链接的问题(这需要我一点时间)。我确实尝试将指针强制转换为 void**,(如 CP 文章 codeproject.com/articles/993067/… 中所建议)但没有任何效果。为什么这是必要的,你知道吗?
  • 将指针转换为 void** 确实可能不是您问题的根源,但这在 JNI 规范中提出了建议,它将防止出现编译警告implicit conversion from void** to JNIEnv**
  • 另外,请注意 -Xcheck:jni 对于调试很有用,但出于性能原因必须将其删除以用于生产代码。
  • 老实说,我无法将此答案与我在问题中描述的问题联系起来。我没有从我的项目目录中加载 dll,无论如何,dlopen 都会找到 Android 库并返回一个指针。 Xcheck:jni 没有给我任何额外的 adb 日志记录。
  • 好的,很抱歉,如果它没有帮助。关于-Xcheck:jni,这不会在所有情况下都找到所有无效参数或诊断所有错误,但它可能有助于进行多次检查。对于 dll 加载,它通常是问题的根源,这就是我建议您检查的原因。您使用哪种开发环境?您是否也将 /bin/server 添加到 PATH 中,如 CP 文章中所述?
【解决方案2】:

看起来问题与提供 JNI_CreateJavaVM 的库有关。我指的是这一行

void *libart_dso = dlopen("libart.so", RTLD_NOW); 

如果我从所有与 JVM 无关的东西中剥离你的代码,并且如果我使用基于 macOS 的 JDK,它就可以正常工作。

--- 8< CUT HERE 8< ----
#include <jni.h>
#include <dlfcn.h>
#include <iostream>
#include "MyJniClass.h"

using namespace std;

bool MyJniClass::init_jvm()
{
    JavaVM *jvm;
    JNIEnv *env = NULL;
    JavaVMOption opt[1];
    opt[0].optionString = "-Djava.class.path=.";

    JavaVMInitArgs args;
    args.version = JNI_VERSION_1_6;
    args.options = opt;
    args.nOptions = 1;
    args.ignoreUnrecognized = JNI_FALSE;

    int status = JNI_CreateJavaVM (&jvm, (void **) &env, &args);

    cout << status << endl;

    return true;
}

int main(int argc, char **argv) {
  MyJniClass jni;
  jni.init_jvm();
}
--- 8< CUT HERE 8< ----
--- 8< CUT HERE 8< ----
#pragma once
class MyJniClass
{
    //Create this once and cache it.
    JavaVM *m_jvm;                      // Pointer to the JVM (Java Virtual Machine)
    JNIEnv *m_env;                      // Pointer to native interface
    public: bool init_jvm();
};
--- 8< CUT HERE 8< ----

然后,我编译代码:

g++ -o lib/recipeNo027_main c/recipeNo027_main.c \
        -I/Library/Java/JavaVirtualMachines/jdk-12.0.1.jdk/Contents/Home/include \
        -I/Library/Java/JavaVirtualMachines/jdk-12.0.1.jdk/Contents/Home/include/darwin/ \
    -rpath -L/Library/Java/JavaVirtualMachines/jdk-12.0.1.jdk/Contents/Home/lib/server -ljvm

(它基于来自这里的 recipeNo027:http://jnicookbook.owsiak.org/recipe-no-027/

我可以毫无问题地运行它

> lib/recipeNo027_main
0

您的 JNI_CreateJavaVM 实现中似乎发生了一些可疑的事情。

【讨论】:

  • 谢谢!好吧,是的,我知道 JNI_CreateJavaVM 实现内部发生了一些可疑的事情——这正是我问的问题!
猜你喜欢
  • 1970-01-01
  • 2013-07-20
  • 2017-03-27
  • 1970-01-01
  • 2022-01-24
  • 2011-11-29
  • 1970-01-01
  • 2013-08-21
  • 1970-01-01
相关资源
最近更新 更多