java - 使用 JNI 创建 JFrame
问题描述
我有需要从 C++ 调用的 java 库,使用 JNI 我能够成功调用 java 库上的方法,除了一个。
有问题的方法创建了一个 JFrame 并导致 jvm 在 MacOS 上运行时陷入死锁,经过一些研究,我发现任何涉及 AWT/swing 的与 JNI 的交互都不能从主线程完成。
所以我修改了代码以在辅助线程上对 JVM 进行所有调用,但我仍然遇到相同方法的问题。我得到了要显示的 JFrame,但之后 C++ 代码在 CFRunLoopRunInMode 函数调用上被阻塞,直到我关闭 JFrame,然后进程突然终止。
目前我的代码看起来像这样(这是一个简化版本,但基本相同。为简单起见,C++ 代码中也省略了错误处理):
Java 代码
import javax.swing.*;
import java.awt.*;
public class MyClass extends JFrame
{
private Thread frameThread;
public void createFrame()
{
System.out.println("Creating Frame Thread");
frameThread = new Thread(() -> {
setTitle("JFrameCenter Position");
add(new JLabel("JFrame set to center of the screen", SwingConstants.CENTER), BorderLayout.CENTER);
setSize(400, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null); // this method display the JFrame to center position of a screen
setVisible(true);
});
System.out.println("Starting Frame Thread");
frameThread.start();
}
}
C++ 代码
#include <iostream>
#include <jni.h>
#include <dlfcn.h>
#include <CoreFoundation/CoreFoundation.h>
#define JLI_DYLIB "/lib/jli/libjli.dylib"
#define JNI_CREATEVM_FUNC "JNI_CreateJavaVM"
typedef jint (JNICALL CreateJavaVM_t)(JavaVM **pvm, void **env, void *args);
void* DyLibHandler;
JavaVM *jvm;
CFRunLoopRef currentLoopRef;
CFRunLoopSourceRef sourceRef;
void *GetStartJvmFunc(std::string javaHome) {
std::string jliPath = javaHome.append(JLI_DYLIB);
DyLibHandler = dlopen(jliPath.c_str(), RTLD_LAZY);
if(!DyLibHandler)
{
return NULL;
}
void * jvmFunc = dlsym(DyLibHandler, JNI_CREATEVM_FUNC);
if (!jvmFunc)
{
dlclose(DyLibHandler);
return NULL;
}
return jvmFunc;
}
void StartJvm(const char * javaHome, const char * classPath) {
std::string jarsPath = "-Djava.class.path=";
std::string home = "-Djava.home=";
JavaVMOption* options = new JavaVMOption[3]; // JVM invocation options
options[0].optionString = (char*)jarsPath.append(classPath).c_str();
options[0].extraInfo = NULL;
options[1].optionString = (char*)home.append(javaHome).c_str();
options[1].extraInfo = NULL;
options[2].optionString = "-Dsun.net.inetaddr.ttl=10";
options[2].extraInfo = NULL;
JavaVMInitArgs vm_args;
vm_args.options = options;
vm_args.nOptions = 3;
vm_args.version = JNI_VERSION_1_8;
JNIEnv *jniEnvironment;
CreateJavaVM_t* createVMFunc = (CreateJavaVM_t*)GetStartJvmFunc(std::string(javaHome));
jint rc = createVMFunc(&jvm, (void**)&jniEnvironment, &vm_args); // YES !!
delete[] options; // we then no longer need the initialisation options.
if (rc != JNI_OK) {
createVMFunc = NULL;
exit(EXIT_FAILURE);
}
}
void CreateJFrame(JNIEnv *jniEnvironment)
{
jclass localHandler = jniEnvironment->FindClass("MyClass");
if (!localHandler)
{
std::cout << "Could not Find LocalClass" << std::endl;
exit(EXIT_FAILURE);
}
jclass classHandle = (jclass) jniEnvironment->NewGlobalRef(localHandler);
jniEnvironment->DeleteLocalRef(localHandler);
jmethodID constructorHandler = jniEnvironment->GetMethodID(classHandle, "<init>", "()V");
if (!constructorHandler)
{
std::cout << "Could not find Constructor" << std::endl;
exit(EXIT_FAILURE);
}
jmethodID createMethodHandler = jniEnvironment->GetMethodID(classHandle, "createFrame", "()V");
if (!createMethodHandler)
{
std::cout << "Could not Find CreateFrame Method" << std::endl;
exit(EXIT_FAILURE);
}
jobject localRef = jniEnvironment->NewObject(classHandle, constructorHandler);
if(!localRef)
{
std::cout << "Could not instantiate MyClass" << std::endl;
exit(EXIT_FAILURE);
}
jobject classInstance = jniEnvironment->NewGlobalRef(localRef);
jniEnvironment->DeleteLocalRef(localRef);
if (classInstance == nullptr)
{
std::cout << "Could not create global ref to MyClass instance" << std::endl;
exit(EXIT_FAILURE);
}
jniEnvironment->CallVoidMethod(classInstance, createMethodHandler);
}
void SourceCallback ( void *info ) {
std::cout << "From Source Callback! Got Event" << std::endl;
}
void * ThreadProc(void * arg)
{
JNIEnv *env = NULL;
jint result =jvm->AttachCurrentThread((void **)&env, NULL);
if( result != JNI_OK)
{
std::cout << "Error! Could not attach JNIEnv to thread (" << result << ")"<< std::endl;
goto clean;
}
CreateJFrame(env);
std::cout << "Deataching Thread" << std::endl;
result = jvm->DetachCurrentThread();
if(result != JNI_OK)
{
std::cout << "Error! could not deatached current thread (" << result << ")" << std::endl;
}
else{
std::cout << "Done Deataching Thread" << std::endl;
}
clean:
std::cout << "Done Creating Frame" << std::endl;
CFRunLoopSourceSignal(sourceRef);
CFRunLoopWakeUp(currentLoopRef);
std::cout << "Done signalling RunLoop" << std::endl;
return NULL;
}
int main(int argc, const char * argv[])
{
currentLoopRef = CFRunLoopGetCurrent();
CFRunLoopSourceContext sourceContext;
sourceContext.version = 0;
sourceContext.info = NULL;
sourceContext.retain = NULL;
sourceContext.release = NULL;
sourceContext.copyDescription = NULL;
sourceContext.equal = NULL;
sourceContext.hash = NULL;
sourceContext.schedule = NULL;
sourceContext.cancel = NULL;
sourceContext.perform = &SourceCallback;
sourceRef = CFRunLoopSourceCreate(NULL, 0, &sourceContext);
CFRunLoopAddSource(currentLoopRef, sourceRef, kCFRunLoopCommonModes);
StartJvm(/*JavaHome*/, /*ClassPaht*/);
pthread_t vmThread;
pthread_create(&vmThread, NULL, ThreadProc, NULL);
CFRunLoopRunResult result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, TRUE);
std::cout << "Done with Loop Run" << std::endl;
}
使用上面的代码,我希望框架能够显示并立即关闭,并在控制台上看到SourceCallback
函数的打印和调用后的打印。CFRunLoopRunInMode
但是发生的情况是框架没有关闭并且两次打印都没有发生,我必须手动关闭框架然后程序终止而不打印任何内容。
我也尝试使用 while 循环而不是CFRunLoopRunInMode
VM 上发生了相同的死锁。即使我没有向 CFRunLoopSource 发出信号,程序也不会终止(CFRunLoop 不会超时),直到我手动关闭 JFrame 并且最后一次打印没有发生。
我已经陷入这个问题一段时间了,我对正在发生的事情一无所知。为什么程序“等待”直到 Frame 关闭才突然终止?为什么 CFRunLoop 忽略事件和超时计时器?
编辑:我通过在主线程上JNIEnv *
创建指针并从辅助线程调用以获得有效的. 可悲的是,程序的行为与以前相同,JFrame 得到显示,主线程卡住,直到我关闭 JFrame。JavaVm
AttachToCurrentThread
JNIEnv
我还注意到我忘记将新源添加到 CFRunLoop,这就是为什么在从辅助线程发出 CFRunLoop 信号后没有调用源回调的原因。现在我看到了来自源回调的打印,但主线程仍然卡在 CFRunLoop 上。
解决方案
推荐阅读
- node.js - 升级到 Gulp 4 时发出异步完成警告
- android - Google Places API 如何在内部为 android/ios 工作?
- python - 使用 Python 的 PIL 删除图像背景并创建透明图像
- laravel - 无法在 Laravel 中打开使用表单请求验证的表单
- sql - 在 SQL 中替换和拆分字符串并从值创建变量
- java - Java:通配符类型与普通类型
- c# - 如何使用 C# 将消息从 AWS lambda 函数发布到 AWS IoT Core
- java - 如何使用绝对路径为 *directory* 创建 FileSystem 对象?
- machine-learning - 我们如何分析损失与时期图?
- python - 在 Python 输入提示中启用自动完成