Since its introduction in 1996, Java has proven a useful tool, filling a gap in the computer language market in terms of productivity and object orientation. After several years of working with it, the only defect Ive found is that if you start developing in Java, youre forced to keep developing in Java. Unless you work in low-level C or C++ through the Java Native Interface (JNI), interacting with the outside world in a different language becomes complicated. .NETs Common Language Runtime (CLR) simplifies the issue to a degree, but .NET provides no help when you need to develop or reuse complex Java code. To bridge the gap, Ive developed JavaBridge, a tool that allows you to use an existing Java component from a .NET environment
For C# purposes JavaBridge is a .NET package that will come into scope as soon as you provide a using BridgeToJava statement for your C# class. The JavaBridge package is compiled into VMLoader.DLL, which you must reference either by adding it to the Visual Studio references list (if youre compiling from Visual Studio IDE) or by specifying /r:VMLoader.dll on the csc C# compiler command line.
To illustrate the benefits and ease-of-use of JavaBridge, Ive provided a small C# example application, cstester. Ill look first at the example, then Ill examine the implementation details for JavaBridge.
cstester accepts the name of a Java class as a command-line argument, then performs the following tasks:
Note that introspection in Java has roughly the same meaning as reflection in .NET. It will list the available Java methods for the loaded class and make them available to the .NET side.
using BridgeToJava ;
This instruction makes the JavaBridge classes and methods visible and usable from the C# scope.
JavaBridge bridge = new JavaBridge() ; JavaTimer timer = new JavaTimer() ;
The call to the JavaBridge constructor initializes the JavaBridge environment. JavaTimer, a tiny, straightforward class used to measure time spent inside JavaBridge methods, is also initialized.
bridge.init
( "C:\\jdk1.2.2\\jre\\bin\\classic
\\jvm.dll" , null ) ;
cstester next calls the init method to load and initialize the JVM. The init method accepts two arguments: the location of the JVM DLL and an optional ClassPath which may be null. When not null this parameter contains a list of class locations following the same syntax as the standard Java ClassPath or cp string.
JavaClass javaClass = bridge.loadClass(className) ;
cstester then loads the class specified in argument 1 of the main args entry into the Java environment.
ArrayList methods = javaClass.introspectMethods() ;
The loaded class methods are introspected and made available inside a .NET array list for later usage.
foreach ( JavaMethod method in methods )
{
Console.WriteLine
("method : {0} " , method.ToString() ) ;
}
Finally, cstester populates the introspected Java methods on the screen, using the Java-side toString() method call. Figure 1 shows the resulting display on a standard Windows console.
This small example demonstrates how easy it is to instantiate a Java method from a .NET environment language (C#, VB, etc.). Ive kept the example simple, but JavaBridge places no limits on the Java methods you can call or the Java classes you can instantiate from the .NET side.
When using a .NET environment, you may use C#, J#, or even VB. All of these languages are potential candidates, since they all generate CLR intermediate representation.
However, for the current purpose, youll need to link with the Java JVM through the JNI and generate a library that will contain both managed MSIL entries for net callers and standard flat-C-compliant entry points for non-managed legacy or JNI interfaces. This DLL object will be used by both legacy code and .NET managed C# loaders.
The need to build this type of complex library reduces the potential language candidates to one: C++ .NET. This is the only .NET language that is able to precisely target both managed and unmanaged code in the same library.
Once a project language is defined, there are additional tools that help when it comes to the building and debugging stages. See sidebar, .NET Tools.
The VMLOADER design is straightforward and consists of a JNI wrapping an unmanaged class (VirtualJavaMachine) and complementary .NET managed classes located inside the BridgeToJava package providing .NET access facilities to Java objects like JavaClass, JavaMethod, and JavaString.
VirtualJavaMachine represents the bridging pattern classes used to interact between the .NET environment and the JVM environment. This class is unmanaged and deals with specific JVM actions like loading the JVM DLL or wrapping all used JNI functions. In order to provide a clean design, the unmanaged VirtualJavaMachine class is only manipulated behind the scenes by the managed JavaBridge class. JavaBridge is the initial class to instantiate in order to make a JavaBridge facility ready to use. The next JavaBridge instance is there to make pure Java objects visible to the .NET side.
The next step is to load the JVM you have chosen to use, which may be any JDK 1.2 compliant JVM for Windows (Sun, IBM, or BEA JVMs are freely available on the Internet). The JavaBridge.init method is provided to make the JVM environment ready to use. Finally the JavaBridge class will also be useful to initialize a given Java class using the JavaBridge.loadClass method, which takes the ClassName as a parameter and returns a .NET wrapping JavaClass instance ready to use.
JavaClass provides the primary facilities for JavaClass access from the .NET environment. The class-loading step provides the previously described JavaBridge instance. Each used JVM JavaClass is mapped on the .NET side through a C++ Managed JavaClass instance.
JavaObject provides class instantiation semantics using the JNI API. This class has two constructors:
JavaMethod is provided as a Java method object peer, which is used to call Java methods from the .NET side.
JavaString inherits JavaClass and simply implements an interesting utility class that may be useful when conversion between the .NET string object instances and Java string object are necessary. The conversion method facilities are provided as a static method in order to avoid a JavaString object allocation when Java String/.NET String conversion is requested. The JavaString class also illustrates the privileged way of populating a given Java class to the .NET side: you need to implement your own C++ class inside your C++ namespace that inherits JavaClass from the BridgeToJava namespace. Other minor classes are also provided and used internally by the major classes described.
The #include <jni.h> is used inside the VirtualJvm.h prototype. The JVM class is an unmanaged front-end wrapping class that is used for bridging between the Java Virtual Machine context and the .NET world. This class follows the very classical bridging pattern between two isolated environments:
From a developers standpoint, the JVM is obviously designed for operating system portability and suffers from a lack of cross language interoperability, whereas .NET is just the opposite: a very good cross-language environment design. I have some doubts on the operating system portability of the .NET system outside Windows and the market acceptance of .NET by the UNIX community. It will take some time before the .NET environment will have the same UNIX coverage as the Java environment today, although even GNU is trying to develop a free .NET implementation ... but thats another story.
The content of the jni.h prototype is perfectly acceptable at first sight, since it just provides a number of Unmanaged pointer types in order to help C++ compiler encapsulation. The fact that the prototype does not mention the .NET decorated Managed C++ keyword __nogc, used to declare an unmanaged type, does not hurt: this is the default assumed by the .NET C++ compiler for class declarations.
So the declares of class and pointers to class inside the jni.h prototype are all considered unmanaged when processed by the .NET C++ compiler, and thats what we expect. The problem comes with the following declares found inside the jni.h prototype:
struct _jfieldID; typedef struct _jfieldID *jfieldID; struct _jmethodID; typedef struct _jmethodID *jmethodID;
At first sight the declare looks okay: struct should be considered as Unmanaged by default and the associated typedef as an unmanaged pointer to an unmanaged object. But when the _jmethodID comes into scope at runtime, the TypeLoadException shown in Figure 2 is received inside the C# test program.
This indicates that the _jmethodID struct just confuses the system when it comes into scope. Even if we replace the struct declare by a __nogc struct (which should be the default when not specified) the exception still remains. The only workaround that I found to this problem was to replace the original SUN JNI declares with the following ones for C++ context:
class _metadata {};
class _jfieldID : public _metadata {} ;
typedef _jfieldID *jfieldID;
class _jmethodID : public _metadata {} ;
typedef _jmethodID *jmethodID;
After I did that, my first question was why the JNI implementers did not provide it that way? Both ways of defining jfield and jmethodID as a Java _metadata context object are cleaner C++ wrapping implementations: since jfield and jmethodID pointers cant be manipulated outside the Java Virtual Machine, they should rather be considered as _metadata handles to be passed back and forth between JVM and JNI context instead.
I then tried using ILDASM on the generated introspectMethods code to compare when the generated IL works and when it doesnt: generated IL code production was the same in both cases. If you want to get through the problem, you must use the jni.h. I have modified or provide the same tiny modifications inside yours. To avoid confusion, I renamed my modified prototype into jninet.h and combined it with the other JavaBridge project prototypes files.
The main marshalling activity inside the VM loader will first consist of converting from managed callers .NET data types (C#, VB,....) into JVM JNI unmanaged front-end expected parameters. Again there are multiple ways of implementing the marshalling strategy using either marshalling .NET attributes or low-level Interop marshalling. I wrote a dedicated Unmanaged C++ class to deal with the marshalling activity from managed code to unmanaged Java VM entries.
This way of dealing with marshalling provides the following advantages over using standard marshalling attributes:
The marshalling class is named QuickMarshaller and has been implemented inside the marshaller.h prototype of the project.
Here is a simple code snapshot showing usage of the QuickMarshaller class for calling an unmanaged Java VmLoader instance class.
QuickMarshaller marshall(2) ;
// Marshall for 2 parameters requested
// Load Java VM of course inside
// Unmanaged space
_pJvm->loadVM
( marshall.string2pChar(dllLocation) ,
marshall.string2pChar(classPath) ) ;
My conclusion when I finished this tiny technical project was that I could not figure out how it would have been possible to encapsulate both worlds without C++ help. Both C# and Java were inadequate and will remain inadequate for this kind of task. Each time youre faced with tiny technical low-level projects, either C or C++ is the right answer, specifically for heterogeneous bridging. Next to this, Visual C++ encapsulation to the .NET framework is very impressive. The obvious cost is an increasing C++ syntax complexity, which reduces code readability when compared to either a C# or Java syntax. Finally, one of the biggest advantages of the .NET framework is really the language independence provided by the environment, which allows you to use the language you want for a given project.
The main interest in using JavaBridge consists in the capacity of reducing the gap existing between two technologically similar environments that have not been naturally provided with interoperability tools.
Nevertheless, at some time youll be faced with an existing Java JAR library, providing some functionality that youll need, which is not available from .NET natively, and at that time youll need bridging. Bridging facilities are nice to have since bridging also enhances a developers freedom in choosing the best-suited language for a given project
In order to keep the article simple to understand, not all of the functionalities that you may expect inside a full-featured bridge have been implemented in the provided code. All the basic fundamental tools are there, but you may need to add some extra features of your own.
Although the JavaTimer classes show that performances on the cstester sample are acceptable, they are not significant, since cstester is a tiny C# program. Due to JVM and .NET existing infrastructures, .NET to Java bridging will suffer the two following drawbacks:
Java
Listed below are the three references that I consider to be the fundamentals for a good understanding of both the Java language and the Java Virtual Machine architecture:
[1] <www.sysinternal.com>