获取Java虚拟机引用,从C++创建Java对象和并发

4
我有以下情况:一个C++服务器应用程序正在监听传入的客户端连接。每次客户端连接尝试都会生成一个新的会话。该会话将调用特定服务,具体取决于从客户端提供的序列化数据中提供的服务ID。一旦服务返回结果,会话将向客户端发送数据。
情况的陷阱在于,该服务是用Java实现的。
因此我的问题是:
1.如果收到新的客户端请求,如何使用C++的VM引用来实例化和调用Java服务类?
我知道我需要一个Java VM来做到这一点。由于C++服务器类将首先通过SWIG生成的包装器从Java应用程序中调用,因此我想我可以将此应用程序的VM引用传递给我的服务器类(以及随后的会话)。
但是:
2.如何在我的Java代码中获取对当前VM的引用?
通常,在启动服务器后,Java应用程序将什么也不做。也许我必须保持它处于空闲状态以保持VM引用活动?除了服务内部的正常并发处理之外,是否有任何特殊的问题需要考虑关于C++和Java交互中的并发调用?

例子:

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

我已经看过这个问题,但必须承认解决方案很难理解,而且不清楚提问者试图实现什么。在这篇文章中,作者正在使用Java内置类型进行类似的操作,但似乎代码生成器不能用于自己的Java类型。我也知道可以生成新的VM来完成工作,但如果可能的话,我想使用现有的VM。

对于问题1,我不确定,但也许可以使用 jint JNI_OnLoad(JavaVM *vm, void *reserved); 方法在使用C++服务器类加载库时获取指向VM的指针。不幸的是,Oracle文档没有解释这个问题。是否有人有相关经验可以提供帮助?


为什么不用Java编写整个程序,从而消除这个问题呢? - user207421
C++库已经包含了许多功能。但是我同意您的看法,如果所有的东西都由Java代码编写,那么可能更容易维护。 - little_planet
1个回答

6

调用 API + JNI 函数 将会有所帮助。

如何在 Java 代码中获取对当前 VM 的引用?

  1. 调用 JNI_GetCreatedJavaVMs 方法获取一个 JavaVM* 引用。如果 JVM 已经在当前进程中启动,则此函数通常返回仅一个 JVM 引用数组。
  2. 如果当前线程未由 Java 创建,则使用步骤 1 中获取到的引用调用 JavaVM->AttachCurrentThread。如果当前线程已与 JVM 相关联,则跳过此步骤。
  3. 调用 JavaVM->GetEnv 获取当前线程的 JNIEnv* 指针。使用 JNIEnv 结构体,您将能够调用 JNI 函数,如下所示。

如何通过 C++ 中的 VM 引用实例化和调用 Java 服务类?

  1. 使用JNIEnv->FindClass获取要实例化的Java类的jclass引用。
  2. 调用JNIEnv->GetMethodID获取类的构造函数的引用。默认构造函数(即无参构造函数)的签名为"()V"
  3. 调用JNIEnv->NewObject,将步骤1和2中获取的jclassjmethodID传递给它,以实例化给定的类。
  4. 运行JNIEnv->CallObjectMethod来执行一个Java方法。 obj参数是在步骤3中获取的对象引用,methodID参数是您想要调用的方法,通过GetMethodID获取。

示例代码

    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;

注意事项

  • 您可以在整个应用程序中缓存和重复使用JavaVM*jclassjmethodID
  • JNIEnv*与线程相关联。它只能在一个线程内重复使用。
  • JNI函数本身是线程安全的。但是,如果您在C++中使用静态变量,请确保在C++代码中正确同步对这些变量的访问。

哇,谢谢。没想到你会给我这么完整和详细的答案。我今天会试一下! - little_planet
小问题补充:之后我需要释放jobject吗?否则,Java垃圾回收器就无法确定我何时完成该服务。如何操作?使用JNIenv的DeleteLocalRef吗? - little_planet
1
@little_planet 正确。一旦完成服务,调用 DeleteLocalRef 以释放 jobject。对于本地方法来说,这是不必要的 - JVM会在从本地方法返回时自动释放所有本地 jobject - apangin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接