Click here to Skip to main content
15,867,686 members
Articles / Containers / Virtual Machine
Article

Espresso- Java & .NET Native Interoperability - Part I

Rate me:
Please Sign up or sign in to vote.
4.52/5 (13 votes)
7 Jun 2006CPOL15 min read 163.8K   3.3K   60   31
This article describes a high-performing interoperability solution between the Java platform and the .NET Framework. The suggested solution does not replace the Java Virtual Machine or the .NET Framework runtime, instead, your JVM or .NET are each hosted within the opposing runtime environment

Preface

This article is first in a series of articles, that will show you how to fully integrate Java and .NET.

The articles are as follows:

  • Part I: Introduction to Java & .NET interoperability and the suggested solution
  • Part II: Implement .NET proxy to the Java classes
  • Part III: Using Attributes to extend the Java API solution
  • Part IV: Java to .NET API calls
  • Part V: Implement Java proxy to the .NET classes
  • Part VI: Adding Annotations to extend the .NET API solution

Introduction

Java has been with us for more than ten years now and has made phenomenal inroads into the world of system, business, Internet and educational programming. In the last few years, a new and competing technology was introduced by Microsoft; this technology is known today as the .NET Framework. This article is not intended to deal with each of the platforms, the purpose of this article is to showcase my experience for making C# co-exist with Java and vice versa in a native manner.

This article is based on a project with the code name Espresso, which was developed by Reflective Software. On their Web site, you will be able to find updated information and build for this project.

Background

As mentioned before, Java and .NET have been around for several years. During this time, there were many articles and solutions attempting to solve the interoperability issues between these two frameworks. The major problem with these solutions were that they tried to integrate the two frameworks using external communication. This means that each framework simultaneously runs as a separate process on the same machine, or as a process on different machines. The communication used employs several technologies, such as Web Services, .NET remoting etc. The cardinal problem with this type of communication is that it's very slow and network dependent.

The suggested solution will show how the two frameworks can live together in the same process and communicate seamlessly with each other.

This article describes a high-performing interoperability solution between the Java platform and the .NET Framework. The suggested solution does not replace the Java Virtual Machine or the .NET Framework runtime, instead, your JVM or .NET are each hosted within the opposing runtime environment, ensuring that vendor-specific VM optimizations are preserved.

"Espresso" objectives are:

  • Run on any combination of .NET runtime (1.0+) and Java Virtual Machine (1.2+).
  • Allow full reuse of any Java library from a .NET environment and vice versa, working exclusively at the API level and avoiding bytecode translation of the actual implementation.
  • Provide optimal performance, running the JVM and .NET under the same process and avoiding network or IPC costs.

The initial code was taken from the Caffeine open source, which is a one-way API invocation from .NET to Java. This article and the following ones, describe a full solution, that will allow invocation of APIs from .NET to Java and vice versa and seamless integration between the two platforms.

In one of the projects we developed for my company, Reflective Software, we had an interoperability requirement between Java and .NET. We tried using Web Services, but it was just not good enough. We used Caffeine and extended it, so we will be able to make Java call .NET and vice versa. These articles contain the phases to accomplish the project and the source code.

Part I

In this first article, I will explain the Espresso solution which will be the base for the other articles.
Must of the information of this first part article is based on the Caffeine project, so if you are familiar with this problem you can just jump to the next part, which is about extending the Caffeine project to new levels.

Runtime Architecture

The Java Native Interface (JNI) is a public interface that all implementors of the Java Virtual Machine must provide. JNI is a set of native functions contained in the JVM native library (jvm.dll, or libjvm.so depending on the architecture) that allows calling native functions from Java as well as native code creating a JVM and invoking Java classes.

Espresso employs JNI technology to host a Java Virtual Machine (JVM) under a .NET runtime. As the JVM runs under the same OS process as the .NET runtime, there are no IPC costs associated with this solution. The next figure illustrates the runtime architecture of Espresso. The blocks in grey are provided by Espresso and the blocks in yellow are provided by the JVM.

Image 1

The bridge.dll is C++ code that exposes the function that will be used by the JNI.NET.Bridge.dll. The JNI.NET.Bridge.dll is a group of classes that enable the .NET Framework to call Java API using P/Invoke. The .NET classes are some classes that wrap the Caffeine API in order to give it a more attractive and OO look. In these articles, we will not display this library because I took a different approach, the proxy one, which is different from the Caffeine approach.

bridge.dll

If you know JNI, you will know that the JNIEnv interface pointer is at its core. JNIEnv contains a table of JNI functions passed as an argument to each native method. The main advantage of the JNIEnv interface pointer design is that JNI implementations do not need to link to a particular version of the Java Virtual Machine. Binary compatibility is the main design criteria for JNI, and JNIEnv obeys this.

