| By Alexander R. Krapf | Article Rating: |
|
| February 13, 2004 12:00 AM EST | Reads: |
13,219 |
If you have never had to integrate Java with code written in other languages, you are a very lucky person. Java is a wonderful language when you are looking for platform portability, but unless you are a big CORBA fan, code written in Java is hard to use from all other programming languages.
Codemesh set out four years ago to integrate Java with C++ in a platform-portable way, but when Microsoft introduced .NET, we decided that quite obviously a Java/.NET integration product would also be a very good idea. Our JuggerNET product was the result.
You might ask: "Why would I ever want to integrate Java and .NET? Porting is one thing, but mixing them? Java believers will continue to use Java, and .NET believers will continue to use .NET."
If only it were that easy. Few enterprises have the luxury of creating 100% homogeneous development and deployment environments anymore. Even if you started out with one development environment, customer pressure or mergers and acquisitions ensure that eventually you will be working in a mixed environment. But even if you are not forced to consider Java/.NET interoperability, there are many good reasons to integrate the two platforms. Consider the following scenarios:
- You have invested heavily in enterprise Java (J2EE), but you also have internal groups or customers who write their applications in C# or VB. How are their .NET applications going to access your JMS or EJB infrastructure on the Java side?
- You have spent years developing Java applications that heavily leverage the Java runtime libraries and maybe even third-party Java libraries. Now you have customers who are requesting access to your APIs from .NET. How do you solve this problem without porting or reimplementing all the supporting code?
- You have Java code that you would like to use from .NET applications (for example Word or Excel). How can you do this?
There are a million ways to integrate code written in different languages. I've already mentioned CORBA, but there are also the other serialization-based integration approaches, such as XML-RPC, SOAP, Web services, messaging, sockets, etc. These solutions all have something in common: they either have to publish a remote reference or serialize the object's state and transmit it via a wire protocol in order to make the object available to the other language. The first alternative makes every interaction with the object a remote operation; the second requires potentially large amounts of data serialization and deserialization. Both alternatives imply at least two processes, potentially huge performance penalties, and they usually require external infrastructure (name servers, Web servers, proxy servers, etc.)
One true alternative to the over-the-wire solutions is the multitude of cross-compiler projects that attempt to compile one environment's source code/bytecode into the other environment's bytecode. All cross-compiler solutions suffer from the fact that the two sides are not just languages but rather entire platforms. Even if you came up with a perfect Java source–to-IL compiler, you would still require a JRE at runtime because of the native runtime libraries that are being referenced. Reproducing all native libraries would be prohibitively expensive and keep you permanently out of sync with the current version of Java.
We decided that neither the serialization nor the cross-compilation solutions had all the characteristics we wanted from a good integration solution. In particular, we had these top design goals:
- Totally natural usage of Java types in .NET
- Near-native performance
- Ability to work with any version of the platform (.NET and Java)
- Xcopy deployment of the integrated solution without involving complicated server processes
public static void Main( String[] args )
{
InitialContext ictx = new InitialContext();
TopicConnectionFactory tqf =
(TopicConnectionFactory)ictx.lookup( "TCF" );
...
}
What's so unusual about this code? Well, unless you noticed that Main is capitalized, you might not have realized that this is a C# rather than a Java code snippet. Making this snippet work as expected takes a lot of work, namely:
- Creating .NET types that resemble the underlying Java types so closely that the developer isn't surprised by differences in usage. This is achieved with the help of a code generator.
- Launching a correctly configured JVM within the CLR process.
- Delegating each .NET proxy– type usage to the corresponding Java type usage.
Once we have generated the proxy types, we need to launch a JVM from within the CLR. This can be done through the invocation interface, which is part of JNI. "Not so fast!" you say, "JNI is a C-API, so how do we call the C-API from C#?" There are two possible answers: managed C++ or PInvoke. At first we tried managed C++ because we already had a lot of experience with C++ bindings for Java. After getting a prototype to work on one of our development machines, we tested on a different machine and immediately ran into the "deadlock during initialization bug" between the managed and unmanaged runtime libraries. None of the published workarounds were acceptable, so we chose to go the PInvoke route instead. This worked beautifully. The resulting design is illustrated in Figure 1. User-written code references a generated proxy class (a C# type that is a stand-in for a Java type). The proxy class delegates all its work to a runtime assembly written in C#. This managed runtime assembly delegates to an unmanaged runtime library written in C, which in turn delegates to the JVM via the Java Native Interface.

To show you what the process involves, I have taken a method invocation as an example and combined the entire invocation process in Listings 1–3. (All of the code listings referenced in this article can be downloaded from www.sys-con.com/dotnet/sourcec.cfm.) Listing 1 holds all the managed code, Listing 2 holds the PInvoke interface, and Listing 3 holds all the unmanaged code. (All exception- and performance-related details have been omitted for brevity; the entire example is extremely simplified.) This process sounds complicated, but a carefully designed API makes almost all of these function calls perform like regular, in-process method invocations, thereby achieving the (second) design goal of near-native performance.
One of the problems we needed to solve was the configuration of the Java runtime environment for the .NET application. Virtually all useful Java applications need to have their classpath configured, and possibly some additional JVM options set before they can execute. The same is of course true for any .NET application that internally executes Java code. We decided that there should be two ways to provide the configuration information: the .NET way via a .config file (see Listing 4) and the programmatic way, as shown in the following code snippet, in which the developer specifies the information in code. Adding the provided XML snippet to your application's configuration file provides all necessary information about the desired Java runtime environment (strictly speaking, the MaximumHeapSize setting is not necessary and is included simply to illustrate that you can control every aspect of the JVM).
SunJava2JvmLoader loader = new SunJava2JvmLoader();
loader.JVMPath = @"..\jre\bin\client\jvm.dll";
loader.ClassPath = @"..\lib\myapp.jar";
loader.MaximumHeapSize = 256;
Using this code has exactly the same effect as a .config file but it hides the configuration information from the application's user.
Notice that both examples use relative paths for the JVMPath and the ClassPath settings. By installing a private JRE as part of your application and using only relative paths in your JVM configuration, you can achieve Xcopy deployability, our fourth design goal.
But what about the most important of our design goals: totally natural usage of Java types in .NET? This turned out to be in some ways easier than expected, and in others, harder. .NET has many similarities to Java, plus some additional features that were very helpful in making the two sides work together:
- Java interfaces map to .NET interfaces. This works very well, with one exception, which I will discuss a little later.
- Java classes map to .NET classes.
- Java constructors map to .NET constructors.
- Java methods map to .NET methods.
- Java fields map to .NET properties. Every operation on the property is translated to a corresponding JNI operation on a Java field.
The Java interface:
public interface Context
{
public static final String PROVIDER_URL = "ProviderURL";
public Object lookup( String name );
};
The generated .NET interface:
public interface Context
{
public object lookup( string name );
};
The generated .NET class:
public class ContextImpl : Context
{
public static string PROVIDER_URL
{
get { return ...; }
}
public object lookup( string name )
{
return ...;
}
};
The following snippet illustrates the usage of the generated code:
Hashtable env = new Hashtable();
env.put( ContextImpl.PROVIDER_URL, "test" );
Context ctx = new InitialContext( env );
object temp = ctx.lookup( "myLookupName" );
The String type introduces another problem. Both Java and .NET have powerful, built-in String types, and we certainly need to allow the programmer to use .NET string literals as function arguments or right-hand-side values in assignments. This requirement generally means that we have to allow the use of any object in places where a string literal is a legal value, because both strings and proxy types need to be allowed. It also means that when we map java.lang.String types into .NET, we don't map them to a String proxy type but rather directly to the System.String type.
One of the nastiest problems is related to exception handling. We want the programmer to be able to write the following code:
try
{
object o = ctx.lookup( null );
}
catch( NullPointerException npe )
{
Console.WriteLine( npe.Message );
}
catch( Throwable t )
{
Console.WriteLine( t.Message );
}
This means that the proxy exception types have to be derived from the .NETSystem. Exception type; otherwise they cannot be thrown or caught. Because this takes up the sole class inheritance slot, they cannot be derived from the proxy type for java.lang.Object either, thereby neatly breaking the inheritance hierarchy. Figure 2 illustrates this problem. The orange types represent the .NET object hierarchy; the gray types the mapped Java proxy types. The red lines indicate inheritance relationships that should be present but cannot be reproduced because of .NET constraints. This could impose a usage restriction when exceptions are passed as arguments, but it will not usually be a problem.

The trickiest part of the entire integration involves callbacks. We wanted a .NET developer to be able to implement a Java interface in .NET. Why is this necessary? Because many useful APIs in Java are defined by interfaces that are expected to be implemented by the developer. Take JMS as an example: the javax.jms.Message Listener interface needs to be implemented by the developer and then an instance of the implementation type is registered with a Topic or a Queue to receive asynchronous message notifications. We built a mechanism by which developers can simply implement a generated interface in their .NET language of choice and use an instance of that type as a callback object. Listings 5 and 6 illustrate this functionality.
Altogether, I believe that we came up with a mapping that allows a developer to use Java from within .NET without having to be too aware of the origin of any given type. Listings 7 and 8 contain real applications that illustrate the usability of the proxy types.
Conclusion
- If you don't need distributed computing, don't use XML RPC, Web services, or CORBA for language integration projects. All are complete overkill and will introduce as many problems as solutions.
- Consider all aspects of the integration problem at hand. While sockets-based integration can be simple, it is also usually extremely limiting and you often end up implementing dual type systems and a protocol on top of the sockets solution just to deal with failure modes.
- Java and .NET are platforms, not just languages. Integrating the two is much harder than simply translating bytecode or writing a wrapper type for one class.
- I really like the idea of the CLR and I hope that you will find this technology useful if you are one of the unlucky who have to mix Java and .NET. Hopefully, you can even have some fun mixing and matching.
Published February 13, 2004 Reads 13,219
Copyright © 2004 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
More Stories By Alexander R. Krapf
Krapf has over 15 years experience in software engineering, product development, and project management in the United States and Europe. In addition to founding and managing CodeMesh, Krapf has worked for IBM, Thomson Financial Services, Hitachi, Veeder-Root, and Document Directions, Inc. He has been extensively involved in a variety of complex product development efforts using his in-depth understanding of .NET, C++ and Java.
Krapf has been published in technology journals and been a speaker at a variety of industry conferences. He received a Bachelor of Science degree in Electrical Engineering from the University of Stuttgart, Germany, and can be reached at alex@codemesh.com.
- Kindle 2 vs Nook
- Wave on Ulitzer: Confessions of a Google Wave Fanboy
- Confessions of a Ulitzer Addict
- Cloud Computing Best Practices
- IBM Hardware Chief, Intel VC Exec Arrested in Insider Trading Scam
- Tactical Cloud Computing Panel at 1st Annual GovIT Expo
- Ulitzer.com Named Exclusive "New Media" Sponsor of Cloud Computing Conference & Expo
- Infrastructure-as-a-Service Will Mature in 2010: Microsoft's David Chou
- Windows 7 – Microsoft’s First Step to the Cloud
- Cloud Computing & Federal IT - What Does the Future Hold?
- Jill Tummler Singer, Deputy CIO of CIA, Keynotes at GovIT Expo
- Cloud Expo and the End of Tech Recession
- Kindle 2 vs Nook
- The Difference Between Web Hosting and Cloud Computing
- Ajax in RichFaces 3.3, JSF 2 and RichFaces 4
- Wave on Ulitzer: Confessions of a Google Wave Fanboy
- Confessions of a Ulitzer Addict
- IBM Hardware Chief, Intel VC Exec Arrested in Insider Trading Scam
- Cloud Computing Best Practices
- Tactical Cloud Computing Panel at 1st Annual GovIT Expo
- Ulitzer.com Named Exclusive "New Media" Sponsor of Cloud Computing Conference & Expo
- Eval JavaScript in a Global Context
- Infrastructure-as-a-Service Will Mature in 2010: Microsoft's David Chou
- Windows 7 – Microsoft’s First Step to the Cloud
- Google Maps and ASP.NET
- Crystal Reports XI & How It Has Changed
- Converting VB6 to VB.NET, Part I
- Creating Controls for.NET Compact Framework in Visual Studio 2005
- Where Are RIA Technologies Headed in 2008?
- How to Write High-Performance C# Code
- AJAX World RIA Conference & Expo Kicks Off in New York City
- Implementing Tab Navigation with ASP.NET 2.0
- i-Technology Photo Exclusive: Bill Gates & Steve Jobs In "Nerds"
- .NET Archives: Getting Reacquainted with the Father of C#
- i-Technology Viewpoint: "SOA Sucks"
- Programmatically Posting Data to ASP .NET Web Applications



































