我使用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?
答案 0 :(得分:29)
我遇到了同样的问题。我还没有尝试过,但我计划为所有平台添加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
。
答案 1 :(得分:19)
我有一个工作实现,现在引用SWT FAQ。
此方法现在可用作ANT任务:SWTJar
[编辑] SWTJar现已更新为使用Alexey Romanov的解决方案,如上所述。
首先,我构建一个包含所有应用程序类的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:
以下是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>
这个loader类使用jar-in-jar-loader创建一个ClassLoader,它从两个jar中加载类。
一旦有了这个类加载器,我们就可以使用反射启动实际的应用程序main方法。
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。
答案 2 :(得分:10)
如果您不想将所有内容汇总到一个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
并在运行时通过使用"os.name"
在运行时检查Java系统属性"os.arch"
和System.getProperty(String name)
来动态加载正确的jar文件名。
然后你可以通过调用通常受保护的方法URLClassloader.addURL(URL url)
来使用稍微顽皮的反射(OO纯粹主义者现在移开!),以便在需要第一个SWT类之前将正确的jar添加到系统类加载器的类路径中。
如果你能忍受代码味道,我在这里提出了一个有效的例子http://www.chrisnewland.com/select-correct-swt-jar-for-your-os-and-jvm-at-runtime-191
答案 3 :(得分:1)
非常奇怪的是,这里的所有答案都建议将所有SWT JAR打包成一个巨大的应用程序JAR文件。恕我直言,这严重违背了SWT的目的:每个平台都有一个SWT库,因此它应该只为每个平台打包适当的SWT库。这很容易做到,只需在您的ANT版本中定义5个构建配置文件:win32,win64,linux32,linux64和mac64(您也可以执行mac32,但所有现代Mac都是64位)。
无论如何,如果您希望将良好的应用程序集成到操作系统中,那么您必须执行一些特定于操作系统的操作,并且您再次使用构建配置文件。对于桌面应用程序,为开发人员和用户提供适用于所有平台的应用程序包是不方便的。
答案 4 :(得分:0)
用linux指定的swt jar-file替换src =“lib / org.eclipse.swt.win32.win32.x86_3.5.2.v3557f.jar ”中的粗体选定文本