Accessing an interface pointer from C#, although possible, is very cumbersome. For this reason, Espresso provides the bridge library. Another reason for the bridge library is that JNIEnv uses thread-local storage, which means that each native thread must be attached to a Java thread in order to ensure correctness. The Espresso bridge library flattens the JNIEnv interface and ensures that each native thread is correctly attached to a Java thread.

FindClass function is as follows:

Java
jclass
FindClass(const char *name)
{
    JNIEnv *env = GetEnv();
    return (*env)->FindClass(env, name);
}

For those who know the C++ bindings of JNI, the construct above will be familiar. We are flattening the structure interface, removing JNIEnv from the function signature. Instead, the first line in the FindClass function calls the GetEnv() function. GetEnv() returns the JNIEnv interface pointer attached to the current thread, and if the current native thread is not attached to a thread-local storage, it attaches the thread and returns a JNIEnv interface pointer. The second line makes the actual call to the JNI FindClass function, via the JNIEnv interface pointer.

The bridge library contains 1:1 flattened versions of all the functions contained in the JNIEnv interface pointer structure. Additionally, the bridge library contains the GetEnv() function described above, as well as functions to create and destroy a Java VM.

Creating the JVM

The bridge library provides two convenience functions to create a Java VM:

Java
int CreateJavaVMDLL(const char *dllName, 
               JavaVMOption * options, 
               int nOptions);

int CreateJavaVMAnon(JavaVMOption * options, 
                int nOptions);

CreateJavaVMDLL allows to specify the shared library containing the Java VM which should be loaded at runtime. The bridge library is not linked to any particular Java VM implementation. Instead, it uses dynamic library location and loading (LoadLibrary/GetProcAddress), based on the dllName passed to the CreateJavaVMDLL function. CreateJavaVMAnon does not take a dllName, and defaults dllName to jvm.dll on Win32. This seems to be a valid default for 90% of the setups. Most applications will not need to specify the JVM library, but in case the default does not work, JNI.NET.Bridge provides the ability to configure the name of the DLL which contains the JVM (see Section Configuration).

Passing Arguments

Most of the JNIEnv functions have three forms:

