Friday, May 30, 2008

 

Java without Java, part I

Java language has been there for a while, and various "alternative" implementations, proprietary and open source, flourished. Microsoft announced recently that it is dropping its Visual J++ implementation (which is part of Visual Studio 2005 but not 2008),  but in open source world, nothing could be "dropped" as such, so using Java without Sun's implementation was and is an option.

In this 2-part post, I am going to review some options to run relatively simple Java programs on Windows without using Sun's Java VM. First part is devoted to using gcj to compile and run Java code.

It is of course relatively straightforward to actually compile Java files (to .class files or natively), and Java front-end to Gnu Compiler Collection has been known for a while as "gcj"; it is much more demanding and challenging task to implement substantial piece of Java library; this activity has been ongoing as "classpath project". Standard GNU gcc distribution includes a slightly modified version of classpath, which is compiled into library "libgcj.a/.so"

Cygwin installation gladly offers gcj version 3.4.6, along with corresponding library. Furthermore, this program passes standard "Hello, World!" test:

public class Hello {
  static public void  main (String[] argv) {
    System.out.println ( "Hello, World!" );
  }
}

here is how to compile and run :

> /usr/bin/gcj --main=Hello Hello.java -o Hello
> ls -l Hello.exe
-rwxrwxrwx 1 user mkpasswd 6359272 May 30 16:45 Hello.exe*
> ./Hello.exe
Hello, World!

Unfortunately, any attempt to move substantially beyond "Hello, Word!" kind of tests result in run-time failure like this:

> ./Test.exe 
Exception in thread "main" java.lang.Error: Not implemented
   <<No stacktrace available>>

without even a clear indication of what is not implemented.

Worse, resulting Cygwin executables while being statically linked against "libgcj.a" (thus hefty 6MB for a trivial program) still depend on Cygwin DLL "cygwin1.dll"; compile option "-mno-cygwin" which is meant to generate executable independent of Cygwin DLL and does so for C and C++ files (so-called MinGW mode) fails even for aforementioned "Hello, World!" example (compilation succeeds, but executable crashes with no error messages).

Well, of course, version 3.4.6 is already quite old. What if one tries to download latest GCJ and compile under Cygwin?

In fact, one will quickly discover that Cygwin support is all but dropped from GCJ. You can build and install a compiler itself, but Java library is specifically marked as "not supported" in GCC configuration script:

  *-*-cygwin*)
    target_configdirs="$target_configdirs target-libtermcap target-winsup"
    noconfigdirs="$noconfigdirs target-gperf target-libgloss ${libgcj}"

If one tries to remove this restriction and force compilation, it proceeds for a while and then hangs indefinitely while trying to execute

gcj-dbtool -n classmap.db

Another option to try is MinGW distribution. However, they currently support gcc-3.4.5, with GCC 4 being in "alpha" testing. So this option is not yet viable.

Situation is not completely hopeless, though: there is some nice guy Mohan Embar who makes his own distributions of MinCW/GCJ, including latest versions. This kind of works, but not without some caveats.

For the purpose of this slightly-more-complex-than-Hello-World testing, I used the following Java program "Test.java":

import java.util.*;
import java.util.regex.*;
public class Test {
  static private Pattern p=Pattern.compile ("\\s*(\\d+)\\s*\\+\\s*(\\d+)\\s*$");
  static public void  main (String[] argv) {
    String arg = join ( Arrays.asList(argv), " " );
    Matcher m = p.matcher ( arg );
    if (m.lookingAt()) {
      int a = (new Integer(m.group(1))).intValue();
      int b = (new Integer(m.group(2))).intValue();
      System.out.println ( a + " + " + b + " = " + (a + b) );
    }
    else
      System.out.println ( "Could not parse!" );
  }
  public static String join(Collection s, String delimiter) {
    StringBuffer buffer = new StringBuffer();
    Iterator iter = s.iterator();
    while (iter.hasNext()) {
      buffer.append(iter.next());
      if (iter.hasNext())
 buffer.append(delimiter);
    }
    return buffer.toString();
  }
}

If you download this distribution, unzip it, set your $PATH and compile Test.exe as before, you will get the following failure trying to run it:

