web-dev-qa-db-fra.com

Créer une application Java SWT multiplate-forme

J'ai écrit une interface graphique Java en utilisant SWT. Je compresse l'application à l'aide d'un script ANT (fragment ci-dessous).

<jar destfile="./build/jars/swtgui.jar" filesetmanifest="mergewithoutmain">
  <manifest>
    <attribute name="Main-Class" value="org.swtgui.MainGui" />
    <attribute name="Class-Path" value="." />
  </manifest>
  <fileset dir="./build/classes" includes="**/*.class" />
  <zipfileset excludes="META-INF/*.SF" src="lib/org.Eclipse.swt.win32.win32.x86_3.5.2.v3557f.jar" />
</jar>

Cela produit un seul fichier jar sur lequel je peux simplement cliquer deux fois sur Windows pour exécuter mon interface graphique. L'inconvénient est que j'ai dû explicitement empaqueter le paquet Windows SWT dans mon pot. 

J'aimerais pouvoir exécuter mon application sur d'autres plateformes (principalement Linux et OS X). La méthode la plus simple consiste à créer des fichiers JAR spécifiques à une plate-forme qui regroupent les fichiers SWT appropriés dans des fichiers JAR distincts.

Y a-t-il une meilleure manière de faire cela? Est-il possible de créer un seul fichier JAR qui fonctionnerait sur plusieurs plates-formes?

32
mchr

J'ai une implémentation de travail qui est maintenant référencée à partir de SWT FAQ .

Cette approche est maintenant disponible pour être utilisée comme tâche ANT: SWTJar

[EDIT] SWTJar a été mis à jour pour utiliser la solution d'Alexey Romanov décrite ci-dessus.

build.xml

Tout d'abord, je construis un pot contenant toutes mes classes d'application.

<!-- UI (Stage 1) -->   
<jarjar jarfile="./build/tmp/intrace-ui-wrapper.jar">
  <fileset dir="./build/classes" includes="**/shared/*.class" />
  <fileset dir="./build/classes" includes="**/client/gui/**/*.class" />
  <zipfileset excludes="META-INF/*.MF" src="lib/miglayout-3.7.3.1-swt.jar"/>
</jarjar>

Ensuite, je construis un pot pour contenir tout ce qui suit:

  • JARs
    • Le pot que je viens de construire
    • Tous les pots SWT
  • Des classes
    • Les classes "Jar-In-Jar"
    • Une classe de chargeur spéciale - voir ci-dessous

Voici le fragment de build.xml.

<!-- UI (Stage 2) -->
<jarjar jarfile="./build/jars/intrace-ui.jar">
  <manifest>
    <attribute name="Main-Class" value="org.intrace.client.loader.TraceClientLoader" />
    <attribute name="Class-Path" value="." />
  </manifest>
  <fileset dir="./build/classes" includes="**/client/loader/*.class" />
  <fileset dir="./build/tmp" includes="intrace-ui-wrapper.jar" />
  <fileset dir="./lib" includes="swt-*.jar" />
  <zipfileset excludes="META-INF/*.MF" src="lib/jar-in-jar-loader.jar"/>
</jarjar>

TraceClientLoader.Java

Cette classe de chargeur utilise le programme jar-in-jar-loader pour créer un chargeur de classe qui charge les classes à partir de deux fichiers jar.

  • Le bon pot SWT
  • Le banderole d'application

Une fois que nous avons ce chargeur de classe, nous pouvons lancer la méthode principale de l’application à l’aide de la réflexion.

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;
  }
}

[EDIT] Comme indiqué ci-dessus, pour ceux qui recherchent le "chargeur de classes jar-in-jar": Il est inclus dans le JDT d'Eclipse (Java IDE construit sur Eclipse). Ouvrez org.Eclipse.jdt.ui_ * numéro_version * .jar avec un archiveur et vous trouverez un fichier jar-in-jar-loader.Zip à l'intérieur. J'ai renommé cela jar-in-jar-loader.jar.

