【问题标题】:Fetch Java VM Reference, create Java Object from C++ and Concurrency获取 Java VM 参考,从 C++ 和并发创建 Java 对象
【发布时间】:2016-06-05 15:02:48
【问题描述】:

我遇到了以下情况:C++ 服务器应用程序正在侦听传入的客户端连接。在每次客户端连接尝试时,都会生成一个新会话。此会话将调用特定服务,具体取决于服务 id,该服务 id 在来自客户端的序列化数据中提供。一旦结果从服务到达,会话就会将数据发送到客户端。

该场景中的缺陷是,该服务是用 Java 实现的。

所以我的问题是:

  1. 如果有新的客户端请求到达,如何使用 C++ 中的 VM 引用实例化和调用 Java 服务类?

我知道为此我需要一个 Java VM。而且因为 C++ 服务器类将首先从 Java 应用程序调用(通过 SWIG 生成的包装器),我想我可能会将此应用程序的 VM 引用传递给我的服务器类(以及之后的会话)。

但是:

  1. 如何在我的 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
            }
      }
}

我已经看到了this question,但我不得不承认这个解决方案很难理解,而且还不清楚提问者想要达到什么目的。在this post 中,作者正在使用Java 内置类型做类似的事情,但似乎代码生成器不能用于自己的Java 类型。我也知道that a new VM can be generated 可以完成这项工作,但如果可能的话,我想使用现有的。

编辑

to 1) 我不确定,但是当我使用 C++ 服务器类加载库时,jint JNI_OnLoad(JavaVM *vm, void *reserved); 方法可能可用于获取指向 VM 的指针。不幸的是,Oracle documentation 没有解释这个问题。有人可能有这方面的经验吗?

【问题讨论】:

  • 为什么不用Java写完整的东西来消除问题呢?
  • 具有很多功能的 C++ 库已经存在。但我同意你的观点,如果一切都是 Java 代码,它会更容易,也许也更易于维护。

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


【解决方案1】:

The Invocation API + JNI Functions 会有所帮助。

如何在我的 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. 在第 1 步和第 2 步中通过 jclass 和 jmethodID 调用 JNIEnv->NewObject 以实例化给定的类。
  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*、jclass、jmethodID。
  • JNIEnv* 与线程相关联。它只能在一个线程内重复使用。
  • JNI 函数本身是线程安全的。但是,如果您在 C++ 中使用静态变量,请确保对这些变量的访问在 C++ 中正确同步。

【讨论】:

  • 哇,谢谢。没想到会有如此完整而广泛的答案。我今天就试试看!
  • 附加小问题:之后我是否必须发布作业?我猜java垃圾收集器不会知道我什么时候完成服务。这是怎么做到的?通过使用 JNIenv 的 DeleteLocalRef?
  • @little_planet 正确。完成服务后,致电DeleteLocalRef 释放jobject。这对于本机方法不是必需的 - JVM 在从本机方法返回时自动释放所有本地 jobjects。
猜你喜欢
  • 2010-11-02
  • 1970-01-01
  • 2011-12-27
  • 1970-01-01
  • 1970-01-01
  • 2020-06-10
  • 1970-01-01
  • 2014-12-25
  • 1970-01-01
相关资源
最近更新 更多