SWT is my preferred graphics library for client-side Java. The threading model is sensible, performance is good, set of built-in UI functions is comprehensive, and the native implementations have a good look and feel that fits in with the OS.
SWT is a native graphics library. It makes use of the operating system's existing GUI framework which is GTK on Linux and Unix, Cocoa on MacOS, and Win32 / Win64 calls on Windows. This means there is a different jar for each OS and each architecture (32 or 64 bit).
If you are building for multiple operating systems and/or architectures then you need a way to specify which SWT jar should be used.
One way to do this is to provide multiple launchers (Windows .bat files or shell scripts) which set the classpath accordingly in each one. You could also detect the OS/Arch during installation and deploy the correct launcher that way.
If you don't want to go down that route then there are 2 ways to dynamically select the correct SWT jar at runtime:
1) Use a custom classloader which inspects the OS and JVM. This is fine except you will need to specify the custom classloader in your call to the java binary:
java -Djava.system.class.loader=com.chrisnewland.classloader.CustomLoader -jar ChrisApp.jar
Your custom classloader is now responsible for loading everything (3rd party classes, resources, images ... and this might be more than you bargained for. You could also run in to the headache of remembering which classload is responsible for what classes and resources and end up with memory leaks.
2) The second option, presented here, uses a little bit of naughty reflection to call a protected method on the URLClassloader to insert the URL of your jar (can be a local file path or a remote URL) at runtime.
If you are happy to break strict object-oriented guidelines to simply solve a common problem then read on!
Let's say that your application is supported on 32 and 64 bit versions of Windows and Linux. You will need to bundle 4 SWT jars with your application. Name them as follows:
lib/swt_win_32.jar
lib/swt_win_64.jar
lib/swt_linux_32.jar
lib/swt_linux_64.jar
lib/swt_win_64.jar
lib/swt_linux_32.jar
lib/swt_linux_64.jar
Now here is some code to query the OS and architecture and return the jar filename according to the jar naming convention above:
public static String getArchFilename(String prefix)
{
return prefix + "_" + getOSName() + "_" + getArchName() + ".jar";
}
private static String getOSName()
{
String osNameProperty = System.getProperty("os.name");
if (osNameProperty == null)
{
throw new RuntimeException("os.name property is not set");
}
else
{
osNameProperty = osNameProperty.toLowerCase();
}
if (osNameProperty.contains("win"))
{
return "win";
}
else if (osNameProperty.contains("mac"))
{
return "osx";
}
else if (osNameProperty.contains("linux") || osNameProperty.contains("nix"))
{
return "linux";
}
else
{
throw new RuntimeException("Unknown OS name: " + osNameProperty);
}
}
private static String getArchName()
{
String osArch = System.getProperty("os.arch");
if (osArch != null && osArch.contains("64"))
{
return "64";
}
else
{
return "32";
}
}
{
return prefix + "_" + getOSName() + "_" + getArchName() + ".jar";
}
private static String getOSName()
{
String osNameProperty = System.getProperty("os.name");
if (osNameProperty == null)
{
throw new RuntimeException("os.name property is not set");
}
else
{
osNameProperty = osNameProperty.toLowerCase();
}
if (osNameProperty.contains("win"))
{
return "win";
}
else if (osNameProperty.contains("mac"))
{
return "osx";
}
else if (osNameProperty.contains("linux") || osNameProperty.contains("nix"))
{
return "linux";
}
else
{
throw new RuntimeException("Unknown OS name: " + osNameProperty);
}
}
private static String getArchName()
{
String osArch = System.getProperty("os.arch");
if (osArch != null && osArch.contains("64"))
{
return "64";
}
else
{
return "32";
}
}
Calling it with
String jarFilename = getArchFilename("lib/swt");
will get the correct jar filename at runtime for the system.
Now use the following reflection code to invoke URLClassloader.addURL(URL url) to add this jar to the classpath at runtime (make sure you invoke this code to add the jar before the first class that imports an SWT class is loaded!).
public static void addJarToClasspath(File jarFile)
{
try
{
URL url = jarFile.toURI().toURL();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<?> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", new Class<?>[] { URL.class });
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[] { url });
}
catch (Throwable t)
{
t.printStackTrace();
}
}
{
try
{
URL url = jarFile.toURI().toURL();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<?> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", new Class<?>[] { URL.class });
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[] { url });
}
catch (Throwable t)
{
t.printStackTrace();
}
}
So here is the calling code to dynamically select the correct SWT jar:
File swtJar = new File(getArchFilename("lib/swt"));
addJarToClasspath(swtJar);
addJarToClasspath(swtJar);
Greetings!
Great tip and site overall. I hope you won't mind a rather long-winded question, but I've been struggling over this problem for hours (8 of them), and your solution seems to be the best one around... Unfortunately, I am sad to say that I am confused as to how to apply your solution during development. I suspect this might be something that some other readers may also struggle with. I am happy to admit at this point that partly my problem is to do with not knowing how I would successfully describe the information that I am looking for so that I might have a decent chance of finding it!
For background, I am trying to develop a lightweight chat application both for my own learning purposes and perhaps even for use by real people. For this reason, I would enjoy being able to distribute a single executable .jar to all windows users (another for linux, another for mac, ...) that is bitness-independent. I started using SWT the other day because it sounded more appropriate and easier than the old GUI I had made from scratch with Swing. It would therefore be nice, as you know, to be able to pick which bitness of SWT jar to use based on the bitness of the jre running my .jar at runtime!
I have successfully used your code to both detect which SWT jar needs to be added to the classpath at runtime, and to add it as such. I've since played about and had some fun with reflection, so I think all this much I understand.
However, let's say I have a SWTApplication class which uses a bunch of imports:
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
...
Before I instantiate this class, I have another class called SWTPreloader which can do the job which you have described in your post.
I am developing in Eclipse, and my understanding is that those imports in SWTApplication won't resolve unless I already have a SWT jar on my project's classpath (although I would love to be corrected on this point!). That is, if I remove "<classpathentry kind="lib" path="swtlib/swt_win_64.jar"/>" or equivalent from my '.classpath' xml, none of the imports resolve (and why should they?) and my code won't compile/I get a bunch of Eclipse errors (as I would expect).
This may be something fundamental about java that I have missed, since I do not have any prior experience in dynamically loading classes. I guess then that my primary question is how can I find information about how to use classes in a sensible way which are not added to the classpath until runtime (either via command-line or by your method)? My secondary question is whether this is even how I would describe the information I am looking for?
I have tried all sorts of things and used all of my google-fu. I've seen loads of things from "bug reports" describing the issue of SWT lacking dynamic native library loading (apparently yet to be resolved), to solutions which suggest actually deleting the unused libraries at run-time!
I really don't want to give up yet and be forced to distribute a 32bit and 64bit version for each JRE architecture, not least because I feel like most users will have no idea which JRE/s they have, let alone which one of their perhaps multiple JREs is actually associated with jarfiles. Any solution that seems like too much hassle to the user (like downloading something first which tells them which distro they need) is something which I would like to avoid, as it reduces the chances of them actually wanting to use it. Besides, it seems like this sort of problem should have been solved by now!
Any help or advice you can give would be much appreciated, and I apologise if you already have an article which covers this topic (I have not managed to find it) or if this is a really entry-level problem in terms of Java which I have failed to approach until now.
Many thanks,
William
PS please feel free to contact me on [HIDDEN], since I suspect you won't be publishing this comment due to its longness.