intrace-ui.jar - c'est le fichier jar que j'ai construit à l'aide du processus décrit ci-dessus. Vous devriez pouvoir exécuter ce seul fichier jar sur l’un des win32/64, linux32/64 et osx32/64.

[EDIT] Cette réponse est maintenant référencée à partir de SWT FAQ .

19
mchr

Je viens de rencontrer le même problème. Je ne l'ai pas encore essayé, mais je prévois d'inclure des versions de swt.jar pour toutes les plateformes et de charger celle qui convient de manière dynamique au début de la méthode main.

UPDATE: Cela a fonctionné. build.xml inclut tous les pots:

<zipfileset dir="/home/aromanov/workspace/foo/lib" includes="swt_linux_gtk_x86.jar"/>
<zipfileset dir="/home/aromanov/workspace/foo/lib" includes="swt_macosx_x86.jar"/>
<zipfileset dir="/home/aromanov/workspace/foo/lib" includes="swt_win32_x86.jar"/>
<zipfileset dir="/home/aromanov/workspace/foo/lib" includes="swt_linux_gtk_x64.jar"/>
<zipfileset dir="/home/aromanov/workspace/foo/lib" includes="swt_macosx_x64.jar"/>
<zipfileset dir="/home/aromanov/workspace/foo/lib" includes="swt_win32_x64.jar"/>

et ma méthode main commence par appeler ceci:

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);
    }
}

[EDIT] Pour ceux qui recherchent le "chargeur de classes jar-in-jar": Il est inclus dans le JDT d'Eclipse (Java IDE construit sur Eclipse). Ouvrez org.Eclipse.jdt.ui_*version_number*.jar avec un archiveur et vous trouverez un fichier jar-in-jar-loader.Zip à l'intérieur.

29
Alexey Romanov

si vous ne souhaitez pas tout rassembler dans un fichier JAR unique et utiliser jar-in-jar, vous pouvez également résoudre ce problème en incluant des fichiers JAR SWT nommés pour chaque plate-forme cible dans le répertoire lib de votre application déployée:

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

et en chargeant dynamiquement le bon fichier lors de l'exécution en inspectant les propriétés système Java "os.name" et "os.Arch" à l'exécution à l'aide de System.getProperty(String name) pour créer le nom de fichier jar correct.

Vous pouvez ensuite utiliser un peu de réflexion un peu vilain (les puristes OO détournent le regard maintenant!) En appelant la méthode normalement protégée URLClassloader.addURL(URL url) pour ajouter le fichier jar correct au classpath du chargeur de classes du système avant que la première classe SWT ne soit nécessaire.

Si vous pouvez supporter le code-odeur j'ai mis un exemple de travail ici http://www.chrisnewland.com/select-correct-swt-jar-for-your-os-and-jvm-at-runtime- 191

10
ChrisWhoCodes

Il est très étrange que toutes les réponses ici conseillent simplement de regrouper tous les fichiers JAR SWT dans un fichier JAR d'application géant unique. IMHO, cela est strictement contraire à l'objectif de SWT: il y a une bibliothèque SWT pour chaque plate-forme, elle est donc supposée contenir uniquement la bibliothèque SWT appropriée pour chaque plate-forme. C’est très facile, définissez simplement 5 profils de construction dans votre version ANT: win32, win64, linux32, linux64 et mac64 (vous pouvez aussi utiliser mac32, mais tous les Mac modernes sont en 64 bits).

Quoi qu'il en soit, si vous souhaitez avoir une bonne intégration d'applications dans le système d'exploitation, vous devrez faire certaines choses spécifiques au système d'exploitation et vous créerez à nouveau des profils. Pour une application de bureau, il n’est pas pratique de disposer d’un seul package d’applications pour toutes les plateformes, à la fois pour le développeur et pour ses utilisateurs.

1
nucleo

Remplacer le texte sélectionné en gras dans src = "lib / org.Eclipse.swt.win32.win32.x86_3.5.2.v3557f.jar " avec le fichier jt swt spécifié par linux

0
user10679754