您可以使用 JNI 在从 java 调用的 c++ 函数中创建新的 JVM 吗?
Can you create a new JVM in a c++ function called from java using JNI?
所以我的设置是我有一个由我开发的 .dll (A.dll),它在原始应用程序中是从外部进程调用的基本上只是一个 .exe 文件,我没有 (B.exe) 的源代码。 A.dll的目的是和一个.jar文件进行通信,也是我开发的(C.jar) .所以在应用中,"communication flows"如下图
B.exe -> A.dll -> (通过 JNI) -> C.jar
现在,我想做的是添加 A.dll 和 C.jar 之间的调用作为 C.jar 开发环境中我的测试套件的一部分。到目前为止,我已经创建了另一个 .dll (D.dll),它反映了 A.dll 中的所有函数,但使用 JNIEXPORT,并简单地直接调用 A.dll 中的相应函数。所以这种情况下的"communication flow"会变成:
C.jar 开发框架中的单元测试 -> (通过 JNI) -> D.dll -> A.dll -> (通过 JNI) -> C.jar
在这一点上,一个非常简单的函数调用会在 C.jar 中简单地打印出一些内容,从而贯穿整个链;从单元测试调用一直到 C.jar。然而,当我调用 A.dll 中的函数时,问题出现了,它使用 CreateJavaVM() 创建了一个新的 JVM,这会产生以下错误:
VM初始化出错
无法加载本机库:找不到指定的过程
所以基本上我想知道是否真的有可能做到这一点,或者当已经有 [=106= 时调用 CreateJavaVM() 根本不可能] JVM在同一个进程中?我知道你不能在同一个进程中多次调用 CreateJavaVM() ,但在这种情况下它只被调用一次但是进程中已经存在一个 JVM - 你能不能有多个 JVM 运行 在同一个进程中?
解决方案:
感谢@apangin 的回答,下面的代码片段解决了我的问题:
jsize nVMs = 0;
JavaVM** buffer;
jni_GetCreatedJavaVMs = (GetCreatedJavaVMs) GetProcAddress(GetModuleHandle(
TEXT("jvm.dll")), "JNI_GetCreatedJavaVMs");
if (jni_GetCreatedJavaVMs == NULL) {
// stuff
CreateJavaVM(&jvm, (void **) &env, &args);
} else {
jni_GetCreatedJavaVMs(NULL, 0, &nVMs); // 1. just get the required array length
JavaVM** buffer = new JavaVM*[nVMs];
jni_GetCreatedJavaVMs(buffer, nVMs, &nVMs); // 2. get the data
buffer[0]->GetEnv((void **) &env, jni_version); // 3. get environment
jvm = buffer[0];
}
当前的 JNI 规范明确指出 creation of multiple VMs in a single process is not supported, and this is actually asserted in HotSpot source code.
即使您的 dll 只调用了一次 JNI_CreateJavaVM
,也不意味着这是整个过程中的第一次调用。事实上,JNI_CreateJavaVM
首先由 java.exe
或您的 IDE 的另一个启动器调用(idea.exe
、eclipse.exe
、netbeans.exe
等)。
因此,A.dll
不应盲目创建Java VM,而是应先检查JVM是否已存在于当前进程中,方法是调用JNI_GetCreatedJavaVMs. If the function returns nonempty array, then use GetEnv or AttachCurrentThread获取现有进程的JNIEnv*
VM,否则创建一个新的 VM。
所以我的设置是我有一个由我开发的 .dll (A.dll),它在原始应用程序中是从外部进程调用的基本上只是一个 .exe 文件,我没有 (B.exe) 的源代码。 A.dll的目的是和一个.jar文件进行通信,也是我开发的(C.jar) .所以在应用中,"communication flows"如下图
B.exe -> A.dll -> (通过 JNI) -> C.jar
现在,我想做的是添加 A.dll 和 C.jar 之间的调用作为 C.jar 开发环境中我的测试套件的一部分。到目前为止,我已经创建了另一个 .dll (D.dll),它反映了 A.dll 中的所有函数,但使用 JNIEXPORT,并简单地直接调用 A.dll 中的相应函数。所以这种情况下的"communication flow"会变成:
C.jar 开发框架中的单元测试 -> (通过 JNI) -> D.dll -> A.dll -> (通过 JNI) -> C.jar
在这一点上,一个非常简单的函数调用会在 C.jar 中简单地打印出一些内容,从而贯穿整个链;从单元测试调用一直到 C.jar。然而,当我调用 A.dll 中的函数时,问题出现了,它使用 CreateJavaVM() 创建了一个新的 JVM,这会产生以下错误:
VM初始化出错 无法加载本机库:找不到指定的过程
所以基本上我想知道是否真的有可能做到这一点,或者当已经有 [=106= 时调用 CreateJavaVM() 根本不可能] JVM在同一个进程中?我知道你不能在同一个进程中多次调用 CreateJavaVM() ,但在这种情况下它只被调用一次但是进程中已经存在一个 JVM - 你能不能有多个 JVM 运行 在同一个进程中?
解决方案:
感谢@apangin 的回答,下面的代码片段解决了我的问题:
jsize nVMs = 0;
JavaVM** buffer;
jni_GetCreatedJavaVMs = (GetCreatedJavaVMs) GetProcAddress(GetModuleHandle(
TEXT("jvm.dll")), "JNI_GetCreatedJavaVMs");
if (jni_GetCreatedJavaVMs == NULL) {
// stuff
CreateJavaVM(&jvm, (void **) &env, &args);
} else {
jni_GetCreatedJavaVMs(NULL, 0, &nVMs); // 1. just get the required array length
JavaVM** buffer = new JavaVM*[nVMs];
jni_GetCreatedJavaVMs(buffer, nVMs, &nVMs); // 2. get the data
buffer[0]->GetEnv((void **) &env, jni_version); // 3. get environment
jvm = buffer[0];
}
当前的 JNI 规范明确指出 creation of multiple VMs in a single process is not supported, and this is actually asserted in HotSpot source code.
即使您的 dll 只调用了一次 JNI_CreateJavaVM
,也不意味着这是整个过程中的第一次调用。事实上,JNI_CreateJavaVM
首先由 java.exe
或您的 IDE 的另一个启动器调用(idea.exe
、eclipse.exe
、netbeans.exe
等)。
因此,A.dll
不应盲目创建Java VM,而是应先检查JVM是否已存在于当前进程中,方法是调用JNI_GetCreatedJavaVMs. If the function returns nonempty array, then use GetEnv or AttachCurrentThread获取现有进程的JNIEnv*
VM,否则创建一个新的 VM。