如何在JNI方法中正确调用NSOpenPanel并在主线程运行?
问题:Java Swing应用通过JNI调用Cocoa文件对话框时的主线程异常
我对Objective-C和JNI不熟悉,JNI类仅能部分执行,以下是我的尝试:
代码文件
Open.java(最小复现代码)
这是大型Java Swing应用的最小复现代码,目的是替代难用的java.awt.FileDialog或功能有限的javax.swing.JFileChooser,实现更美观完善的打开文件对话框:
import java.awt.event.*; import javax.swing.*; public class Open extends JFrame { public Open () { getContentPane().add(new JButton(new AbstractAction() { public void actionPerformed (ActionEvent e) { try { NativeOpenFileDialog.run(); } catch (Throwable ex) { System.err.println(ex.getMessage()); } } })); pack(); setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { new Open(); } }); } }
NativeOpenFileDialog.java(JNI桥接类)
import javax.swing.*; public class NativeOpenFileDialog { static { System.loadLibrary("natopndlg"); }; public static native void run (); public static void main (String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { NativeOpenFileDialog.run(); } }); } }
NativeOpenFileDialog.m(Objective-C实现)
#import <Cocoa/Cocoa.h> #include <jni.h> #include "NativeOpenFileDialog.h" JNIEXPORT void JNICALL Java_NativeOpenFileDialog_run (JNIEnv *thisEnv, jclass jcls) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanChooseFiles:YES]; [panel setCanChooseDirectories:YES]; [panel setAllowsMultipleSelection:YES]; if ([panel runModal] != NSModalResponseOK) NSLog(@"User did not press OK"); [pool release]; }
Makefile(Xcode 15.2环境)
SDK = /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk Open.class: Open.java NativeOpenFileDialog.java NativeOpenFileDialog.m javac -h . NativeOpenFileDialog.java javac Open.java gcc -dynamiclib -o libnatopndlg.dylib -framework Cocoa -isysroot $(SDK) -I $(JAVA_HOME)/include -I $(JAVA_HOME)/include/darwin -fobjc-exceptions -std=c99 NativeOpenFileDialog.m
执行步骤与异常
执行make编译后,运行命令:
java Open
点击按钮时抛出异常:
NSInternalInconsistencyException', reason: 'NSWindow should only be instantiated on the main thread!
问题分析与思路
这个异常是合理的,因为Cocoa的UI组件必须在主线程(UI线程)上创建。我认为需要确保NSOpenPanel在主线程打开,并且作为JFrame的子窗口,这至少需要实现一个Java函数来获取JFrame对应的NSWindow实例。
内容的提问来源于stack exchange,提问作者Jeff Holt
相关产品推荐
相关产品推荐

