1

I am working on a Xamarin.Android app with a C++ part. Now I need to call directly into Android Java interfaces from the C++ library.

I copied the code from Caleb Fenton's detailed and very helpful blog post which uses the JNI to call from C++ to Java. But I can't get the pointer to the JVM in the same way that he does it.

(By the way, I am mostly a C# programmer, so it's entirely possible that I've made an elementary mistake in C++).

In the header file:

 #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();
}

In the .cpp file:

    #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;
}

I tried to find the function JNI_CreateJavaVM in the ART source code here, but I couldn't find it. But surely it should be there, so that dlsym can find the function? I think I have to look further to find the source code for libart.so.

What am I doing wrong, that I can't get a valid call to JNI_CreateJavaVM?

user1725145
  • 3,993
  • 2
  • 37
  • 58
  • Why don't you just save the `JavaVM*` that gets passed to `JNI_OnLoad` and keep using that? I don't see why you would need to try to create a JVM. – Michael May 27 '19 at 05:51
  • @Michael Because it is a Xamarin app, so the C++ libraries never get loaded from Java, and the JNI_OnLoad handler never runs. – user1725145 May 27 '19 at 09:07
  • I solved the problem by another strategy, not using JNI_CreateJavaVM or the above code. – user1725145 Jun 12 '19 at 07:12
  • Good for you. How about sharing the working solution with the rest of us? – Max Kielland Aug 06 '22 at 15:51
  • @MaxKielland Ahh, "Give me codez" never loses its charm... – user1725145 Aug 24 '22 at 05:57
  • @user1725145 That wasn't what I said. I share my solutions, even if I figured it out myself to help others in the same situation. To share and help each other, isn't that what Stack Overflow is all about ;) – Max Kielland Aug 25 '22 at 16:43
  • Think about what you said. Giving hours of time for nothing to SO worked well for Monica, didn't it....I'm more than happy to share complex solutions; I am a freelancer, and my rate is in euros. – user1725145 Aug 26 '22 at 09:04

2 Answers2

0

the first modification here would be to add the diagnosis option Xcheck:jni. This will provide details in case of errors. Add another option by modifying JavaVMOption opt[1]; to JavaVMOption opt[2]; and then, add the following option: opt[1].optionString = "-Xcheck:jni".

Also, the dll must be loaded from it's original location (as other DLLs are involved) and not from your project directory. Mor details are provided in the following post: JNI_CreateJavaVM() terminates with exit code 1

Finally, you should cast the pointer to native interface JNIEnv by modifying:

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

to

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

This should solve the problem.

T1B0
  • 11
  • 3
  • Thank you for your answer! The dll loading is probably wrong, I am now reading the question you linked (it will take me a little time). I did try casting the pointer to void**, (as advised in CP article https://www.codeproject.com/articles/993067/calling-java-from-cplusplus-with-jni ) with no effect. Why is this necessary, do you know? – user1725145 May 24 '19 at 11:34
  • 1
    Casting the pointer to void** is indeed probably not the source of your problem, but this advised in JNI specification and it will prevent to get compilation warning `implicit conversion from void** to JNIEnv**`. – T1B0 May 24 '19 at 13:25
  • Also, note that -`Xcheck:jni` is useful for debug but has to be removed for production code for performance reason. – T1B0 May 24 '19 at 13:27
  • I will be honest, I cannot relate this answer to the problem that I described in my question. I am not loading the dll from my project directory, and in any case, dlopen finds the Android library and returns a pointer. Xcheck:jni doesn't give me any extra adb logging. – user1725145 May 26 '19 at 06:26
  • 1
    Ok, sorry if it did not help. Regarding `-Xcheck:jni`, this will not find in all cases all invalid arguments or diagnose all bugs, but it can be helpful for several checks. For dll loading, it is often the source of issues which is why I advised you to check. Which development Environment do you use? Did you add also /bin/server to the PATH as mentionned in CP article? – T1B0 May 28 '19 at 12:06
  • I'm using Visual Studio, and I have added \bin\Server to my computer's PATH variable. But actually this is the path of jvm.dll, which is used for the jni functions in the CP article. I think I'm not getting far enough to reference jvm.dll. Instead, I want to check that libart.so does contain JNI_CreateJavaVM, but I can't find either this library, or a way to see the symbol table of a .so file in Windows. – user1725145 May 28 '19 at 13:31
0

It looks like issue is related to library that provides JNI_CreateJavaVM. I am referring to this line

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

If I strip your code from all the stuff that is not related to JVM, and if I use macOS based JDK it works just fine.

--- 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< ----

and then, I compile the code:

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

(it is based on recipeNo027 from here: http://jnicookbook.owsiak.org/recipe-no-027/)

I can run it without any issues

> lib/recipeNo027_main
0

It looks like something fishy is going on inside your JNI_CreateJavaVM implementation.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Oo.oO
  • 12,464
  • 3
  • 23
  • 45
  • Thank you! Well yes, I know something fishy is going on inside the JNI_CreateJavaVM implementation - that is exactly the question that I asked! – user1725145 May 26 '19 at 06:30