4

I got the following scenario: A C++ server application is listening for incomming client connections. On each client connection attempt a new session is generated. This session will call a specific service, depending on a service id, which is provided in the serialized data from the client. As soon as the result arrives from the service the session sends the data to the client.

The pitfall in the scenario is, that the service is implemented in Java.

Therefore my question is:

  1. How can I instantiate and call the Java service class using the VM reference from C++ if a new client request arrives?

I know I will need a Java VM for this. And because the C++ server class will be called from a Java Application in first place (through SWIG generated wrappers) I thought I might pass the VM reference of this application to my server class (and the sessions afterwards).

But:

  1. How can I obtain a reference to the current VM in my Java code?

Normally the Java application will be doing nothing after kicking on the server. Maybe I will have to keep it idle for keeping the VM reference alive? Is there anything special I should worry about regarding concurrent calls to the services in the C++ and Java interaction (beyond the normal concurrency handling inside the services)?

Example:

//Java Service
public class JMyService{
  public String loadContactInformation(int userid){
        return "test";
  }
}

//C++ (very simplified)
class Session{    
   public:
      //[...]
      void handleWrite(){
            vm = getVMReference(); //is saved beforehand
            if(serviceId == CONTACT_INFO){                     
                 //todo call JMyService.loadContactInformation
            }
      }
}

I already saw this question but I have to admit that the solution is hard to understand and it remains unclear what the questioner was trying to achieve. In this post the author was doing something similar with Java build in types, but it seems that the code generator can not be used for own java types. I also knew that a new VM can be generated to do the job, but I would like to use the existing one if this is possible.

EDIT

to 1) I'm not sure, but maybe the jint JNI_OnLoad(JavaVM *vm, void *reserved); Method can be used to obtain a pointer to the VM when I load the library with the C++ server class. Unfortunately the Oracle documentation does not explain this issue. Someone out there who might have experience with this?

Community
  • 1
  • 1
little_planet
  • 1,005
  • 1
  • 16
  • 35
  • Why not write the whole thing in Java and eliminate the problem? – user207421 Feb 24 '16 at 01:25
  • The C++ library with a lot of functionality does already exist. But I agree with you that it would be easier and maybe also more maintainable if everything would be Java code. – little_planet Feb 24 '16 at 07:59

1 Answers1

6

The Invocation API + JNI Functions will help.

How can I obtain a reference to the current VM in my Java code?

  1. Call JNI_GetCreatedJavaVMs to get a JavaVM* reference. If JVM has been started in the current process, this function typically returns an array of exactly one JVM reference.
  2. If current thread is not created from Java, call JavaVM->AttachCurrentThread using the reference obtained at step 1. Skip this step if current thread is already associated with a JVM.
  3. Call JavaVM->GetEnv to get JNIEnv* pointer for current thread. Using JNIEnv structure you will be able to call JNI functions, see below.

How can I instantiate and call the Java service class using the VM reference from C++

  1. Use JNIEnv->FindClass to get a jclass reference for a Java class you want to instantiate.
  2. Call JNIEnv->GetMethodID to obtain a reference to the class' constructor. The default constructor (the constructor without arguments) has "()V" signature.
  3. Call JNIEnv->NewObject passing jclass and jmethodID from steps 1 and 2 to instantiate the given class.
  4. Run JNIEnv->CallObjectMethod to execute a Java method. obj argument is an object reference obtained at step 3, and methodID argument is a method you want to call obtained by GetMethodID.

Sample code

    JavaVM* vm;
    jsize vmCount;
    if (JNI_GetCreatedJavaVMs(&vm, 1, &vmCount) != JNI_OK || vmCount == 0) {
        fprintf(stderr, "Could not get active VM\n");
        return NULL;
    }

    JNIEnv* env;
    jint result = vm->GetEnv((void**)&env, JNI_VERSION_1_6);
    if (result == JNI_EDETACHED) {
        result = vm->AttachCurrentThread((void**)&env, NULL);
    }
    if (result != JNI_OK) {
        fprintf(stderr, "Failed to get JNIEnv\n");
        return NULL;
    }

    jclass cls = env->FindClass("JMyService");
    jmethodID ctor = env->GetMethodID(cls, "<init>", "()V");
    jobject service = env->NewObject(cls, ctor);

    jmethodID loadMethod = env->GetMethodID(cls, "loadContactInformation", "(I)Ljava/lang/String;");
    jobject serviceResult = env->CallObjectMethod(service, loadMethod, userId);
    return serviceResult;

Notes

  • You may cache and reuse JavaVM*, jclass, jmethodID across the application.
  • JNIEnv* is associated with a thread. It can be reused only inside one thread.
  • JNI functions themselves are thread-safe. However if you use static variables in C++ make sure access to these variables is properly synchronized in C++ land.
apangin
  • 92,924
  • 10
  • 193
  • 247
  • Wow thank you. Didn't expected such a complete and extensive answer. I will try it out today! – little_planet Feb 24 '16 at 07:55
  • Small additional question: Will I have to release the jobject afterwards? I guess the java garbage collector wouldn't know when I'm finished with the service otherwise. How is this done? By using DeleteLocalRef of the JNIenv? – little_planet Feb 24 '16 at 12:47
  • 1
    @little_planet Correct. Once you've done with the service, call `DeleteLocalRef` to release `jobject`. This is not necessary for native methods - JVM automatically releases all local `jobject`s on return from a native method. – apangin Feb 24 '16 at 14:27