Java
jchar (JNICALL *CallNonvirtualCharMethod)
  (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
jchar (JNICALL *CallNonvirtualCharMethodV)
  (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
   va_list args);
jchar (JNICALL *CallNonvirtualCharMethodA)
  (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID,
   jvalue *args);

JNIEnv functions can take either an ellipsis, a variable argument list, or a jvalue union. P/Invoke does not allow ellipsis nor variable argument lists, as type marshalling would not be possible, so the bridge library only exhibits the function form using the jvalue union.

JNI.NET.Bridge.dll

This library is the .NET counterpart of bridge.dll. In this library, you will find a class that wraps the JNI calls in a more OO manner and enables the .NET developer to use the Java API in a much more elegant way than just calling JNI API.

Binding

All the JNI types have a .NET counterpart:

  • JObject, encapsulates jobject, and is the base type for all .NET types
  • JClass, encapsulates the JNI jclass type
  • JMethod, encapsulates jmethodID
  • JField, encapsulates jfieldID
  • JArray, JBooleanArray, JByteArray, JCharArray, JDoubleArray, JFloatArray, JIntArray, JLongArray, JObjectArray, and JShortArray, which encapsulate, respectively, jarray, jbooleanarray, jbytearray, jchararray, jdoublearray, jfloatarray, jintarray, jlongarray, jobjectarray and jshortarray
  • JThrowable, encapsulates jthrowable
  • JString, encapsulates jstring

JNI.NET.Bridge provides a number of additional types, to simplify the usage of the bindings:

  • JMember, base class for JConstructor, JMethod, and JField
  • JConstructor, special version of JMethod

All JNIEnv functions have been encapsulated in the corresponding .NET type. For example, the bridge function:

Java
jint MonitorExit(jobject obj);

is encapsulated by:

Java
public class JObject 
{
   // ...
    
   [DllImport(JNIEnv.DLL_JAVA)]
   static extern int MonitorExit (IntPtr obj);

   public void MonitorExit () { // ... }
    
   // ...
}

Passing Arguments

As mentioned earlier, the bridge library only exposes the functions using a jvalue union, but not the ones using a variable argument list, nor ellipsed parameters. JValue is a C# struct that can be marshalled to a C union, by using the FieldOffset attribute. The original jvalue union, defined in jni.h, looks like:

C++
typedef union jvalue {
    jboolean z;
    jbyte    b;
    jchar    c;
    jshort   s;
    jint     i;
    jlong    j;
    jfloat   f;
    jdouble  d;
    jobject  l;
} jvalue;

The C# struct that simulates this union looks like:

C#
[StructLayout (LayoutKind.Explicit)]
public struct JValue
{
    [FieldOffset (0)] bool z;
    [FieldOffset (0)] byte b;
    [FieldOffset (0)] char c;
    [FieldOffset (0)] short s;
    [FieldOffset (0)] int i;
    [FieldOffset (0)] long j;
    [FieldOffset (0)] float f;
    [FieldOffset (0)] double d;
    [FieldOffset (0)] IntPtr l;
    
    // ...
}

Instantiating a Java Object

All Java objects are wrapped by JObject. JObject is a .NET type that keeps a reference (Handle property) to the Java object. In other words, to instantiate a Java object, we must create an instance of a JObject.

There are two ways to create an instance of a JObject: by calling the JObject constructor and providing a JConstructor, or by calling the instance method NewInstance in JClass. Let's concentrate on the first option, which is the generic way to create an instance. The steps we must follow to create an object are:

  • Get an instance of the JClass object corresponding to the Java type we are interested in.
  • Out of the JClass instance, find a suitable constructor, and obtain an instance of JConstructor.
  • Finally, call the constructor of JObject and pass the JConstructor instance obtained previously from JClass.

In order to find the class, we invoke the static method ForName in JClass, providing the fully qualified name of the Java type we require to be loaded. Under the scenes, ForName calls the FindClass function in the JNIEnv interface pointer.

Once we have a JClass object, which corresponds to a Java type, we must obtain a constructor for that type. We do this by calling the GetConstructor(string sig) method in JClass. Note that the signature follows the JNI naming convention (you can use the Java tool javap for obtaining signatures). An example of use would be:

Java
JClass clazz = JClass.ForName ("Prog");
JConstructor ctr = clazz.GetConstructor ("()V");

In this example, we are obtaining the no args constructor for the Java class, Prog. For the no-args constructor, we could have also called the convenience method GetConstructor(), equivalent to calling GetConstructor("()V");.

Once we have a constructor, we can call the JObject constructor:

Java
JObject obj = new JObject (ctr);

You should know that JObject exposes three constructors, none of them public. The first constructor is the one we have just shown, and is used by derived classes from JObject. For example, if we were writing Prog.cs, a C# wrapper to a Java class Prog.class, we would use that constructor. This constructor's signature is:

C#
protected JObject (JConstructor ctr, params object[] args);

This constructor takes a variable number of System.Object arguments (this is what the C# params keyword does). This allows derived classes to obtain the constructor (JConstructor) during the static class constructor, and call the base constructor with the reference to JConstructor and the arguments passed to it.

Invoking a Method

JConstructor is just a special type of JMember. We can query JClass for any JMember, be it a field, a constructor or a method. Invoking a method or accessing a field is not very different from creating a new object instance. We still require a JClass, out of which we will obtain a JMethod or a JField using its signature.

Going back to our example, Prog, if we had a Java method:

Java
public class Prog {
    public int max (int a, int b) {
        return (a > b) ? a : b;
    }
}

We could obtain the method from JNI.NET.Bridge by calling the GetMethod() in JClass:

Java
JClass clazz = JClass.ForName ("Prog");
JMethod _max = clazz.GetMethod ("max", "(II)I");

GetMethod takes two arguments: the name of the method, and the internal method signature. To obtain the internal method signature, you can either learn the specifications, and use Sun's Java SDK javap tool.

Once we have obtained a method, we can invoke it. In our Prog example, we would call:

Java
int result = _max.CallInt (obj, a, b);

CallInt is used for calling methods that return an int. Based on the return type, there are other variants in JMethod to invoke a Java method:

  • CallBoolean, CallByte, CallChar, CallShort, CallInt, CallLong, CallFloat, CallDouble, CallVoid, used for calling methods that return a boolean, byte, char, short, int, long, float, double and no return, respectively
  • CallObject, used for calling methods that return an object or an array (regardless of whether it is an object array or a primitive type array)

All CallXXX methods take two versions: one that takes a JValue array, and one that takes a variable number of arguments. For example, for CallInt:

Java
public int CallInt (JObject obj, JValue[] args);
public int CallInt (JObject obj, params object[] args);

Whenever possible, the JValue[] version should be preferred for performance reasons. When a variable argument list is used, boxing/unboxing overhead, as well as a conversion routing overhead is incurred.

Additionally, JMethod provides a convenience method, Invoke, that avoids having to know which version of CallXXX to use. The signatures of Invoke are:

Java
public object Invoke (JObject obj, JValue[] args);
public object Invoke (JObject obj, params object[] args);

Invoke will determine the right CallXXX to call based on the method signature provided when the instance of JMethod was created. Invoke returns an object, exploiting .NET's boxing. Note therefore that although convenient, this method provides less performance than calling directly the right CallXXX version, where there is no boxing involved.

All versions of CallXXX, as well as Invoke, take as their first argument a reference to an instance of JObject. Obviously, an object reference is only required for instance methods, and for class (static) methods, the first argument is ignored. It is therefore legal to pass a null reference to class (static) methods.

Arrays

All arrays in Java (and .NET) are objects. JNI exposes all arrays as a jarray, which is encapsulated in JNI.NET.Bridge as JArray. JNI also has specialized versions of jarray, such as jintarray, which JNI.NET.Bridge encapsulates as JIntArray.

JNI.NET JArray specialized versions, such as JIntArray or JObjectArray, provide three constructors:

  • public JObjectArray (JObject other); A copy constructor
  • public JObjectArray (int length, JClass clazz); Creates a JObjectArray with the specified length and using the Java class referenced by the JClass instance
  • public JObjectArray (JObject[] source, JClass clazz); Creates a JObjectArray out of the array of JObject, using the type specified by JClass

An example will clarify when to use each constructor. If we were calling a Java method fooB:

Java
public int fooB (Prog[] arg);

The method fooB needs to get an instance of a JObjectArray, so the first step in .NET will be to convert the .NET Prog[] array to a JObjectArray. We do this by calling the third constructor:

Java
// in .NET
Prog[] args = ...
JObjectArray array = new JObjectArray (args, Prog.JClass);
int r = _fooB.CallInt (this, array);

The JObjectArray constructor first allocates a jobjectarray using the JNI function NewObjectArray, and providing the jclass referenced by Prog.JClass. The constructor of JObjectArray then copies the elements contained in Prog[] args into the newly created object using the JNI function SetObjectArrayElement.

We have seen how to call a Java method taking an array. A similar pattern is used for returned arrays. Let's say we wanted to call a Java method fooA:

Java
public Prog[] fooA ();

As we know, the call to fooA is done as:

Java
JObject o = _fooA.CallObject (this);

In this case, we must cast a JObject to a .NET array. We will use the copy constructor:

Java
JObjectArray array = new JObjectArray (o);

And obtain the elements in the array as an array of JObject[] and copy one to one to a newly allocated Prog[]:

Java
JObject[] oArray = (JObject[]) array.Elements;
int l = array.Length;
Prog[] r = new Prog[l];
for (int i = 0; i < l; i++) {
    r[i] = new Prog (oArray[i]);
}

Note that ideally, we would have liked to write something like:

Java
Prog[] r = (Prog[]) array.Elements;

The current implementation does not do this because of performance reasons. The same as with casting, commented in the previous section, we could have implemented array casting using reflection, as JMethod knows the type of o, and JObjectArray could have used the static JClass property in JObject to instantiate the right object array.

Configuration

The configuration mechanism allows you to specify the library load path, the classpath, and the command line arguments you would like to pass to the Java runtime. This is especially interesting when either you have to setup a complex CLASSPATH (usually the case in J2EE applications), or because your JVM is not called jvm.dll or libjvm.so.

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="jvm">
     <section name="settings" type="Jni.Net.Bridge.JNISectionHandler, Jni.Net.Bridge"/>
    </sectionGroup>
  </configSections>

  <jvm>
    <settings>
      <jvm.dll value="C:\Program Files\Java\jre1.5.0_07\bin\client\jvm.dll"/>
      <java.class.path value="minijavart.jar"/>
      <java.class.path value="."/>
      <java.library.path value="C:\Program Files\Java\jre1.5.0_07\lib"/>
      <java.option value="-verbose:gc,class"/>
    </settings>
  </jvm>

</configuration>

In this example, we are specifying that the JVM is contained in a DLL called jvm.dll; that the classpath should contain the minijavart.jar file and "." directory (in the current directory since no relative nor absolute path is given in this example); that native libraries (e.g. System.loadLibrary()) should be searched under C:\Program Files\Java\jre1.5.0_07\lib, and that class garbage collection should be traced at verbose level.

The jvm.dll needs to be specified in the settings section if it is not found in the PATH or in the registry under HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment.

Sample Source and Build

The project was developed in .NET Framework 2.0, using Visual C# 2005 Express and Visual C++ 2005 Express.
The source zip file contains two Visual Express projects, the first one is the bridge project, which is the C++ project of bridge.dll. First, open this project and build it. The second project is the JNI.NET.Bridge project which is C# project, it includes all the source code of JNI.NET.Bridge.dll and a sample test program.
Open it and build it using Visual C# 2005 Express or Visual Studio 2005.
Run the Test project to check if everything is working.

To Be Continued...

In the next part, I will explain how to implement .NET proxy in order to call the Java classes in a more OO manner, and how to call .NET from Java.

History

  • 8th June, 2006: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO Software Technologies EOOD
Israel Israel
Guy is a software engineer and co-founder of "Reflective Software". A consulting company that specializes in development in C#, Java and J2EE, C++, Windows programming and more.

Comments and Discussions

 
QuestionHow to call classes with Package? I need urgent help. Pin
neal12319-Jan-10 0:53
neal12319-Jan-10 0:53 
AnswerRe: How to call classes with Package? I need urgent help. Pin
jfr14-Oct-10 3:24
jfr14-Oct-10 3:24 
Generaljni4net Pin
Zamboch31-Oct-09 8:41
Zamboch31-Oct-09 8:41 
GeneralRe: jni4net Pin
Nitin S30-Nov-12 4:12
professionalNitin S30-Nov-12 4:12 
GeneralRe: jni4net Pin
qwecoder28-Aug-13 22:39
qwecoder28-Aug-13 22:39 
QuestionNeed help for Java and .Net interoperability Project Pin
Ramsworld25-Jul-08 9:26
Ramsworld25-Jul-08 9:26 
QuestionCan't find the other articles Pin
univero8-Jul-08 21:26
univero8-Jul-08 21:26 
QuestionHow to convert between Jobject to Jstring and System.String? Pin
Member 30575618-May-08 3:57
Member 30575618-May-08 3:57 
AnswerRe: How to convert between Jobject to Jstring and System.String? Pin
Xeshan Ahmed18-Apr-11 18:57
Xeshan Ahmed18-Apr-11 18:57 
QuestionTypeInitializationException (ASAP) Pin
Sathyan...26-Sep-07 7:27
Sathyan...26-Sep-07 7:27 
GeneralReg:- Calling a Java Class from C#.NET Pin
SRKVELLANKI26-Apr-07 23:52
SRKVELLANKI26-Apr-07 23:52 
QuestionCan't build Bridge project (C++ library) Pin
Korn_de_Bouc24-Apr-07 6:01
Korn_de_Bouc24-Apr-07 6:01 
AnswerRe: Can't build Bridge project (C++ library) Pin
SRKVELLANKI27-Apr-07 0:17
SRKVELLANKI27-Apr-07 0:17 
QuestionRe: Can't build Bridge project (C++ library) Pin
Member 30575617-May-08 23:22
Member 30575617-May-08 23:22 
AnswerRe: Can't build Bridge project (C++ library) Pin
Xeshan Ahmed15-Apr-11 1:26
Xeshan Ahmed15-Apr-11 1:26 
GeneralMemory Problem Pin
Hemanth Gops16-Apr-07 20:43
Hemanth Gops16-Apr-07 20:43 
QuestionMemory Problem Pin
Xeshan Ahmed20-Apr-11 1:43
Xeshan Ahmed20-Apr-11 1:43 
QuestionWhere can i find Part IV Pin
Member 230909127-Mar-07 22:03
Member 230909127-Mar-07 22:03 
QuestionFolders in jar files [modified] Pin
Carlos Rocca13-Mar-07 10:57
Carlos Rocca13-Mar-07 10:57 
AnswerRe: Folders in jar files Pin
neal12319-Jan-10 1:03
neal12319-Jan-10 1:03 
Hi Carlos,

I think you have done lot of research for this issue. I am facing problem currently to impliment my java classes with .net. I have allready posted my problem under heading "How to call classes with Package? I need urgent help.".

Can you please guide me for the same?

Thanks in advance...

Neal.
AnswerRe: Folders in jar files Pin
neal12319-Jan-10 21:50
neal12319-Jan-10 21:50 
QuestionI hava a question? Pin
TylerDurdon7-Feb-07 16:22
TylerDurdon7-Feb-07 16:22 
AnswerRe: I hava a question? Pin
Guy Balteriski7-Feb-07 19:31
Guy Balteriski7-Feb-07 19:31 
GeneralRe: I hava a question? Pin
TylerDurdon26-Feb-07 22:21
TylerDurdon26-Feb-07 22:21 
Generalhandling String Pin
inacio.ferrarini8-Jan-07 13:55
inacio.ferrarini8-Jan-07 13:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.