Создать кроссплатформенное Java SWT-приложение - PullRequest
32 голосов
/ 25 апреля 2010

Я написал графический интерфейс Java с использованием SWT. Я упаковываю приложение, используя скрипт ANT (фрагмент ниже).

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

Это производит один jar, который в Windows я могу просто дважды щелкнуть, чтобы запустить мой графический интерфейс. Недостатком является то, что мне пришлось явно упаковать пакет Windows SWT в мою банку.

Я хотел бы иметь возможность запускать мое приложение на других платформах (в первую очередь, Linux и OS X). Самый простой способ сделать это - создать jar-файлы для конкретной платформы, которые упаковывают соответствующие SWT-файлы в отдельные JAR-файлы.

Есть ли лучший способ сделать это? Можно ли создать один JAR, который будет работать на нескольких платформах?

Ответы [ 5 ]

29 голосов
/ 08 июля 2010

Я только что столкнулся с той же проблемой. Я еще не пробовал, но я планирую включить версии swt.jar для всех платформ и динамически загрузить правильную версию при запуске метода main.

ОБНОВЛЕНИЕ: Это сработало. build.xml включает все банки:

<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"/>

и мой main метод начинается с вызова этого:

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] Для тех, кто ищет «загрузчик классов jar-in-jar»: он включен в JDT Eclipse (Java IDE, построенная на Eclipse). Откройте org.eclipse.jdt.ui_*version_number*.jar с помощью архиватора, и вы найдете файл jar-in-jar-loader.zip внутри.

19 голосов
/ 26 апреля 2011

У меня есть рабочая реализация, на которую теперь ссылаются из SWT FAQ .

Этот подход теперь доступен для использования в качестве задачи ANT: SWTJar

[EDIT] SWTJar был обновлен для использования решения Алексея Романова, как описано выше.

build.xml

Сначала я создаю флягу, содержащую все мои классы приложений.

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

Затем я создаю банку, в которой содержится все следующее:

  • JAR-файлы
    • банка, которую я только что построил
    • Все банки SWT
  • Классы
    • Классы загрузчика классов "Jar-In-Jar"
    • Специальный класс загрузчика - см. Ниже

Вот фрагмент из 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

Этот класс загрузчика использует jar-in-jar-loader для создания ClassLoader, который загружает классы из двух jar.

  • Правильная банка SWT
  • Аппликация-баночка

Как только у нас появится этот загрузчик классов, мы сможем запустить реальный основной метод приложения, используя отражение.

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] Как указано выше, для тех, кто ищет «загрузчик классов jar-in-jar»: он включен в JDT Eclipse (Java IDE, построенная на Eclipse). Откройте org.eclipse.jdt.ui_ * version_number * .jar с помощью архиватора, и вы найдете внутри файл jar-in-jar-loader.zip. Я переименовал это в jar-in-jar-loader.jar.

intrace-ui.jar - это банка, которую я построил, используя описанный выше процесс. Вы должны быть в состоянии запустить этот единственный jar на любом из win32 / 64, linux32 / 64 и osx32 / 64.

[EDIT] На этот ответ теперь ссылаются из SWT FAQ .

10 голосов
/ 11 февраля 2012

если вы не хотите свернуть все в один файл jar и использовать jar-in-jar, вы также можете решить эту проблему, включив именованные SWT-файлы jar для каждой целевой платформы в каталог lib развернутого приложения:

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

и динамическую загрузку правильного значения во время выполнения путем проверки системных свойств Java "os.name" и "os.arch" во время выполнения с использованием System.getProperty(String name) для создания правильного имени файла jar.

Затем вы можете использовать немного капризное отражение (пуристы OO теперь отводят взгляд!), Вызвав нормально защищенный метод URLClassloader.addURL(URL url), чтобы добавить правильный jar-путь к системному загрузчику классов перед тем, как понадобится первый класс SWT.

Если вы можете выдержать запах кода, я привел здесь рабочий пример http://www.chrisnewland.com/select-correct-swt-jar-for-your-os-and-jvm-at-runtime-191

1 голос
/ 22 сентября 2013

Очень странно, что все ответы здесь просто советуют упаковать все SWT-JAR-файлы в один гигантский JAR-файл приложения. ИМХО, это строго против цели SWT: для каждой платформы есть библиотека SWT, поэтому предполагается, что она должна упаковывать только соответствующую библиотеку SWT для каждой платформы. Это очень легко сделать, просто определите 5 профилей сборки в вашей сборке ANT: win32, win64, linux32, linux64 и mac64 (вы можете также сделать mac32, но все современные Mac являются 64-битными).

В любом случае, если вы хотите иметь хорошую интеграцию приложений с ОС, вам придется делать некоторые специфичные для ОС вещи, и вы снова получаете профили сборки. Для настольных приложений неудобно иметь один пакет приложений для всех платформ как для разработчика, так и для его пользователей.

0 голосов
/ 25 апреля 2010

Заменить выделенный жирным шрифтом текст в src = "lib / org.eclipse.swt.win32.win32.x86_3.5.2.v3557f.jar " указанным в linux jar-файлом swt

...