2

I'm trying to call some java classes from c++ code by using the JNI. Today I experienced a very strange behaviour in my programm. I suspect that the c++ code is not wait until the java side finished it's work and I don't know why.

The C++ code (in a shared object library) is running in it's own C++ thread. It is using a existing JavaVM of a java app that is already up and running. The references to the VM and the ClassLoader where fetched while the java application was loading the shared object library in JNI_Onload. Here I call the java method of a java object I created with JNI in the C++ thread:

env->CallVoidMethod(javaClassObject, javaReceiveMethod, intParam, byteParam, objectParam);
uint32_t applicationSideResponseCode = getResponseCode(objectClass, objectParam, env);
resp.setResponseCode(applicationSideResponseCode);
std::string applicationData = getApplicationData(serviceResultClass, serviceResultObject, env);
resp.setData(applicationData);

The Java javaReceiveMethod is accessing the database and fetching some applicationData which is stored in the objectParam. Unfortunately the C++ code fetched the applicationData before the java class completed it's work. applicationData is null and the JNI crashes. Why? I can't find any documentation of Oracle stating that CallVoidMethod is executed asynchronous?

Edit

I verified that no exception has occured on the java method. Everything seems fine, except that Java is still busy while C++ is trying to access the data.

Edit

I can confirm that if I debug the java application two threads are shown. On main thread, that is executing javaReceiveMethod and one thread that is fetching the applicationData. How can I solve this problem? Idle in the second thread until the data is available?

Edit

In my C++ code I'm creating a new object of the java class that I want to call:

jmethodID javaClassConstructor= env->GetMethodID(javaClass, "<init>", "()V");
                    jobject serviceObject = env->NewObject(javaClass, serviceConstructor);
jobject javaClassObject = env->NewObject(javaClass, javaClassConstructor);

