any organizations have applications already written in Java; but applications often aren’t static?they need constant attention and modifications to remain productive. When it’s time to upgrade, rather than rewriting the base functionality underlying these applications, it’s often more feasible to extend them by taking advantage of the rich new user interfaces and technologies present in Microsoft .NET. You can use the techniques discussed in this article to host .NET Controls in Java at the user interface level.
Microsoft .NET and Java are two powerful frameworks that provide a rich set of classes. While either of these frameworks can solve many of the same problems, each has their advantages and disadvantages. When you have existing, tested components, you may find it beneficial to use both .NET and Java in your application, perhaps to support or extend legacy applications that have already been written in Java but adding value using the .NET Framework.
By using the Component Object Model (COM) interoperability services provided by the .NET Framework and the Java Native Interfaces (JNI) present in Java, you can host .NET controls in your Java application.
In this article, you’ll build and host a control built with Microsoft C# in a Java application. Figure 1 shows the completed control hosted in a Java app.
|Designing the Java Container Control
You need a Java control to host the .NET control. Both AWT and Swing controls provide a window handle, or HWND, to which you can attach a Windows control. That control can be a simple java.awt.Canvas to which the Java Virtual Machine (JVM) provides an HWND.
Because you’ll be using JNI to attach the .NET control to the Java control’s HWND, you need to add the following line into the class’s static constructor:
That line tells the JVM to load the JNIBridge.dll (or JNIBridge.so library in BSD or Linux) when it loads the class. The system uses the JNIBridge library whenever your code calls any native methods in the class. The library must exist in a directory specified in the PATH environment variable as with most libraries. The JVM will not, however, look for libraries in the current working directory. You’ll see a workaround for that later in this article.
Declaring the native methods is trivial. They are simply declarations that use the native Java keyword. Later, you’ll create a C header file from these definitions. Two native methods are required to initialize and destroy the .NET control. The JVM calls the addNotify and removeNotify methods to allow native code to attach controls to a window handle and to destroy those controls, so the native methods will be called from the implementation of these two methods. The native methods are declared below, and the two methods are overridden that call these two native methods:
Other native methods are similarly called by overridden methods.
Compile the class using javac.exe from the Java Development Kit (JDK), for example:
Creating the C Header for Native Methods
The preceding code line creates functions in the form Java_
The JNIEnv parameter references the Java environment and you can use it to access classes, methods, and properties exposed by the JVM. The second parameter references the object itself?an instance of COMControl class in this case.
Implementing the Native Functions
Create a new C/C++ project. You’ll need to #include the jni.h header file, so make sure you include both the include and include/win32 directories from the JDK installation directory. Import the jni.h header and the JNIBridge.h header from this sample in a C source file.
To begin implementing the native functions, copy the declarations from the header file you generated into the source file. Add parameter names and curly braces to each, setting up each function for a definition. You’ll define the initialize function first (see Listing 1 or the sample source for the complete implementation).
This initialize function gets the HWND from the Canvas. This window handle is provided by the JVM. After getting the HWND, execution is passed to a thread so that the method can return and allow the Java control to continuing initializing and handling events. The new thread creates the .NET control you exposed as a COM component and attaches it to the HWND. It then initializes properties of the control when necessary and starts the message loop to process messages and to keep the control running (see Listing 2 or the sample source for implementation).
The destroy function destroys the COM component window resources, decrements the reference count, and deletes the pointer. This makes sure that the .NET control is destroyed and memory is reclaimed (see Listing 3 or sample source for implementation).
Implementing the getUsername native function requires that you get a string from the .NET control and return it to the Java control:
The Common Language Runtime (CLR)?the runtime for the .NET Framework?creates a COM-Callable Wrapper (CCW) that marshals System.String values as BSTR values. The Microsoft Active Template Library (ATL) provides a handy wrapper class, CComBSTR, to make this easier and to automatically free space allocated to the BSTR. You should first make sure that you have a valid reference to the .NET control. Then call get_Username?the get accessor of the Username property?passing a reference to the CComBSTR for the username. If the method returns a successful HRESULT code, use the NewStringUTF method of the JNIEnv object to convert the Unicode string to a jstring and return it.
You’ll encounter an interesting problem when implementing the getBirthday native function: there is no intrinsic type for the java.util.Date class provided by JNI. You must create an object of type Date in the native implementation as shown in the following code fragment:
Use the FindClass and GetMethodID methods from the JNIEnv object to get the jclass reference and find the method ID for the appropriate constructor. The JNI specifications state that you should use the string “
Another problem arises when dealing with dates. The DateTime structure in .NET stores the number of ticks?or 100 nanoseconds?from January 1, 0001 A.D. using the local time zone, as described in the NET Framework SDK documentation.
The CLR marshals the DateTime structure as a DATE, which is a typedef for a double. The DATE data type documentation describes that the CLR stores time as the number of milliseconds since December 30, 1899, also using the local time zone.
Unfortunately, Java stores the number of milliseconds since January 1, 1970 in Universal Coordinate Time (UTC). Not only do you have to worry about adjusting the base time, you must also take the time zone difference into account. The java.util.Calendar class can assist with the time zone problem.
The size and location methods in the Java class are straight-forward. Any method calls to change the position or size of the Java COMControl class will call the native functions which marshal the values to the .NET control. All three environments use 32-bit integers to represent the top and left coordinates, and the width and height of the control.
Assigning the background and foreground can be difficult, but there is a workaround to simplify your implementation. Both environments use a structure to hold color values similarly, but each uses a slightly different structure. Marshaling these two types would add complexity to your code. To make marshaling easier, the Java control instead passes the RGB value of a java.awt.Color object as a long, which is then marshaled as an OLE_COLOR to the .NET control?which converts the value to a System.Drawing.Color structure.
Finish implementing your native functions. Just remember that passing complex types can lead to marshaling problems so try to use basic types as much as possible. Understanding the marshaling services provided by the .NET Framework in the System.Runtime.InteropServices namespace and understanding the JNI specifications can help solve these problems if you must use complex types.
When you finish implementing the native functions, configure the project to link against the jawt.lib static library. I’ve done this for you in the sample project. You should make sure to add the lib directory in the JDK to your LIB environment variable. Compile the project.
Deploying and Running the Solution
You must register the .NET assembly so that the path to the assembly can be resolved, or installed the assembly into the GAC. This procedure was discussed earlier in the section “Designing the .NET Control.”.
Install the JNI library into a directory listed in the target machine’s PATH environment variable. If you prefer to keep all the application files together in a single directory, you should either add the current working directory (represented by “.”) or your application installation directory to the PATH environment variable, or change the lookup paths in the command line shown below used to start the Java application:
The command line ensures that the current working directory is included in the search path for any native libraries required by the JVM. Adding the current working directory to the CLASSPATH also ensures that the the JVM can find the Java application class files. See the Sun Java Web site for more information about deploying and executing Java classes.
Setting up your C/C++ project correctly allows you to compile and debug your native C/C++ code and your .NET code in whatever language you use. The sample project is configured to run the Java application using the command line above. You could instead use the jdb.exe application to debug the Java application at the same time.
When you have completed your testing plan, you should recompile your entire source in a release build and deploy the solution in the same way described above.
Although the process to host .NET controls in Java isn’t trivial, being able to use the rich user interfaces and technologies in .NET within your Java application is advantageous. You can reuse your .NET controls in both .NET and Java applications and can potentially reduce the development time of producing and maintaining separate class libraries in different languages.