在Mac上正确运行基于SWT的跨平台jar文件

25

我一直在开发一个基于SWT的项目,该项目旨在部署为Java Web Start,并且可以在多个平台上使用。

到目前为止,我已经解决了由于SWT依赖于系统特定库而引起的导出问题(请参见相关线程)。生成的jar文件似乎可以在32/64位Linux和64位Windows上正常启动,但是在Mac上执行时会失败,输出如下:

$ java -jar dist/test.jar 
Adding { file:/Volumes/LaCie/ChiBE_Local/swt/swt-cocoa-macosx-x86_64-3.6.1.jar } to the classpath
***WARNING: Display must be created on main thread due to Cocoa restrictions.
Exception in thread "main" java.lang.reflect.InvocationTargetException
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
   at java.lang.reflect.Method.invoke(Method.java:597)
   at org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader.main(JarRsrcLoader.java:58)
Caused by: java.lang.ExceptionInInitializerError
   at org.eclipse.gef.tools.MarqueeSelectionTool.<init>(MarqueeSelectionTool.java:99)
   at org.gvt.MarqueeZoomTool.<init>(MarqueeZoomTool.java:16)
   at org.gvt.action.MarqueeZoomToolAction$1.<init>(MarqueeZoomToolAction.java:28)
   at org.gvt.action.MarqueeZoomToolAction.createTool(MarqueeZoomToolAction.java:28)
   at org.gvt.action.AbstractGEFToolAction.<init>(AbstractGEFToolAction.java:24)
   at org.gvt.action.MarqueeZoomToolAction.<init>(MarqueeZoomToolAction.java:20)
   at org.gvt.TopMenuBar.createBarMenu(TopMenuBar.java:113)
   at org.gvt.ChisioMain.createMenuManager(ChisioMain.java:617)
   at org.eclipse.jface.window.ApplicationWindow.addMenuBar(ApplicationWindow.java:235)
   at org.gvt.ChisioMain.main(ChisioMain.java:149)
   at org.gvt.RuntimeMain.main(RuntimeMain.java:14)
   ... 5 more
Caused by: org.eclipse.swt.SWTException: Invalid thread access
   at org.eclipse.swt.SWT.error(Unknown Source)
   at org.eclipse.swt.SWT.error(Unknown Source)
   at org.eclipse.swt.SWT.error(Unknown Source)
   at org.eclipse.swt.widgets.Display.error(Unknown Source)
   at org.eclipse.swt.widgets.Display.createDisplay(Unknown Source)
   at org.eclipse.swt.widgets.Display.create(Unknown Source)
   at org.eclipse.swt.graphics.Device.<init>(Unknown Source)
   at org.eclipse.swt.widgets.Display.<init>(Unknown Source)
   at org.eclipse.swt.widgets.Display.<init>(Unknown Source)
   at org.eclipse.swt.widgets.Display.getDefault(Unknown Source)
   at org.eclipse.swt.widgets.Display$1.run(Unknown Source)
   at org.eclipse.swt.graphics.Device.getDevice(Unknown Source)
   at org.eclipse.swt.graphics.Resource.<init>(Unknown Source)
   at org.eclipse.swt.graphics.Cursor.<init>(Unknown Source)
   at org.eclipse.draw2d.Cursors.<clinit>(Cursors.java:170)
   ... 16 more

我查看了许多相关的线程:(无法在Mac OS X上获取SWT显示, Mac上的SWT问题) 以及SWT FAQ中的UI线程条目和教程,例如将Java应用程序带到Mac在Mac OSX上部署SWT应用程序

据我所知,问题源于Mac OSX上的线程处理,我应该尝试在执行时实现JVM参数-XstartOnFirstThread。这是正确的吗?

假设我对问题的理解是准确的,我有点困惑,因为该软件旨在跨平台并运行在javaws上。如果是这样,我需要创建一个info.plist文件吗?如果需要,在包中的哪里以及如何创建?否则,在执行时如何“有条件地”将该参数传递给JVM?

先行致谢,


说句实话,我已经改进了错误信息: https://bugs.eclipse.org/bugs/show_bug.cgi?id=470174 ***警告:由于Cocoa的限制,显示必须在主线程上创建。使用vmarg -XstartOnFirstThread。 - Leo Ufimtsev
2个回答

38

在 Mac OS X 上运行程序时,你一定需要使用 -XstartOnFirstThread 参数。由于它是虚拟机参数,所以只能在启动应用程序时指定,因此无法从代码中检测操作系统并设置该参数(如果是 Mac OS X 的话)。Eclipse 网站上的解决方案创建了一个适用于 Mac OS X 的正确的 My Application.app,这是针对特定平台的,而在你的情况下不可行。

