【问题标题】:JNI: Does C++ calls Java asynchronous?JNI:C++ 是否异步调用 Java?
【发布时间】:2016-03-25 11:38:59
【问题描述】:

我正在尝试使用 JNI 从 c++ 代码中调用一些 java 类。今天我在我的程序中遇到了一个非常奇怪的行为。我怀疑c++代码没有等到java端完成它的工作,我不知道为什么。

C++ 代码(在共享对象库中)在它自己的 C++ 线程中运行。它正在使用已经启动并运行的 Java 应用程序的现有 JavaVM。在 Java 应用程序在 JNI_Onload 中加载共享对象库时获取的对 VM 和 ClassLoader 的引用。这里我调用了我在C++线程中用JNI创建的一个java对象的java方法:

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

Java javaReceiveMethod 正在访问数据库并获取一些存储在objectParam 中的applicationData。不幸的是,C++ 代码在 java 类完成它的工作之前获取了 applicationData。 applicationData 是null,JNI 崩溃。为什么?我找不到任何说明 CallVoidMethod 是异步执行的 Oracle 文档?

编辑

我确认java方法没有发生异常。一切似乎都很好,只是在 C++ 尝试访问数据时 Java 仍然很忙。

编辑

我可以确认,如果我调试 java 应用程序,会显示两个线程。在主线程上,正在执行javaReceiveMethod 和一个正在获取applicationData 的线程。我怎么解决这个问题?在第二个线程空闲直到数据可用?

编辑

在我的 C++ 代码中,我正在创建一个要调用的 java 类的新对象:

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

之后我调用如上所示的代码。经过更多调试后,我可以说该方法是在名为Thread-2 的线程中调用的(我不知道那是c++ 线程还是来自JNI 的新线程)。绝对不是java主线程。然而,该方法的工作被中断。这意味着如果我调试代码,我可以看到数据很快就会设置,但在下一个调试步骤中会执行 getApplicationData 方法(只有在 c++ 调用它时才会发生)。

编辑 我调用的Java方法:

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

第二种方法是

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

【问题讨论】:

  • 你能澄清你的问题的描述吗?您首先说“javaReceiveMethod”在C++线程上执行,然后您说它在主线程上执行?顺便说一句,方法调用是同步执行的。
  • 我会更新我的答案,并尽可能清楚地描述它。但目前我很困惑 JNI 到底在做什么。
  • 您尚未向我们展示您正在调用的 Java 方法。
  • JNI 本身不会创建新线程,因此 Thread-2 应该是您的 C++ 线程。您可以将 JavaVMAttachArgs arg 中的线程名称设置为 AttachCurrentThread() btw。
  • 作为 JNI 编码的一般建议:从长远来看,为您执行所有检查的所有 JNI 函数(返回代码、ExceptionOccurred() 等)都有一个包装器确实值得如果出现问题,则抛出 C++ 异常。自 2003 年以来,我们一直在使用这样的包装器,它不止一次地拯救了我们。

标签: java c++ java-native-interface


【解决方案1】:

CallVoidMethod 同步执行。

也许你在 c++ 方面有异常?,你使用 c++ jni 异常检查吗?:

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

C++ 代码(在共享对象库中)在它自己的 C++ 线程中运行。它正在使用已启动并运行的 Java 应用程序的现有 JavaVM。

不清楚您是否已将当前线程附加到虚拟机。确保env 来自AttachCurrentThread 电话。您将在此处找到示例:How to obtain JNI interface pointer (JNIEnv *) for asynchronous calls

【讨论】:

  • 线程已附加。我调用 vm->AttachCurrentThread((void**)&env, NULL);
  • 这确实是我的 Java 代码中抛出的异常,但未在 Java 端显示。 addId 方法导致NullPointerException 并且我天真地认为Java 会在我的开发环境中显示这样的异常,我无法相信这种奇怪的行为会由这样一个愚蠢的错误引起。任何不便敬请谅解。非常感谢你指点我env-&gt;ExceptionOccured。我还是 JNI 的新手,不知道我可以通过这种方式捕获异常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-10
  • 1970-01-01
相关资源
最近更新 更多