Search code examples
javac++java-native-interface

JNI: Does C++ calls Java asynchronous?


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;
}

Solution

  • 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.