Search code examples
javac++java-native-interfaceshared-ptrsmart-pointers

How to instantiate shared pointer with JNI to call java implementation


I'm new in JNI and C++. I have to call lib function with shared pointer. My code:

JNIEXPORT jint JNICALL Java_com_test_NativeClient_subscribe(JNIEnv* env, jobject thisObj, jobject handler) {
jclass handlerClass = env->GetObjectClass(handler);
jmethodID starts = env->GetMethodID(handlerClass, "starts", "(I)V");
jmethodID joins = env->GetMethodID(handlerClass, "joins", "(Ljava/lang/String;Ljava/lang/String;)V");

// int subscribe(std::shared_ptr< SomeHandler > handler) // I need implement this
 
 std::shared_ptr<?> sharedPointer = new std::shared_ptr<?>;
 
return some::lib::subscribe(sharedPointer);
}

SomeHandler it is an interface from lib - some::lib::SomeHamdler, but also I pass java implementation in the method (jobject handler). How I can properly define sharedPointer to call java implementation after subscribe method performed? Thanks in advance.

UPD: Java code:

public native int subscribe(SomeHandler handler); // native method in NativeClient

SomeHandler interface:

public interface SomeHandler {

void starts(int uptime);

void joins(String mac, String name);

SomeHandlerImpl class:

public class SomeHandlerImpl implements SomeHandler {

@Override
public void starts(int uptime) {
    System.out.println("uptime is " + uptime);
}

@Override
public void joins(String mac, String name) {
    System.out.println("mac: " + mac + ", nName: " + name);
}

Solution

  • All you need to do is store a global reference to the jobject and write some wrapper code:

    class JavaWrapperHandler : public some::lib::callback {
        jobject java_handler;
    
    public:
        JavaWrapperHandler(jobject handler) {
            JNIEnv *env = nullptr;
            vm->GetEnv(&env, JNI_VERSION_1_6);
            java_handler = env->NewGlobalRef(handler);
        }
    
        ~JavaWrapperHandler() {
            JNIEnv *env = nullptr;
            vm->GetEnv(&env, JNI_VERSION_1_6);
            env->DeleteGlobalRef(java_handler);
        }
    
        virtual joins(std::string mac, std::string name) {
            JNIEnv *env = nullptr;
            vm->GetEnv(&env, JNI_VERSION_1_6);
            jclass handlerClass = env->GetObjectClass(java_handler);
            jmethodID joins = env->GetMethodID(handlerClass, "joins", "(Ljava/lang/String;Ljava/lang/String;)V");
            env->CallVoidMethod(java_handler, joins, ...);
        };
    };
    

    And you can instantiate this as follows in your JNI method:

    std::make_shared<JavaWrapperHandler>(handler);
    

    Note that you still need to store the shared_ptr again somewhere, otherwise it will immediately be freed. You could for example store it in a std::map<long, shared_ptr<JavaWrapperHandler>> and return the long as a jlong.

    Points of note:

    • This code keeps a global reference to prevent the Java handler object from being garbage collected.
    • The global reference is freed when the handler is destroyed. Make sure to unregister the callback at some point if you want to free the Java object.
    • We use the GetEnv method from the JNI Invocation API. It will only produce a useful value if the current (C++) thread has already been attached to the JVM. If it fails, you need to call vm->AttachCurrentThread or vm->AttachCurrentThreadAsDaemon.