简体   繁体   中英

Java callback to C++ callback from C++

There are countless of articles and questions about how to call Java code from C++ using JNI, and I can do that, I can call some Java function from C++.

Now what I cannot find any information on is the following:

Assume I have a Java function which requires a callback function passed to it. This callback function is called at some later point in time from a different thread.

Now I want to call this function from a C++ program, and once the callback function is called, I want a C++ callback to be called. Can anybody point me to a source with information on how to do that?

Background is I want to use a Java library inside an existing C++ project (all on Linux, though I doubt this is relevant). The overhead for calling Java functions through JNI is not an issue here.

You are right, somehow the documentation for this is not found easily. But I still remember from a former project the way I did this. You will have to do your part by reading some freely available online documentation because I might miss some details. I will give you the links at the end of this post.

So if I understand you correct, you want to call a native C++ function from Java. First of all remember the Java Native Interface is not C++ but C. This is like most native interfaces of higher level programming languages (all I have ever seen so far).

  1. Create your Java view of the native interface. That is create a Java class and declare native methods. There is the keyword native you can use for that. You do not provide any implementation just declare it.

  2. Use javac -h to generate native header files. Read the docs of that tool. In Java 7 there was a seperate tool for that called javah . But with current Java 11 you should use javac .

  3. Use C or C++ to provide an implementation for the functions declared in the generated headers. Compile and link them to a shared object ( *.so or *.dll ).

  4. At runtime of your java application load your native code from your new library by calling:

    System.load("path-to-lib");

You do not have to do that last step 4 if your native function is already loaded in the current process. That would be if you are embedding a Java app in a CPP application. In that case you might want to look at RegisterNatives .

The documentation for Java keyword native :

The documentation of JNI is here:

Also look at the documentation of the Java Compiler for how to generate native headers. Look for the option -h :

.

Edit

Somehow I understand your question today better than yesterday:

  • You have a C++ application that embeds a Java app.
  • From C++ you want to invoke a Java method.
  • When invoking you want to pass a callback method.
  • When the Java method is done it must call that callback method you passed it before.

Ok, together with what you already know and plus the explanation I gave above this can be done. It actually can be done again in different ways. I will explain a simple one:

Your C++ app already knows which callback needs to be called when the Java method is finished. When you call the Java method you give it the callback as a key . You already know how to invoke a Java method from C++. This key can be anything. To keep it simple the key is a uintptr_t , an integer of the size of your pointers. In that case we just pass the function pointer as a callback to the Java method.

But Java cannot invoke the callback by dereferencing that integer/pointer thing. Now you invoke a native extern "C" function and give it that key as parameter. I explained above how to invoke a native function from Java. That native function now just needs to cast that integer back to a pointer: ( reinterpret_cast<>() ) and invoke your callback. Of course the native function can accept additional parameters than the key to the callback, if there is some data you want to pass to your callback.

I think the idea is now very clear.

If you want to have more portable code then do not use the address of the callback as key. Rather use an integer or even string and use a std::map to map that key to your real callback. But just start with that simple example. And when that is working it is easy to improve it. Most work will be setting up the project and tools to work together.

Alright, here for future readers how I managed to do it. There's a few points which don't seem perfectly clean to me, if anybody has an idea on how to do it more cleanly, I'll be very interested in this.

So, I wrote a simple Java class Bar in package foo, which is to be called from C++, passing a reference to a function (more on that below) and calling the function with some hardcoded parameters.

package foo;
import foo.Functor;

//this is just what we want to call from C++
//for demonstration, expect return type int
public class Bar
{
    public static void run(long addr) {
        Functor F = new Functor(addr);

        //synchronously here, just to prove the concept
        F.run(1,2);
    }
}

As you see, I also wrote a class Functor, which also straightforward

package foo;

//we need to write this for every signature of a callback function
//we'll do this as a void foo(int,int), just to demonstrate
//if someone knows how to write this in a general (yet JNI-compatible) way,
//keeping in mind what we are doing in the non-Java part, feel free to tell me
public class Functor
{
    static {
        System.loadLibrary("functors");
    }
    public native void runFunctor(long addr,int a,int b);

    long address;

    public Functor(long addr)
    {
    address = addr;
    }

    public void run(int a, int b) {
        runFunctor(address,a,b);
    }
}

This depends on a shared library I called functors. Implemented very straightforward. The idea is to keep the actual logic separate, and just provide the interface in the shared object. The main drawback, as mentioned before, is that I have to write it for every signature, I see no way to template this.

Just for completeness, here's the implementation of the shared object:

#include <functional>
#include "include/foo_Functor.h"

JNIEXPORT void JNICALL Java_foo_Functor_runFunctor
  (JNIEnv *env, jobject obj, jlong address, jint a, jint b)
{
    //make sure long is the right size
    static_assert(sizeof(jlong)==sizeof(std::function<void(int,int)>*),"Pointer size doesn't match");

    //this is ugly, if someone has a better idea...
    (*reinterpret_cast<std::function<void(int,int)>*>(address))(static_cast<int>(a),static_cast<int>(b));
}

And finally, here's how I call it in C++, defining the callback function during runtime, outside the shared object:

#include <iostream>
#include <string>
#include <jni.h>
#include <functional>

int main()
{
    //this is from some tutorial, nothing special
    JavaVM *jvm;
    JNIEnv *env;
    JavaVMInitArgs vm_args;
   JavaVMOption* options = new JavaVMOption[1];  
    options[0].optionString = "-Djava.class.path=."; //this is actually annoying, JNI has this as char* without const, resulting in a warning since this is illegal in C++ (from C++11)
    vm_args.version = JNI_VERSION_1_6;   
    vm_args.nOptions = 1;      
    vm_args.options = options;
    vm_args.ignoreUnrecognized = false;  

    jint rc = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
    delete[] options; 

    if (rc != JNI_OK)
        return EXIT_FAILURE;


    jclass cls = env->FindClass("foo/Bar");
    jmethodID mid = env->GetStaticMethodID(cls, "run", "(J)V");

    //the main drawback of this approach is that this std::function object must never go out of scope as long as the callback could be fired
    std::function<void(int,int)> F([](int a, int b){std::cout << a+b << std::endl;});

    //this is a brutal cast, is there any better option?
    long address = reinterpret_cast<long>(&F);
    env->CallStaticVoidMethod(cls,mid,static_cast<jlong>(address));


    if (env->ExceptionOccurred())
        env->ExceptionDescribe();
    jvm->DestroyJavaVM();
    return EXIT_SUCCESS;
}

This works perfectly fine, and I can work with this. Though, a few things are still bothering me:

  1. I have to write the interface (both a Java class and the corresponding C++ implementation) for each signature of functions I want to pass. Can this be done in a more general way? My feeling is Java (and especially JNI) isn't flexible enough for this.
  2. The reinterpret_cast s for converting the pointer to std::function to an integer and back are not something I like doing. But it was the best way I could think of to pass a reference to a function (which possibly exists only during run time) to Java...
  3. In the initialization of the Java VM in C++, I'm setting an option which in the JVM interface is defined as char * (there should be a const here). This looks like a very innocent line, but gives a compiler warning, since it is illegal in C++ (it is legal in C, that's why JNI developers likely didn't care). I found no elegant way around this. I know ways how to make it legal, but I really don't want to write several lines of code just for this (or throw around const_cast s), so I decided, for this, just to live with the warning.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM