Monday, May 18, 2009

Deep in Java: fiddling with Java bytecodes

Even though I heavily program in Java, I have not really felt the need to manipulate at bytecode level for most of the applications that I write. Even for MeTA Studio, which offers scripting interface via. BeanShell and Jython (at present), I used to just use these interpreter packages. Currently BeanShell is (and will probably will always be) the primary scripting interface to MeTA Studio. However, there has been no development on the BeanShell (www.beanshell.org) since long time and I needed some improvements to be put in it which were tailored towards MeTA Studio. So some time back, I forked the BeanShell code for MeTA Studio. Subsequently, I also fixed some minor bugs in the interpreter which affected functionality in MeTA Studio. Later, I also updated the ASM package to the current version, which is used by BeanShell to actually generate bytecodes. All this time, I never ever felt the need to fiddle around JVM bytecode specifications.

However, while finding a best way to call native libraries with in the programmable framework of MeTA Studio (what?? ... yes, but for reasons behind this you will have to wait for subsequent posts, when I get some stuff working) I stumbled upon a JDK incubation project called JNA. JNA, in fact, provides a very clean way to call native libraries without going through the pain of generating JNI stubs.

In fact, its pretty cool. For instance, you can call a C function sqrt like this:

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;

public interface CLibrary extends Library {
double sqrt(double v);
}

// and use it as:
CLibrary clib = (CLibrary) Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "m"),
CLibrary.class);
System.out.println(clib.sqrt(2));

Look ma, no JNI, pure Java!

Now, the above code worked fine if I compile using standard Java complier. But failed to run at all via the BeanShell interpreter with the following error:

java.lang.ClassFormatError: Illegal class modifiers in class CLibrary: 0x201

At first glance, I had absolutely no idea of what this meant. Long time ago, when I was in my graduation, I had fiddled around a bit with JVM specification in the hope to write a JVM for a 16-bit DOS environment. Needless to say, it never materialised ;-) But this some how made me check back the JVM class format again to look for possible solution. I also checked the BeanShell code that was generating the bytecodes, and essentially tracked it down to the following lines in bsh.ClassGeneratorUtil

public byte [] generateClass() {
int classMods = getASMModifiers( classModifiers ) | ACC_PUBLIC;
if ( isInterface )
classMods |= ACC_INTERFACE;
...

This too made not much sense, until I read the following in JVM class format specification:
"If the ACC_INTERFACE flag of this class file is set, its ACC_ABSTRACT flag must also be set (§2.13.1) and its ACC_PUBLIC flag may be set. Such a class file may not have any of the other flags in Table 4.1 set."

So the solution was simply to modify classMods |= ACC_INTERFACE | ACC_ABSTRACT; which worked great, except that I got another error:

java.lang.ClassFormatError: Interfaces must have java.lang.Object as superclass in class CLibrary

It appeared that BeanShell somehow did not do this correctly, and possibly the ASM interface would also have changed. I was not sure of the reason behind this, but I knew how to fix this :

if ( isInterface ) {
classMods |= ACC_INTERFACE | ACC_ABSTRACT;
try {
interfaces = new Class[]{Class.forName(superClassName.replace('/', '.'))};
} catch(Exception e) {
e.printStackTrace();
throw new UnsupportedOperationException("Super class " +
superClassName + " is not varifiable for interface : " + fqClassName);
}
superClassName = "java/lang/Object";
}
....

The above seems to do the trick, though am not very sure if this is the correct way to do it. And well in the end, I enjoyed doing this :-) .. and now calling native methods from with in MeTA Studio scripting environment is a lot easier and intuitive, well should I say its pretty cool ;-)

Note that all the source changes for the above are available from MeTA Studio SVN repository: http://code.google.com/p/metastudio/source/checkout

The above changes are not yet reflected in the binary package. But I hope to provide a full binary update too in coming days.

Have fun ;-)

No comments: