我试图通过使用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
正在访问数据库并获取存储在applicationData
中的一些objectParam
。不幸的是,C ++代码在java类完成它的工作之前获取了applicationData。 applicationData为null
,JNI崩溃。为什么?我找不到任何Oracle的文档,说明CallVoidMethod是异步执行的吗?
修改
我确认java方法没有异常。一切似乎都很好,除了Java在C ++尝试访问数据时仍然很忙。
修改
我可以确认,如果我调试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;
}
答案 0 :(得分:3)
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。