2010-04-24 63 views
29

我已經使用SWT編寫了一個Java GUI。我使用ANT腳本打包應用程序(以下片段)。創建跨平臺的Java SWT應用程序

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

回答

18

我有一個工作實現,現在引用SWT FAQ

這種方法是現在作爲一個Ant任務使用:SWTJar

[編輯] 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」 類裝載器類
    • 一個特殊loader類 - 見下文

下面是從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-在-JAR加載器來創建它從兩個罐子加載類一個ClassLoader。

  • 正確的SWT 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-在-JAR的類加載器」:它包含在Eclipse的JDT(在Java IDE基於Eclipse)。用archiver打開org.eclipse.jdt.ui_ * version_number * .jar,你會在裏面找到一個文件jar-in-jar-loader.zip。我將其重命名爲jar-in-jar-loader.jar。

intrace-ui.jar - 這是我使用上述過程建立的罐子。你應該可以在任何win32/64,linux32/64和osx32/64上運行這個單獨的jar。

[編輯]此答案現在引用自SWT FAQ

+0

我找不到在您的網站上與您聯繫的方式,但我無法讓swtjar任務正常工作。我收到錯誤'D:\ My Dropbox \ Java \ kEllyIRClient \ swtjar-build.xml:16:存檔swtjar.jar不存在。 taskdef具有正確的類路徑路徑,因爲如果你改變它,它會抱怨。我不知道如何使這個工作,我寧願使用你的任務,而不是手動實現反射的東西。 – 2012-02-02 23:16:15

+0

你可以發佈一個鏈接到你的完整build.xml(或者至少是你有問題的目標?)。你能不能給我詳細的關於磁盤上文件/文件夾的佈局?讓我知道你的電子郵件地址,我會盡力幫助你。 – mchr 2012-02-03 13:06:05

+0

這是我發佈的問題:http://stackoverflow.com/questions/9107509/unable-to-make-ant-script-with-swtjar/9112334#comment11467319_9112334但自那時起腳本發生了變化。我的電子郵件地址是[email protected] – 2012-02-03 13:31:26

0

替換SRC加粗選定的文本= 「LIB/org.eclipse.swt.win32.win32.x86_3.5.2.v3557f.jar」 使用Linux指定的SWT JAR文件

+0

這是我想到的方式,但這讓我留下了平臺特定的罐子。我很樂意能夠生產一個單一的跨平臺的罐子,但我懷疑這可能是不可能的。 – mchr 2010-04-24 23:23:07

+0

不幸的是,這是不可能的 – user325141 2010-04-25 17:08:38

+0

這就是我懷疑的。哦,我可以用一個二進制平臺生活 - 我仍然只需要編寫一次代碼:)(雖然我知道我需要在所有平臺上測試) – mchr 2010-04-26 16:34:39

26

我剛遇到同樣的問題。我還沒有嘗試過,但我計劃在所有平臺中包含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-在-JAR的類加載器」:它包含在Eclipse的JDT (構建在Eclipse上的Java IDE)。用存檔器打開org.eclipse.jdt.ui_*version_number*.jar,您會在裏面找到一個文件jar-in-jar-loader.zip

+0

請讓我知道,如果你得到這個工作。 – mchr 2010-07-08 14:29:20

+0

我做到了!查看更新。 – 2010-07-09 08:30:33

+0

我們在哪裏可以找到「Jar-in-Jar」類加載器? – 2010-07-23 14:06:43

9

,如果你不看一切捲起成一個單一的jar文件和使用的jar-在-JAR,那麼你也可以通過在已部署的應用程序的lib目錄中爲每個目標平臺命名爲SWT罐子解決這個問題:

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

,並通過使用System.getProperty(String name)創建正確的罐子的文件名檢查Java系統屬性"os.name""os.arch"和在運行時動態地在運行時加載正確的。

然後可以使用反射的略微調皮位(OO純粹主義者,現在看遠!)通過調用正常保護方法URLClassloader.addURL(URL url)需要第一SWT課前添加正確的罐子系統類加載器的類路徑。

如果你能忍受的代碼氣味,我已經把這裏http://www.chrisnewland.com/select-correct-swt-jar-for-your-os-and-jvm-at-runtime-191

1

這是非常奇怪的是,所有的答案在這裏只是提醒所有的SWT JAR文件打包成巨單應用程序JAR文件的工作示例。恕我直言,這嚴格違背了SWT的目的:每個平臺都有一個SWT庫,所以它應該只爲每個平臺打包適當的SWT庫。這很容易做到,只需在ANT構建中定義5個構建配置文件:win32,win64,linux32,linux64和mac64(您也可以做mac32,但所有現代Mac都是64位)。無論如何,如果你想在OS中有很好的應用程序集成,那麼你將不得不做一些特定於操作系統的事情,並且再次使用構建配置文件。對於桌面應用程序,爲開發人員和用戶使用一個適用於所有平臺的應用程序包是不方便的。

+0

SWT告訴我Java已經爲垃圾箱準備好了。我作爲開發人員的主要賣點是一次寫入,隨處運行。這個承諾從來沒有真正落地過,雖然我爲編寫Java而獲得了體面的支付,但我正在積極尋找要實現的目標;我厭倦了我不得不忍受的部分我的應用程序。 :'( – 2017-05-25 21:18:54