Introduction
The Java Native Interface is used to write native methods that handle situations when an application cannot be written entirely in the Java programming language. Mainly, the Java developer needs to call some native functions in the DLL. For this, he should write a Java class with native methods and write a DLL that implements those methods with export functions. Where desired, the native function can be called.
Whoever has written JNI code knows that it takes a huge amount of effort to use the JNI SDK. Java and native non-primitive data types should be marshaled and unmarshaled with JNI functions: Unicode and ANSI strings, Java arrays, etc. Take into account that most Java developers do not know the JNI SDK. When we wrote code in Microsoft VB, we had no problem calling any native functions. There was enough to write its prototype with a layout that contains data about the library it belongs to and what its original name was. The same paradigm Microsoft used in the .NET languages is called .NET Platform Invoke. The main idea of it is to "declare" and "use" a native function.
The State of the Issue
We are not the first who have tried to substitute JNI with something that will make native library use in Java code easy. In WEB there are a number of products whose authors claim that native functions can be called in Java code without JNI use. These are xFunction, J2Native, JNIWrapper, Neva and others. However, such products do not solve the problem. They use the idea of an indirect function call -- like in reflection -- with data marshal description, including primitive types. When a function has a structure as a parameter, you will write complex code to describe the structure marshaling and unmarshaling, fields alignment, union description, etc. Sometimes code becomes so complex and, for me, it is better to write a simple JNI module than a large and unclear Java code.
Java Virtual Machine can call an export function bound to a Java Class native method. In the call stack, values of the primitive data type are already marshaled. So, JVM does some job and if implemented, the rest marshal the algorithm for Java objects that can be mapped to structures, strings and arrays. The Java developer would be able to call any export functions from native libraries, like native Java methods. The only task to be solved is how to define metadata that will keep information about a native function bound to the Java native method that is a prototype of a native function.
The author wrote the JNI code of the Java Platform Invoke SDK in C++. It is compiled with Microsoft VC++ and MinGW compilers for porting it in future to Linux and Unix systems.
Using Platform Invoke in Java
Java Annotations and .NET Attributes
Starting with JSE 1.5.x, SUN added to Java language standard annotations to eliminate use of custom attributes in a Java class. Annotations provide data about Java classes and their members that are not part of the program itself. They have no direct effect on the operation of the code, but only annotate. Annotations have a number of uses and among them:
- Information for the compiler: Annotations can be used by the compiler to detect errors or suppress warnings.
- Compiler-time and deployment-time processing: Software tools can process annotation information to generate code, XML files and so forth.
- Runtime processing: Some annotations are available to be examined at runtime.
Annotations for runtime processing resemble runtime attributes in .NET languages that define information stored as metadata. In Java, you can annotate classes, methods, method parameters and class fields. Like .NET attributes, annotations can keep default values. To consume in .NET exported DLL functions, you:
- Identify functions in DLLs: Minimally, you must specify the name of the function and the name of the DLL that contains it.
- Create a class to hold DLL functions: You can use an existing class, create an individual class for each unmanaged function, or create one class that contains a set of related unmanaged functions.
- Create function prototypes in managed code.
- Call a DLL function: Call the method on your managed class as you would any other managed method, passing structures and implemented callback functions.
For example, the definition of a MessageBox
function in C# can be written:
[DllImport("User32.dll", EntryPoint= "MessageBox")]
static extern intMessageBox(inthwnd, stringmsg, stringcaption, intmsgtype);
Using Java annotations, you can write the same declaration:
@ImportLibrary(libName = "user32")
public classUserLib extendsCNativeLibrary
{
@Function(entryPoint = "MessageBoxW")
public native intMessageBox(inthwnd,
String msg, String caption, intmsgtype);
}
Annotation @ImportLibrary
denotes the name, i.e. libName
value, of the library corresponding to the class UserLib
. The native function prototype is annotated by @Function
, with the parameter entryPoint = "MessageBoxW"
. This defines the original name of the function bound to its prototype in Java code. In both examples, there are no marshal definitions. While calling the MessageBox
method, the Platform Invoke SDK uses default marshaling rules. In C#, all string
parameters are marshaled as an ANSI character set, but in Java code string
parameters are marshaled and unmarshaled as a Unicode character set by default.
A Function Prototype and Default Marshaling
Were Platform Invoke a part of JVM, it would look as it does in .NET languages. In our SDK, however, we implement it partially in a Java module and the rest in the JNI module. That is to declare a native function prototype or prototype, you create a class that inherits the class CNativeLibrary
with native methods as prototypes of functions. This method is called as any other Java native method:
UserLib user32 = newUserLib();
user32.MessageBox(0, "KUKU from Java", "Java Message", 1);
While processing the call to the function MessageBox
, JavaPInvoke performs the following sequence of actions:
- It locates the library containing the function.
- It loads the library into memory.
- It locates the address of the function in the memory and pushes its arguments -- passed by JVM while the native function prototype calls -- onto the stack, marshaling data as required.
- It transfers control to the native function.
- It gets results returned, unmarshaling them as required, and passes the results back to Java code.
The function MessageBox
prototype above has no explicit marshal declarations and JavaPInvoke uses default marshaling, i.e. string
s are input-output values in Unicode.
Custom Marshaling
Sometimes default marshaling reduces the Java code's performance because function parameters may be only input or only output. Some output pointers could be constant and returned as pointers. Deleting them with the JavaPInvoke engine can cause GPF. Using custom marshaling, the MessageBox
function prototype can be modified as follows:
@ImportLibrary(libName = "user32")
public classUserLib extendsCNativeLibrary
{
@Function(entryPoint = "MessageBoxA", charSet = CharSet.Ansi)
public native intMessageBox(inthwnd,
@In String msg, @In String caption, intmsgtype);
}
Here the native method MessageBox
will be bound to an ANSI version of the function. String
parameters, like input values, are only marshaled to ANSI string
values -- i.e. charSet = CharSet.Ansi
-- and unmarshal is ignored.
Other Features of JavaPInvoke
The JavaPInvoke SDK supports marshaling for data types:
- Structure/union
- Array
- Native class with virtual functions
- Static callback function implementation
- Native event interface (under development)
Structure Marshaling
The JavaPInvoke SDK includes some simple means for marshaling Java class members to/from C++ Structure/Union. The base idea we got from .NET Platform Invoke. Here, only simple structure marshaling will be described -- i.e. for native structures without pointers and nested structures -- but the JavaPInvoke SDK also provides developers with means for definition of more complex marshaling.
Managed and unmanaged data structures are compatible only by the data layout and any Java class field can be defined as a part of some native structure. A structure is defined with a Java class that extends CStructure
. This class should be annotated with @StructLayout(layout = LayoutKind.Sequential)
. For example, the Microsoft Windows API SYSTIME
structure can be defined in Java as:
@StructLayout(layout = LayoutKind.Sequential)
public classSYSTEMTIME extendsCStructure
{
public shortwYear;
public shortwMonth;
public shortwDayOfWeek;
public shortwDay;
public shortwHour;
public shortwMinute;
public shortwSecond;
public shortwMilliseconds;
}
It is a good idea to explicitly set the structure packing setting to be the same as that used by the native structure @StructLayout(pack=8, layout=LayoutKind.Sequential)
. However, most of the system structures are independent of structure packing.
Static Callback Function Implementation
Some native functions can get a callback function pointer as a parameter. The JavaPInvoke SDK has means that create a native wrapper, a native callback function pointer at runtime. To create a native callback function pointer, you define a class with one method. That is, an implementation of the callback function. For example, let's implement WNDENUMPROC
called with the EnumWindows
function. First, define a class that implements WNDENUMPROC
:
public classWNDENUMPROC
{
publicWNDENUMPROC()
{
}
public booleanEnumWindowsProc(inthwnd, intlParam)
{
String shwnd = Integer.toHexString(hwnd);
if(shwnd.length() < 8)
{
switch(8 - shwnd.length())
{
case1:
shwnd = "0" + shwnd;
break;
case2:
shwnd = "00" + shwnd;
break;
case3:
shwnd = "000" + shwnd;
break;
case4:
shwnd = "0000" + shwnd;
break;
case5:
shwnd = "00000" + shwnd;
break;
case6:
shwnd = "000000" + shwnd;
break;
case7:
shwnd = "0000000" + shwnd;
break;
}
}
if(lParam == 1)
System.out.println("EnumWindows: 0x" + shwnd);else
System.out.println("EnumDesktopWindows: 0x" + shwnd);
return true;
}
}
Then before calling the EnumWindows
function, create the callback function object:
WNDENUMPROC proc = newWNDENUMPROC();
Then make a wrapper for this callback function:
CCallback enumer = newCCallback(proc,
WNDENUMPROC.class.getMethod("EnumWindowsProc",
newClass[]{int.class, int.class}), CallingConvention.Stdcall);
Finally, call the EnumWindows
function that was defined in the library object user32
:
user32.EnumWindows(enumer, 1);
Native Coding Can Be Simple
Those who wrote code in managed C++ with unmanaged extensions should find much in common with the Java Native Interface. In contrast to managed C++, JNI has many low-level features that make coding and debugging very complex. These are:
- Java references (local, global, weak), which should be handled in JNI code properly and in most cases are not clear for a regular Java developer
- Array marshaling: In Java forums, I always find questions about how to get or pass back a Java array, especially multi-dimensional
- Java method invoking, where the Java developer should know how to write a method or field signature, etc.
That is, the high-tech industry should hire highly qualified and also highly paid persons who know Java, C++, Pascal, Assembler, etc. and also JNI programming. I omit other things specific to a proper company. However, Java providers do not bother with this issue when developing sophisticated Java libraries in order to eliminate JNI use. They should think about Java extensions for calling native functions directly from Java code, without implementation of Java class native methods. This will give Java developers an option of native, system dependent function use in Java without JNI. Java developers could write only Java code and C++ (Pascal, Assembler, etc.) programmers will write/debug pure native modules that have nothing to do with JNI.
Points of Interest
The JavaPInvoke SDK can be used for the development of a Java-COM bridge in Microsoft Windows environment. Try to implement the IUnknown
and IDispatch
interfaces. Then you will be able to easily create a COM object and call its native functions. Do not forget to call CNativeLoader.OleActivate()
in the Main
procedure.
Other Resources
- IBM, SUN JDK1.5.x and higher
- Java Platform Invoke is available here
References
History
- 24 July, 2007 -- Original version posted
- 4 May, 2009 -- Article updated