: code tutorial (v3)

Lesson 1: Project Setup

The topic of this lesson is how to set up your .NET project to use Java types. As described in the Technology overview, your application code usually deals with .NET proxy types, but the .NET proxy types internally rely on both the managed .NET and the unmanaged C++ runtime library. If you have not read that overview yet, now would be a good time to do it.

We are intentionally using just the commandline C# compiler in this lesson to keep the "magic" that VisualStudio usually performs for you to a minimum. You would probably not do a commandline build but rely on a VisualStudio project and MSBuild. Please take this lesson as an educational exercise on the kinds of decisions you have to make and not as a blueprint for how to set up your own project.

In the Technology overview we glossed over this a little bit, but you do not only deal with .NET proxy types but also directly with the managed .NET runtime library when you configure the Java runtime environment for your .NET application. In fact, you could use just the .NET runtime library without any proxy types to load a JVM into your process.

Therefore, the simplest Java/.NET integration project does not involve any proxy types at all. It only uses some of the framework APIs from the runtime library to load a JVM. Such a program might look like this:

using System;
using System.IO;
// this makes available all the framework types that you
// might normally have to deal with
using Codemesh.JuggerNET;

namespace Codemesh {

    namespace Examples {

        public class LoadJvmTest {

            public static void Main( string[] args ) {
                // acquire the loader singleton on which we issue all the configuration API calls
                IJvmLoader  loader = JvmLoader.GetJvmLoader();

                try
                {
                    // feel free to comment out this line to get less clutter
                    loader.SetTraceLevel( TraceFacility.TraceJvm, TraceLevel.TraceInfo );

                    Console.Error.WriteLine( "INFO: About to load JVM...");

                    // have the loader attempt to load a JVM.
                    // this could fail for example if no JVM is installed or if the loader
                    // cannot locate an installed JVM
                    // The JVM instance is owned by the loader and does not need to be deleted.
                    IJvm  jvm = loader.Load();

                    Console.Error.WriteLine( "INFO: Successfully loaded JVM!");
                }
                catch( Exception e )
                {
                     Console.Error.WriteLine( "ERROR: {0}", e.ToString());
                }

                Console.Error.WriteLine( "INFO: Done with explicit example!");
            }
        }
    }
}

This program uses the JDBC Courier managed runtime library's API to acquire a JVM loader instance and then attempts to use that instance to load the default Java Virtual Machine. In a real integration project, you would follow the JVM loading by using some .NET proxy types before returning from Main().

Once the JVM has been loaded into the process, it is managed internally and remains active until the process terminates, unless you manually unload it. Please note that most implementations do not support the repeated loading and unloading of JVMs within one process, so you should pretty much assume that your JVM will be around till your process exits. While there is an Unload() function, you should probably never use it and instead rely on the framework to manage the JVM's lifecycle.

All JDBC Courier runtime library type names are located in the Codemesh.JuggerNET namespace and are packaged into an assembly named netrt.

Build

Were you to try to compile this code with the C# compiler, for example by typing:

csc -target:exe -main:Codemesh.Examples.LoadJvmTest LoadJvmTest.cs

you would receive several compilation errors related to missing Codemesh.JuggerNET types. Clearly you need to tell the compiler where to find these types. Thus you add JDBC Courier's managed runtime library to the compiler invocation.

csc -references:netrt -target:exe ...

The problem now is that the compiler does not know where to find the netrt.dll assembly that contains all of Codemesh's framework types. Exploring your JDBC Courier distribution leads you to the dotnet/v3 directory, which contains several different versions of the assembly. It is now time to make an important decision, though usually that decision will already have been made for you at a much earlier point.

You need to decide for which platform you want to build your application. Wait, isn't .NET platform-portable? Yes, .NET is platform-portable, and so is Java, but the portability is implemented at the bytecode level, not at the binary level. A 32-bit Java version uses DLLs that have been built exclusively for 32-bit mode. A 64-bit Java version uses DLLs that have been built exclusively for 64-bit mode. You cannot mix and match DLLs that are built for different platforms within one process. Remember, to achieve the highest performance and the tightest possible integration, JDBC Courier loads the JVM into the .NET process. But you can never load a 32-bit JVM into a 64-bit process or vice versa. This is a limitation enforced by the operating system.

So you need to decide whether you want to run with a 32-bit version of Java or with a 64-bit version of Java and build your .NET application accordingly. Alternatively, you could build your .NET application to be platform neutral and rely on your application's configuration to ensure that you never try to load the wrong kind of JVM (for example by using platform APIs to figure out whether you're running as a 32-bit or 64-bit process and then configuring one or another of two bundled JVMs). Either way, you have to decide which path you're going to take and pick the proper version of netrt.dll. If you are going to target 64-bit deployments, you should pick the dotnet/v3/bin/x64 version. If you target 32-bit deployments, you should pick the dotnet/v3/bin/x86 version. If you are going to manage platform compatibility internally and remain portable, pick the base dotnet/v3/bin version.

As most of you will be working on modern 64-bit Windows computers we will explicitly build for 64-bit Windows and pick the matching runtime assembly:

csc -platform:x64 -lib:<install>/dotnet/v3/bin/x64 -references:netrt ...

Your application should now build but it won't run yet. You need to make the netrt.dll that you referenced at build time available at runtime. You can simply copy it into the directory containing your built executable.

Now it will run but it will crash. The reason is very clear if you look at the diagram illustrating the anatomy of a function call on the Technology page. netrt.dll delegates to the unmanaged runtime library xmogrt.dll to perform the actual integration with Java. You have not yet deployed a proper version of xmogrt.dll.

Where to Find the Right Runtime Library

Once downloaded, you have access to a range of different runtime libraries and hopefully one of them matches your runtime environment. If not, you can always talk to us and we can build a runtime library for you.

Irrespective of platform, the runtime library is always called xmogrt.dll.

The libraries can be located in subfolders of the cpp/v3/lib directory.

Directory hierarchy screenshotThe screenshot to the right shows a typical directory hierarchy of runtime library directories.

As you can see, the first level after cpp/v3/lib is an operating system identifier.

The second level is a processor architecture identifier. The processor architecture is used to identify both the family of processors and the bittedness. 64-bit versions of a processor architecture are represented by their own code. For example, the 32-bit version of Intel X86 compatible processors is represented by the family code x86 whereas the 64-bit version is represented by the family code amd64. We use commonly known identifiers, so you should not have any trouble locating the proper directory.

The third level is a combined compiler type and compiler version identifier. As mentioned above, you don't necessarily have to match the type and version exactly, but you should try to use the best match that you can find.

Finally, there are debug and release versions of the runtime library. The fourth level distinguishes between these two flavors.

Simply pick the most modern release version that matches the platform of the JVM you intend to use and copy it into the diretory containing your executable.