views:

48

answers:

2

Hi,

I have been working on a SWT-based project which is intended to be deployed as Java Web Start, and thus be used on multiple platforms.

So far I have managed to tackle the exporting problem that arises due to the system-specific libraries SWT depends on (see relevant thread). The resultant jar seems to work start fine on 32/64-bit linux and 64-bit windows, however execution fails on a Mac with the following output:

$ 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

I have checked a number of relevant threads: (Can't get SWT Display on Mac OS X, Problems With SWT on Mac) as well as the UI Thread entry on the SWT FAQ and tutorials such as Bringing your Java App to Mac and Deploying SWT applications on Mac OSX.

It's my understanding that the problem originates from thread handling on Mac OSX, and I should try to implement the JVM argument -XstartOnFirstThread at the execution. Is this right?

Assuming that my understanding of the problem is accurate, I am a bit confused as this software is intended to be cross-platform and run on javaws. Do I need to create an info.plist file, if so where in the package and how, otherwise how can I "conditionally" pass that argument to JVM at the time of execution?

Thanks in advance,

A: 

SWT (like any other UI framework) has a "UI thread". That is usually the main thread (i.e. the one that executed main(String[] args). All calls to UI methods must happen in this thread.

If you need to call a UI method from a non-UI thread, you must wrap it:

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

If you need to wait for the result, you can use syncExec()

Aaron Digulla
Well the theory makes sense, but I am confused as to why it becomes a problem specific to Mac environment. Besides as I wasn't the one who coded the GUI, I have no clue as to where in the software there might be "calls to UI methods from a non-UI thread" which makes the problem all the more complicated.
posdef
+2  A: 

Yes, you will definitely need -XstartOnFirstThread to get this working on Mac OS X. Since it's a VM parameter, you can only specify it when launching your application, so detecting the OS from you code and setting it if it's Mac OS X is not possible. The solution on the Eclipse site creates a proper Mac OS X My Application.app, which is platform specific and, again, not feasible in your case.

However, I just tried running an Eclipse RCP application on Windows XP with the -XstartOnFirstThread argument specified, and it didn't complain at all. This means that you can specify this argument in your JNLP file and presumably it will be ignored on all other platforms and picked up on Mac OS X.

UPDATE: If for some reason -XstartOnFirstThread causes trouble on any platform, or you just want to Do The Right Thing, there is another possible solution. You could detect the user's OS in the browser — assuming that the application is launched from a web page —, and supply a different JNLP for Mac OS X and other platforms.

UPDATE 2: As pointed out in the comments, there is a tutorial on deploying SWT applications with Java Web Start. I simply launched the JNLP on Mac OS X (10.6.x), and it worked. Looking at the example JNPL I found the following:

<?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>

Note the <extension href="swt.jnlp"/> line towards the end, pointing to the platform-specific SWT JNLP file (some parts omitted here):

<?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>

There it is towards the end of the file: the Mac OS X specific -XstartOnFirstThread argument.

Zsolt Török
your suggestion (specifying in the JNLP file) sounds cool, could anyone deny or confirm it??
posdef
answering my own question here; `-XstartOnFirstThread` argument is apparently an unrecognized option on linux64 (sun-jdk-1.6.0) which results in failed execution:> java -jar -XstartOnFirstThread dist/test.jar Unrecognized option: -XstartOnFirstThreadCould not create the Java virtual machine.[2]+ Killed java -jar dist/test.jar
posdef
@posdef: interesting, apparently the Windows 32-bit VM is less picky than the Linux 64-bit version. Anyhow, this means you have to have two different JNLPs, as suggested above.
Zsolt Török
@Zsolt: hmm, how about checking the OS in the JNLP file and passing arguments accordingly, as written here (http://www.eclipse.org/swt/jws/)? It should work right? Note that they use the OS-check for another reason.
posdef
@posdef: great find, looking into the referenced swt.jnlp revealed that you can indeed do that. See updated answer.
Zsolt Török
@Zsolt: I dont get the extension part, why two different files?
posdef
@posdef: if I understood correctly you don't have to split the SWT specific stuff into a separate JNLP, but for whatever reason they did. Try this two-file approach first to verify that it works and then you can try to combine them if you like. Also note that swt.jnlp adds platform-specific JARs, be sure to replicate that.
Zsolt Török
@Zsolt: well I have already sorted out the different libraries thing, as I have mentioned in the original post. At this stage what I need to do is to pass the `-XstartOnFirstThread` argument, conditioned on the OS
posdef