After that I call the code as shown above. After more debugging I can say that the method is called in a thread named Thread-2 (I don't know if that is the c++ thread or a new one from JNI). It is definitely not the java main thread. However the work of the method is interrupted. That means If I debug the code, I can see that the data would be set soon but in the next debug step the getApplicationData method is executed (which can only occur if c++ is calling it).

Edit The Java method I call:

public int receive(int methodId, byte[] data, ServiceResult result){
        log.info("java enter methodID = " + methodId + ", data= " + data);  

        long responseCode = SEC_ERR_CODE_SUCCESS;

        JavaMessageProto msg;
        try {
            msg = JavaMessageProto (data);
            log.info("principal: " + msg.getPrincipal());       
            JavaMessage message = new JavaMessage (msg);

            if(methodId == GET_LISTS){
                //this is shown in console
                System.out.println("get lists");                
                responseCode = getLists(message);
                //this point is not reached 
                log.info("leave");  
            }           
            //[... different method calls here...]
            if(responseCode != SEC_ERR_CODE_METHOD_NOT_IMPLEMENTED){
                //ToDoListMessageProto response = message.getProtoBuf();
                JavaMessageProto response = JavaMessageProto.newBuilder()   
                        .setToken(message.getToken())
                        .setPrincipal(message.getPrincipal()).build();
                byte[] res = response.toByteArray();                
                result.setApplicationData(response.toByteArray());
            }
            else{
                result.setApplicationData("");
            }

        } catch (InvalidProtocolBufferException e) {
            responseCode = SEC_ERR_CODE_DATA_CORRUPTED;
            log.severe("Error: Could not parse Client message." + e.getMessage());
        }               

        result.setResponseCode((int)responseCode);
        return 0;
    }

The second method is

public long getLists(JavaMessage message) {
log.info("getLists enter");

String principal = message.getPrincipal();
String token = message.getToken();  

if(principal == null || principal.isEmpty()){           
    return SEC_ERR_CODE_PRINCIPAL_EMPTY;
}       
if(token == null || token.isEmpty()){       
    return SEC_ERR_CODE_NO_AUTHENTICATION;
}       

//get user object for authorization
SubjectManager manager = new SubjectManager();
Subject user = manager.getSubject();

user.setPrincipal(principal);
long result = user.isAuthenticated(token);
if(result != SEC_ERR_CODE_SUCCESS){
    return result;
}   
try {
    //fetch all user list names and ids         
    ToDoListDAO db = new ToDoListDAO();     
    Connection conn = db.getConnection();       
    log.info( principal + " is authenticated");
    result = db.getLists(conn, message);
    //this is printed
    log.info( principal + " is authenticated");         
    conn.close();   //no exception here
    message.addId("testentry");
    //this not          
    log.info("Fetched lists finished for " + principal);
} catch (SQLException e) {  
    log.severe("SQLException:" + e.getMessage());
    result = SEC_ERR_CODE_DATABASE_ERROR;
}           

return result;
}
little_planet
  • 1,005
  • 1
  • 16
  • 35
  • Could you clarify the description of your problem? You first say that "javaReceiveMethod" is executed on the C++ thread and then you say it's executed on the main thread? Btw, the method calls are executed synchronously. – Stefan Zobel Mar 25 '16 at 12:10
  • I will update my answer and try to describe it as clear as possble. But at the moment I'm very confused what exactly JNI is doing there. – little_planet Mar 25 '16 at 12:11
  • You haven't shown us the Java method that you're calling. – Michael Mar 25 '16 at 12:21
  • JNI itself doesn't create a new thread, so Thread-2 should be your C++ thread. You can set the thread name in the JavaVMAttachArgs arg to AttachCurrentThread() btw. – Stefan Zobel Mar 25 '16 at 12:32
  • Do you really call result.setApplicationData() in the methodId == GET_LISTS case? – Stefan Zobel Mar 25 '16 at 12:51
  • Yes I do. The problem was caused by a NullPointerException, as descripted in my response to marcinj answer. Anyway I will add flag to the object class which handles the result to indicate if any application data is contained in the result. If the flag is false I will just skip the call to getApplicationData to prevent any further problems. – little_planet Mar 25 '16 at 12:57
  • 1
    As a general advice for JNI coding: it really pays off in the long run to have a wrapper for all JNI functions that does all the checks for you (return codes, ExceptionOccurred() and so on) and throws a C++ exception if something went wrong. We are using such a wrapper since 2003 and it saved our butts more than once. – Stefan Zobel Mar 25 '16 at 13:21

1 Answers1

4

CallVoidMethod is executed synchronously.

Maybe you have an excepion on c++ side?, do you use c++ jni exception checks?:

env->CallVoidMethod(javaClassObject, javaReceiveMethod, intParam, byteParam, objectParam);
if(env->ExceptionOccurred()) {
   // Print exception caused by CallVoidMethod
   env->ExceptionDescribe();
   env->ExceptionClear();
}

The C++ code (in a shared object library) is running in it's own C++ thread. It is using a existing JavaVM of a java app that is already up and running.

its not clear whether you have attached current thread to virtual machine. Make sure env is comming from AttachCurrentThread call. You will find example here: How to obtain JNI interface pointer (JNIEnv *) for asynchronous calls.

Community
  • 1
  • 1
marcinj
  • 48,511
  • 9
  • 79
  • 100
  • the thread is attached. I call vm->AttachCurrentThread((void**)&env, NULL); – little_planet Mar 25 '16 at 12:22
  • It was indeed a exception that was thrown in my Java code but not shown on Java side. The `addId` Method caused a `NullPointerException` and in my naive believe that Java would show such a exception in my development environment I couldn't believe this strange behaviour would be caused by such a stupid bug. Sorry for any inconvenience. Thank you very much for pointing me to `env->ExceptionOccured`. I'm still new to JNI and didn't know that I can catch exceptions in this way. – little_planet Mar 25 '16 at 12:49