然而,我刚刚尝试在 Windows XP 上使用指定了 -XstartOnFirstThread 参数的 Eclipse RCP 应用程序,并且没有遇到任何问题。这意味着你可以在 JNLP 文件中指定此参数,其他平台将忽略它,而在 Mac OS X 上会被识别。

更新:如果 -XstartOnFirstThread 在任何平台上都会出现问题,或者你想要做正确的事情,还有另一种可能的解决方案。你可以在浏览器中检测用户的操作系统(假设应用程序是从网页启动的),并为 Mac OS X 和其他平台提供不同的 JNLP。

更新2:正如评论中指出的那样,有一个关于使用 Java Web Start 部署 SWT 应用程序的教程。我刚刚在 Mac OS X(10.6.x)上启动了 JNLP,并且它可以工作。查看示例 JNPL,我发现以下内容:

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+"
    codebase="http://www.eclipse.org/swt/jws/"
    href="controlexample.jnlp">
<information>
      <title>Control Example</title>
      <vendor>eclipse.org</vendor>
      <homepage href="http://www.eclipse.org/swt/jws/" />
      <description>A demonstration of SWT Widgets</description>
      <description>Control Example</description>
</information>

<security>
    <all-permissions />
</security>

<resources>
    <extension href="swt.jnlp"/>
    <jar href="controlexample.jar" />
</resources>

<application-desc main-class="org.eclipse.swt.examples.controlexample.ControlExample" />
</jnlp>

注意末尾的 <extension href="swt.jnlp"/> 行,指向特定于平台的SWT JNLP文件(此处省略了一些部分):

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+"
    codebase="http://www.eclipse.org/swt/jws/"
    href="swt.jnlp">
<information>
      <title>SWT</title>
      <vendor>eclipse.org</vendor>
      <homepage href="http://www.eclipse.org/swt/jws/" />
      <description>SWT</description>
</information>

<security>
    <all-permissions />
</security>

<resources os="Windows" arch="x86">
    <j2se version="1.4+" />
    <jar href="swt-win32-windows-x86.jar" />
</resources>

...

<resources os="Mac\ OS\ X">
    <j2se version="1.5*" java-vm-args="-XstartOnFirstThread"/>
    <jar href="swt-carbon-osx-universal.jar" />
</resources>

<component-desc/>
</jnlp>

在文件末尾有它:Mac OS X特定的-XstartOnFirstThread参数。


在这里回答自己的问题; -XstartOnFirstThread 参数在linux64 (sun-jdk-1.6.0)上显然是无法识别的选项,导致执行失败:
java -jar -XstartOnFirstThread dist/test.jar 未识别的选项:-XstartOnFirstThread 无法创建Java虚拟机。 [2]+ Killed java -jar dist/test.jar
- posdef
@posdef:有趣的是,显然Windows 32位虚拟机比Linux 64位版本不那么挑剔。无论如何,这意味着您必须像上面建议的那样拥有两个不同的JNLP文件。 - Zsolt Török
@Zsolt:嗯,如何在JNLP文件中检查操作系统并相应地传递参数,就像这里所写的一样(http://www.eclipse.org/swt/jws/)?这应该可以行得通吧?请注意,他们使用操作系统检查是出于另一个原因。 - posdef
@posdef:很好的发现,查看引用的swt.jnlp后发现确实可以这样做。请参见更新的答案。 - Zsolt Török
@Zsolt:我不理解扩展部分,为什么需要两个不同的文件? - posdef
显示剩余3条评论

-3

SWT(像任何其他UI框架一样)有一个“UI线程”。通常这是主线程(即执行main(String[] args)的线程)。所有对UI方法的调用都必须在此线程中进行。

如果您需要从非UI线程调用UI方法,则必须将其包装:

Display.getDefault().asyncExec( new Runnable() { 
    public void run() {
         //ui call here
    }
} );

如果您需要等待结果,可以使用syncExec()

理论上讲是有道理的,但我不明白为什么这会成为特定于 Mac 环境的问题。再者,由于我不是编写 GUI 的人,我不知道在软件中可能存在哪些“从非 UI 线程调用 UI 方法”的问题,这使得问题更加复杂。 - posdef
1
在这种情况下,您的评论没有意义,因为异常发生在调用Display.getDefault()时。 - Maksym Bykovskyy

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