Crear una aplicación SWT Java multiplataforma

He escrito una GUI de Java usando SWT. Empaquetar la aplicación usando una secuencia de comandos ANT (fragmento a continuación).

        

Esto produce un único contenedor que en Windows puedo hacer doble clic para ejecutar mi GUI. La desventaja es que tuve que empaquetar explícitamente el paquete SWT de Windows en mi jar.

Me gustaría poder ejecutar mi aplicación en otras plataformas (principalmente Linux y OS X). La forma más sencilla de hacerlo sería crear tarros específicos de plataforma que empaquetaran los archivos SWT apropiados en JAR separados.

¿Hay una mejor manera de hacer esto? ¿Es posible crear un JAR único que se ejecute en múltiples plataformas?

    Me acabo de encontrar con el mismo problema. Todavía no lo he probado, pero planeo incluir versiones de swt.jar para todas las plataformas y cargar la correcta de forma dinámica al comienzo del método main .

    ACTUALIZACIÓN: funcionó. build.xml incluye todos los build.xml

           

    y mi método main comienza con llamar esto:

     private void loadSwtJar() { String osName = System.getProperty("os.name").toLowerCase(); String osArch = System.getProperty("os.arch").toLowerCase(); String swtFileNameOsPart = osName.contains("win") ? "win32" : osName.contains("mac") ? "macosx" : osName.contains("linux") || osName.contains("nix") ? "linux_gtk" : ""; // throw new RuntimeException("Unknown OS name: "+osName) String swtFileNameArchPart = osArch.contains("64") ? "x64" : "x86"; String swtFileName = "swt_"+swtFileNameOsPart+"_"+swtFileNameArchPart+".jar"; try { URLClassLoader classLoader = (URLClassLoader) getClass().getClassLoader(); Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); addUrlMethod.setAccessible(true); URL swtFileUrl = new URL("rsrc:"+swtFileName); // I am using Jar-in-Jar class loader which understands this URL; adjust accordingly if you don't addUrlMethod.invoke(classLoader, swtFileUrl); } catch(Exception e) { throw new RuntimeException("Unable to add the SWT jar to the class path: "+swtFileName, e); } } 

    [EDITAR] Para aquellos que buscan el “cargador de clases jar-in-jar”: está incluido en el JDT de Eclipse (el IDE de Java basado en Eclipse). Abra org.eclipse.jdt.ui_*version_number*.jar con un archivador y encontrará un archivo jar-in-jar-loader.zip dentro.

    Tengo una implementación en funcionamiento a la que ahora se hace referencia desde las preguntas frecuentes de SWT .

    Este enfoque ahora está disponible para usar como una tarea ANT: SWTJar

    [EDITAR] SWTJar ahora se ha actualizado para usar la solución de Alexey Romanov como se describió anteriormente.

    build.xml

    Primero construyo un jar que contiene todas mis clases de aplicaciones.

           

    A continuación, construyo un jar para contener todo lo siguiente:

    • Paso
      • El tarro que acabo de construir
      • Todos los flasks SWT
    • Clases
      • Las clases de cargador de clases “Jar-In-Jar”
      • Una clase de cargador especial – ver abajo

    Aquí está el fragmento de build.xml.

                

    TraceClientLoader.java

    Esta clase de cargador utiliza el jar-in-jar-loader para crear un ClassLoader que carga clases desde dos jar.

    • El jar correcto de SWT
    • El contenedor contenedor

    Una vez que tenemos este cargador de clases, podemos iniciar el método principal de la aplicación real utilizando la reflexión.

     public class TraceClientLoader { public static void main(String[] args) throws Throwable { ClassLoader cl = getSWTClassloader(); Thread.currentThread().setContextClassLoader(cl); try { try { System.err.println("Launching InTrace UI ..."); Class< ?> c = Class.forName("org.intrace.client.gui.TraceClient", true, cl); Method main = c.getMethod("main", new Class[]{args.getClass()}); main.invoke((Object)null, new Object[]{args}); } catch (InvocationTargetException ex) { if (ex.getCause() instanceof UnsatisfiedLinkError) { System.err.println("Launch failed: (UnsatisfiedLinkError)"); String arch = getArch(); if ("32".equals(arch)) { System.err.println("Try adding '-d64' to your command line arguments"); } else if ("64".equals(arch)) { System.err.println("Try adding '-d32' to your command line arguments"); } } else { throw ex; } } } catch (ClassNotFoundException ex) { System.err.println("Launch failed: Failed to find main class - org.intrace.client.gui.TraceClient"); } catch (NoSuchMethodException ex) { System.err.println("Launch failed: Failed to find main method"); } catch (InvocationTargetException ex) { Throwable th = ex.getCause(); if ((th.getMessage() != null) && th.getMessage().toLowerCase().contains("invalid thread access")) { System.err.println("Launch failed: (SWTException: Invalid thread access)"); System.err.println("Try adding '-XstartOnFirstThread' to your command line arguments"); } else { throw th; } } } private static ClassLoader getSWTClassloader() { ClassLoader parent = TraceClientLoader.class.getClassLoader(); URL.setURLStreamHandlerFactory(new RsrcURLStreamHandlerFactory(parent)); String swtFileName = getSwtJarName(); try { URL intraceFileUrl = new URL("rsrc:intrace-ui-wrapper.jar"); URL swtFileUrl = new URL("rsrc:" + swtFileName); System.err.println("Using SWT Jar: " + swtFileName); ClassLoader cl = new URLClassLoader(new URL[] {intraceFileUrl, swtFileUrl}, parent); try { // Check we can now load the SWT class Class.forName("org.eclipse.swt.widgets.Layout", true, cl); } catch (ClassNotFoundException exx) { System.err.println("Launch failed: Failed to load SWT class from jar: " + swtFileName); throw new RuntimeException(exx); } return cl; } catch (MalformedURLException exx) { throw new RuntimeException(exx); } } private static String getSwtJarName() { // Detect OS String osName = System.getProperty("os.name").toLowerCase(); String swtFileNameOsPart = osName.contains("win") ? "win" : osName .contains("mac") ? "osx" : osName.contains("linux") || osName.contains("nix") ? "linux" : ""; if ("".equals(swtFileNameOsPart)) { throw new RuntimeException("Launch failed: Unknown OS name: " + osName); } // Detect 32bit vs 64 bit String swtFileNameArchPart = getArch(); String swtFileName = "swt-" + swtFileNameOsPart + swtFileNameArchPart + "-3.6.2.jar"; return swtFileName; } private static String getArch() { // Detect 32bit vs 64 bit String jvmArch = System.getProperty("os.arch").toLowerCase(); String arch = (jvmArch.contains("64") ? "64" : "32"); return arch; } } 

    [EDITAR] Como se indicó anteriormente, para aquellos que buscan el “cargador de clases jar-in-jar”: está incluido en el JDT de Eclipse (el IDE de Java creado en Eclipse). Abra org.eclipse.jdt.ui_ * version_number * .jar con un archivador y encontrará un archivo jar-in-jar-loader.zip dentro. Cambié el nombre a jar-in-jar-loader.jar.

    intrace-ui.jar – este es el jar que construí usando el proceso descrito arriba. Debería poder ejecutar este único contenedor en win32 / 64, linux32 / 64 y osx32 / 64.

    [EDITAR] Esta respuesta ahora se hace referencia a partir de las preguntas frecuentes de SWT .

    Si no está buscando agrupar todo en un único archivo jar y usar jar-in-jar, también puede resolver este problema incluyendo tarros SWT con nombre para cada plataforma de destino en el directorio lib de su aplicación desplegada:

     lib/swt_win_32.jar lib/swt_win_64.jar lib/swt_linux_32.jar lib/swt_linux_64.jar 

    y cargando el correcto de forma dinámica en el tiempo de ejecución inspeccionando las propiedades del sistema Java "os.name" y "os.arch" en tiempo de ejecución usando System.getProperty(String name) para crear el nombre de archivo jar correcto.

    A continuación, puede utilizar un poco de reflexión (los puristas de OO miran hacia otro lado ahora) invocando el método normalmente protegido URLClassloader.addURL(URL url) para agregar el jar correcto al classpath del cargador de clases del sistema antes de que se necesite la primera clase de SWT.

    Si puedes soportar el código-olor, he puesto un ejemplo que funciona aquí http://www.chrisnewland.com/select-correct-swt-jar-for-your-os-and-jvm-at-runtime-191

    Es muy extraño que todas las respuestas aquí solo aconsejen empaquetar todos los JAR SWT en un solo archivo JAR de aplicación gigante. En mi humilde opinión, esto es estrictamente contra el propósito de SWT: hay una biblioteca SWT para cada plataforma, por lo que se supone que debe empaquetar solo la biblioteca SWT apropiada para cada plataforma. Eso es muy fácil de hacer, solo defina 5 perfiles de comstackción en su comstackción ANT: win32, win64, linux32, linux64 y mac64 (también puede hacer mac32, pero todos los Mac modernos son de 64 bits).

    De todos modos, si quieres tener una buena integración de aplicaciones en el sistema operativo, entonces tendrás que hacer algunas cosas específicas del sistema operativo y estarás con los perfiles de comstackción de nuevo. Para aplicaciones de escritorio, es inconveniente tener un paquete de aplicaciones para todas las plataformas, tanto para el desarrollador como para sus usuarios.

    Reemplazar el texto seleccionado en negrita en src = “lib / org.eclipse.swt.win32.win32.x86_3.5.2.v3557f.jar ” con archivo jar de swt especificado por Linux