创建跨平台 Java SWT 应用程序

Posted

技术标签:

【中文标题】创建跨平台 Java SWT 应用程序【英文标题】:Create cross platform Java SWT Application 【发布时间】:2011-02-11 23:15:40 【问题描述】:

我使用 SWT 编写了一个 Java GUI。我使用 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 上我可以双击运行我的 GUI。缺点是我必须将 Windows SWT 包显式打包到我的 jar 中。

我希望能够在其他平台(主要是 Linux 和 OS X)上运行我的应用程序。最简单的方法是创建平台特定的 jar,将相应的 SWT 文件打包到单独的 JAR 中。

有没有更好的方法来做到这一点?是否可以创建一个可以在多个平台上运行的 JAR?

【问题讨论】:

【参考方案1】:

将 src="lib/org.eclipse.swt.win32.win32.x86_3.5.2.v3557f.jar" 中的粗体选定文本替换为 linux 指定的 swt jar 文件

【讨论】:

这是我的想法,但这给我留下了平台特定的 jars。我希望能够制作一个跨平台的 jar,但我怀疑这可能是不可能的。 很遗憾,这是不可能的 这就是我所怀疑的。哦,好吧,我可以在每个平台上使用一个二进制文件——我仍然只需要编写一次代码 :)(尽管我知道我需要在所有平台上进行测试)【参考方案2】:

我刚刚遇到了同样的问题。我还没有尝试过,但我计划包含所有平台的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);
    

[编辑] 对于那些寻找“jar-in-jar 类加载器”的人:它包含在 Eclipse 的 JDT(基于 Eclipse 的 Java IDE)中。用归档器打开org.eclipse.jdt.ui_*version_number*.jar,你会发现里面有一个文件jar-in-jar-loader.zip

【讨论】:

在哪里可以找到“Jar-in-Jar”类加载器? 或 Eclipse 安装中的最新版本。 用归档器打开org.eclipse.jdt.ui_*version_number*.jar,里面应该有文件jar-in-jar-loader.zip。 (我这里安装了 3.5.1,但我预计 3.6 应该不会有所不同。) 我试过你的代码,但我得到了这个异常:java.net.MalformedURLException: unknown protocol: rsrc at java.net.URL.(URL.java:574) Done。 ..你能帮忙解决什么问题吗? @Alexey - 您可能有兴趣看到我现在已将您的解决方案打包到一个 ant 任务中 - mchr3k.github.com/swtjar【参考方案3】:

我有一个工作实现,现在从 SWT FAQ 引用。

此方法现在可用作 ANT 任务:SWTJar

[EDIT] SWTJar 现已更新为使用上述 Alexey Romanov 的解决方案。

build.xml

首先我构建了一个包含我所有应用程序类的 jar。

<!-- 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 来包含以下所有内容:

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 jar 应用程序包装 jar

一旦我们有了这个类加载器,我们就可以使用反射启动实际的应用程序主方法。

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;
  

[编辑] 如上所述,对于那些寻找“jar-in-jar 类加载器”的人:它包含在 Eclipse 的 JDT(基于 Eclipse 的 Java IDE)中。使用归档器打开 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 上运行这个 jar。

[编辑] 这个答案现在引用自 SWT FAQ。

【讨论】:

我找不到在您的网站上与您联系的方式,但我无法让您的 swtjar 任务正常工作。我收到错误D:\My Dropbox\Java\kEllyIRClient\swtjar-build.xml:16: The archive swtjar.jar doesn't exist。 taskdef 具有正确的类路径路径,因为如果您更改它,它会抱怨。我不知道如何让它工作,我更愿意使用你的任务来做,而不是手动实现反射的东西。 您能否发布一个指向您的完整 build.xml 的链接(或者至少是您遇到问题的目标?)。您还可以给我有关磁盘上文件/文件夹布局的详细信息吗?让我知道您的电子邮件地址,我会尽力直接帮助您。 这是我发布的问题:***.com/questions/9107509/… 但此后脚本已更改。我的电子邮件是 rahatarmanahmed@gmail.com 我已经发布了关于您的 SO 问题的答案。 抱歉,无法获得这项工作。我正在使用这里的分步说明:code.google.com/a/eclipselabs.org/p/timeme/wiki/SWT 但我在线程“main”java.lang.NoClassDefFoundError: org/eclipse/jface/window/ApplicationWindow 中遇到异常【参考方案4】:

如果您不希望将所有内容汇总到单个 jar 文件中并使用 jar-in-jar,那么您还可以通过在已部署应用程序的 lib 目录中包含每个目标平台的命名 SWT jar 来解决此问题:

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 文件名来动态加载正确的文件名。

然后,您可以通过调用通常受保护的方法 URLClassloader.addURL(URL url) 在需要第一个 SWT 类之前将正确的 jar 添加到系统类加载器的类路径中,从而使用稍微有点调皮的反射(OO 纯粹主义者现在看走眼了!)。

如果你能忍受代码的味道,我在这里放了一个工作示例http://www.chrisnewland.com/select-correct-swt-jar-for-your-os-and-jvm-at-runtime-191

【讨论】:

【参考方案5】:

很奇怪,这里的所有答案都只是建议将所有 SWT JAR 打包到一个巨大的应用程序 JAR 文件中。恕我直言,这完全违背了 SWT 的目的:每个平台都有一个 SWT 库,因此应该只为每个平台打包适当的 SWT 库。这很容易做到,只需在您的 ANT 构建中定义 5 个构建配置文件:win32、win64、linux32、linux64 和 mac64(您也可以使用 mac32,但所有现代 Mac 都是 64 位的)。

无论如何,如果您想将应用程序很好地集成到操作系统中,那么您将不得不做一些特定于操作系统的事情,并且您再次使用构建配置文件。对于桌面应用程序,为开发人员和他的用户提供一个适用于所有平台的应用程序包是很不方便的。

【讨论】:

以上是关于创建跨平台 Java SWT 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

java如何内置visio控件

java中awt与swt的区别是啥?

Java桌面应用程序设计:SWT简介

如何使用在所有平台上运行的 SWT 创建可执行 JAR?

JAVA语言程序设计教程的目录

如何在 xp 和 windows 7 上运行 SWT 应用程序