Exception in thread "main" java.lang.ExceptionInInitializerError
   at java.lang.Class.initializeClass(/datal/gcc/gcc/libjava/java/lang/Object.java:513)
   at gnu.java.util.regex.RE.getLocalizedMessage(/datal/gcc/gcc/libjava/classpath/gnu/java/util/regex/RE.java:262)
   at gnu.java.util.regex.RESyntax.(/datal/gcc/gcc/libjava/classpath/gnu/java/util/regex/RESyntax.java:345)
   at java.lang.Class.initializeClass(/datal/gcc/gcc/libjava/java/lang/Object.java:513)
   at java.util.regex.Pattern.(/datal/gcc/gcc/libjava/classpath/java/util/regex/Pattern.java:76)
   at java.util.regex.Pattern.compile(/datal/gcc/gcc/libjava/classpath/java/util/regex/Pattern.java:153)
   at java.util.regex.Pattern.compile(/datal/gcc/gcc/libjava/classpath/java/util/regex/Pattern.java:135)
   at Test.(ccZgp08ajx:0)
   at java.lang.Class.initializeClass(/datal/gcc/gcc/libjava/java/lang/Object.java:513)
   at Test.main(ccZgp08ajx:0)
Caused by: java.util.MissingResourceException: Bundle gnu/java/util/regex/MessagesBundle not found
   at java.util.ResourceBundle.getBundle(/datal/gcc/gcc/libjava/java/util/ResourceBundle.java:372)
   at java.util.ResourceBundle.getBundle(/datal/gcc/gcc/libjava/java/util/ResourceBundle.java:243)
   at gnu.java.util.regex.RE.(/datal/gcc/gcc/libjava/classpath/gnu/java/util/regex/RE.java:133)
   at java.lang.Class.initializeClass(/datal/gcc/gcc/libjava/java/lang/Object.java:513)
   ...9 more

The problem appears to be in static linking (that is, linking with libgcj.a rather than loading libgcj.dll runtime). Java is capable of loading classes dynamically, while static linking can only link-in objects (classes) which are referenced compile-time; hence any implementation which is using dynamic loading is doomed to fail. Therefore, for the time being "static" linking is not "officially" supported by GCJ team, and on Unix shared linking is the default. On Windows, however, most of GCJ distributions don't support DLL linking (Mohan Embar claims that his GCC/GCJ 3.4 distribution supports DLL linking, but it didn't work for me, and even if it did, it useless in this old version anyway)

In this case we are loading not a class, but so-called "resources", specifically message file with various messages associated with "regular expression" errors. If you have access to this file (which is included in any GCJ source distribution), you can link in this resource manually like this:

> gcj --resource gnu/java/util/regex/MessagesBundle.properties -c \
 path\gcc-version\libjava\classpath\resource\gnu\java\util\regex\MessagesBundle.properties \
 -o MessagesBundle.properties.o
> gcj --main=Test MessagesBundle.properties.o Test.java -o Test
> ./Test.exe 2+2
2 + 2 = 4

As you could see this actually works (!!!), though size of Test.exe is 38 Mbytes (!!!) (you can use utility "strip" to get it down to approx. 12M)

Unfortunately, attempt to compile and run any of Swing UI demos results in failure:

> .\DynamicTreeDemo.exe
Exception in thread "main" java.awt.AWTError: Cannot load AWT toolkit: gnu.java.awt.peer.gtk.GtkToolkit
   at java.awt.Toolkit.getDefaultToolkit(/datal/gcc/gcc/libjava/classpath/java/awt/Toolkit.java:544)
   at java.awt.EventQueue.invokeLater(/datal/gcc/gcc/libjava/classpath/java/awt/EventQueue.java:316)
   at javax.swing.SwingUtilities.invokeLater(/datal/gcc/gcc/libjava/classpath/javax/swing/SwingUtilities.java:950)
   at DynamicTreeDemo.main(D:/USERPR~1/IGNATI~1.PTC/LOCALS~1/Temp/ccCwbaaajx:0)
Caused by: java.lang.UnsatisfiedLinkError: gtkpeer: can't open the module
   at java.lang.Runtime._load(/datal/gcc/gcc/libjava/classpath/java/awt/event/WindowEvent.java:309)
   at java.lang.Runtime.loadLibrary(/datal/gcc/gcc/libjava/java/lang/Runtime.java:656)
   at java.lang.System.loadLibrary(/datal/gcc/gcc/libjava/java/lang/System.java:515)
   at gnu.java.awt.peer.gtk.GtkToolkit.(/datal/gcc/build/wingcc_build/i686-pc-mingw32/libjava/gnu/java/awt/peer/
gtk/GtkToolkit.java:145)
   at java.lang.Class.initializeClass(/datal/gcc/gcc/libjava/classpath/java/awt/event/WindowEvent.java:309)
   at java.lang.Class.forName(/datal/gcc/gcc/libjava/classpath/java/awt/event/WindowEvent.java:309)
   at java.awt.Toolkit.getDefaultToolkit(/datal/gcc/gcc/libjava/classpath/java/awt/Toolkit.java:561)
   ...3 more

Don't know if this is fixable with GCJ, but in fact it is possible to run some Swing UI demos "natively" on Windows; we will consider how to do this in Part II.

Labels: ,


Comments: Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?