0

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);
}
Valeriy K.
  • 2,616
  • 1
  • 30
  • 53
  • It sounds like you need to create a class which derives from SomeHandler, and when your class's handler is called, it calls the Java handler. – user253751 Jul 17 '20 at 10:09
  • Yes, I already have java interface SomeHandler and its implementation SomeHandlerImpl. I'll add java code. – Valeriy K. Jul 17 '20 at 10:15
  • Okay, but you're talking about C++ code here so obviously you need a class that `some::lib::subscribe` can use, which is a C++ class. – user253751 Jul 17 '20 at 10:18
  • As I understand from my task, it is required to call java implementation after subscription. I.e. subscribe will invoke starts and joins methods. – Valeriy K. Jul 17 '20 at 10:24
  • What is some::lib::subscribe? – user253751 Jul 17 '20 at 10:28
  • It is a function to subscribe to some events using the notification handler (SOmeHandler). some::lib is a placeholder. It is a quite real implementation but I can't share it here. some::lib::subscribe calls int subscribe(std::shared_ptr< SomeHandler > handler) function – Valeriy K. Jul 17 '20 at 10:36
  • So i need something like std::shared_ptr sharedPointer = GetSharedPointer(handlerClass); But i don't know how to write GetSharedPointer... in c++. – Valeriy K. Jul 17 '20 at 10:41

1 Answers1

1

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.
Botje
  • 26,269
  • 3
  • 31
  • 41
  • The quite simple and obvious solution. Thanks a lot! Why you instantiate *env in constructor and method? And where you get VM variable? Can I use env from Java_com_test_NativeClient_subscribe in JavaWrapperHandler constructor and methods? – Valeriy K. Jul 20 '20 at 10:52
  • No, you cannot share `JNIEnv* env` across threads, that is why I retrieve it every time for the current thread. It was unclear from your explanation, so I assumed your "somelib" uses a separate thread to invoke callbacks, which is different from the subscribing thread. Retrieving the `env` associated with the current thread solves that. As I said, you may need to attach it the thread the first time a callback fires. If you did not create the VM yourself you can use [`env->GetJavaVM`](https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#wp23168) – Botje Jul 20 '20 at 11:05
  • I get error - " 'GetEnv' was not declared in this scope". And also -" note: suggested alternative: ‘setenv’" How I can fix it? – Valeriy K. Jul 22 '20 at 08:22
  • It should be part of the jni header. Did you not include it? – Botje Jul 22 '20 at 08:44
  • Did you mean #include ? I tried and got the same. – Valeriy K. Jul 22 '20 at 08:53
  • Sorry, I was too quick. `GetEnv` is a method of the VM object. So you need to do `vm->GetEnv(...)`. Updated answer. – Botje Jul 22 '20 at 09:03
  • Yes, it works. I wrote working code here - https://stackoverflow.com/a/63032634/7804595 – Valeriy K. Jul 22 '20 at 10:59