Skip to main content

Invoking Assembly Language Programs from Java

October 19, 2006

{cs.r.title}







The Java Native Interface (JNI) provides a powerful platform for integrating code written in languages other than Java--mainly C and C++--with that written in the Java programming language. Although, theoretically speaking, JNI does provide a fairly generalized interface, the support structure that comes with JNI is basically aimed at linking C/C++ code with Java code. The literature that is available also appears to deal exclusively with the methodology of linking Java and C/C++.

This article demonstrates the techniques that allow Java code to call code written in assembly language. The version of assembly language used for writing the illustration code is MASM32. I assume that the reader is familiar with assembly language programming in general and with MASM32 in particular. Familiarity with the Java programming language is also assumed. The resource section lists some material that I have relied upon and have learned from. I hope the interested reader too will derive substantial benefit from these resources.

Before proceeding further, there is one point I would like to mention: the approach and the example shown here are basically for Windows platforms. Within this family, however, they work without any modifications. I have personally tried them out on Win 98/2000/XP and have not faced any problem.

As I have already mentioned, the supporting elements available with JNI are designed for C and C++. For a developer using assembly language, it is necessary to understand clearly how JNI exposes the interfaces. So let us take a quick look at some of the internal details of JNI.

The JNI Approach

When a Java method calls assembly language code, some information will almost always have to move from one environment to the other. The calling method will usually pass parameters to the called function and the called function may return some information to the caller. In addition to this, each environment requires information about the other to be able to work together. The problem is that data representation within the Java Virtual Machine (JVM) is different from that in the assembly language environment. Also some information, especially within the JVM, is of a specialized nature and there is no provision in native languages (C/C++/assembly) to directly access such information. JNI provides a rich set of interface functions that facilitate exchange of such data by providing access to the internal database of the JVM and by providing the required mapping from the data type of one environment to the corresponding data type of the other.

JNI also has certain other support structures that make it easy for C and C++ programs to call these interface functions. Unfortunately, these support mechanisms are not directly usable by assembly language programs. The assembly language programmer, therefore, needs to understand how the interface functions can be directly accessed, and an appreciation of the structure of JNI is necessary to achieve this understanding.

JNI Structure

Whenever a Java program calls a native method, the called method compulsorily receives two parameters in addition to those specified by the calling method. The first is the JNIEnv pointer and the second is a reference to the calling object or class. It is the first parameter that is the key to the world of JNI.

JNIEnv is a pointer that, in turn, points to another pointer. This second pointer points to a function table that is an array of pointers. Each pointer in the function table points to a JNI interface function. In order to call an interface function, we have to determine the value of the corresponding entry in the function table. Let us see how we can do this in two steps.

First we find out what the value of the second pointer in our chain is. In other words, we get the contents of the location pointed to by JNIEnv. We do that as follows:

mov ebx, JNIEnv
mov eax, [ebx]

The first instruction loads the contents of JNIEnv into ebx and the second loads the contents of the address pointed to by ebx into eax. Since the contents of ebx is the same as that of JNIEnv, eax now has the contents of the location pointed to by JNIEnv--the second pointer referred to above. Which means eax now contains the starting address of the function table.

Next we need to retrieve the contents of the entry in the function table that corresponds to the function we want to call. To do this, we have to multiply the zero based index of the function (see Sheng Liang's book) by four--since each pointer is four bytes long--and add the result to the starting address of the function table which we have formed in eax earlier. We do it thus:

mov ebx, eax    ; save pointer to function table
mov eax, index  ; move the value of index into eax
mov ecx, 4
mul ecx         ; multiply index by 4
add ebx, eax    ; ebx points to the desired entry
mov eax, [ebx]  ; eax points to the desired function

The contents of eax can now be used to call the function.

This scheme of accessing JNI interface functions is shown in Figure 1.

Accessing JNI Functions
Figure 1. Accessing JNI functions

An Example

To see how the above technique can be used to call an assembly language program, let us consider a simple example. In our example a Java class (ShowMessage) calls assembly language code to display a Windows message box. If the message box is displayed, then the assembly language code returns a string to tell the calling class that it was successful. Otherwise, an error message is returned. In either case, the calling class prints the returned string on the console.

The Java class looks like this:

class ShowMessage
{
   public native String HelloDLL(String s);

   static
   {
      System.loadLibrary("hjwdll");
   }

   public static void main(String[] args)
   {
      ShowMessage sm = new ShowMessage();
      String returnMessage = sm.HelloDll("Hello, World of JNI");
      System.out.println(returnMessage);
   }
}

Those familiar with JNI will notice that the Java class is identical to what it would have been if the called native method had been written in C or C++. Which, of course, is as it should be, since the calling method need not be aware of the language used to write the called method. All that matters to the Java code is that it is calling a native method as declared in the third line of the code:

public native String HelloDll(String s);

I will not go into the structure of the Java class--both Sheng Liang and Bruce Eckel have explained it clearly. It is the assembly language code that is of interest to us, and we shall examine it in some detail:

.386
.model flat,stdcall
option casemap:none
include <pathname>\include\windows.inc
include <pathname>\include\user32.inc
include <pathname>\include\kernel32.inc
includelib <pathname>\lib\user32.lib
includelib <pathname>\lib\kernel32.lib

Java_ShowMessage_HelloDll PROTO :DWORD, :DWORD, :DWORD

;This macro returns pointer to the function table in fnTblPtr

GetFnTblPtr MACRO envPtr, fnTblPtr
   mov ebx, envPtr
   mov eax, [ebx]
   mov fnTblPtr, eax
ENDM

;This macro returns pointer to desired function in fnPtr.

GetFnPtr MACRO fnTblPtr, index, fnPtr
   mov eax, index
   mov ebx, 4
   mul ebx
   mov ebx, fnTblPtr
   add ebx, eax
   mov eax, [ebx]
   mov fnPtr, eax
ENDM


.data
   Caption      db "JAV_ASM",0
   ErrorMsg     db "String conversion error",0
   SccsMsg      db "MessageBox displayed",0

.code
hwEntry proc hInstance:HINSTANCE, reason:DWORD, reserved1:DWORD

   mov eax, TRUE
   ret

hwEntry endp

Java_ShowMessage_HelloDll proc JNIEnv:DWORD, jobject:DWORD,
Msgptr:DWORD

   LOCAL fntblptr               :DWORD
   LOCAL Message                :DWORD
   LOCAL fnptr                  :DWORD
 
   GetFnTblPtr JNIEnv, fntblptr  ; pointer to function table
   GetFnPtr fntblptr, 169, fnptr ; pointer to GETstringUTFChars

   push NULL    ; push
   push Msgptr  ; parameters for
   push JNIEnv  ; GetStringUTFChars

   call [fnptr] ; call GetStringUTFChars

   mov Message, eax

   ; if eax is NULL then error
   .if eax == NULL
      invoke MessageBox, NULL, addr ErrorMsg, addr Caption, 16

      GetFnPtr fntblptr, 167, fnptr     ; pointer to NewStringUTF

      push offset ErrorMsg              ; push parameters for
      push JNIEnv                       ; NewStringUTF

      call [fnptr]                      ; call NewStringUTF

   .else
      invoke MessageBox, NULL, Message, addr Caption, 64

      push Message
      push Msgptr
      push JNIEnv

      call [fnptr] ; release string


      GetFnPtr fntblptr, 167, fnptr     ; pointer to NewStringUTF

      push offset SccsMsg               ; push parameters for
      push JNIEnv                       ; NewStringUTF

      call [fnptr]                      ; call NewStringUTF

   .endif

   ret  ;return to Java program

Java_ShowMessage_HelloDll endp

End hwEntry

Here again, I will not dwell on the overall structure of the program (for guidance, consult Iczelion's homepage). Note that <pathname> will be determined by the directory structure of your system--on my PC it is C:\masm32. Note also that this program has been written as a .dll as shown by the hwEntry procedure at the beginning of the .code section. A .dll (Dynamic Link Library) can be linked with a Java program at run time. Needless to say, the function of a .dll does not have to be restricted to calling a Windows API; it can do whatever you want it to do. The MASM code should be saved as hjwdll.asm and, on successful assembly, hjwdll.dll and hjwdll.lib files will be created.

The first thing that we need to look at is the name of the native method as it appears in the assembly language code. HelloDll, the name used by the calling method, appears in quite a different form in the called method. This process of transformation from HelloDll to Java_ShowMessage_HelloDll is called mangling and has been explained in The Java™ Native Interface Programmer's Guide and Specification as well as in Thinking in Java. The mangled name can be derived manually by using the algorithm used by JNI or generated automatically by running javah on ShowMessage. You can do this by typing javah -jni ShowMessage at the command line. The resulting file will be ShowMessage.h and will show the mangled name. If you do use the javah approach, do not include the output file in the assembly code. The only thing to be used is the mangled name.

The real points of interest are, of course, the two macros GetFnTblPtr and GetFnPtr. These are modified versions of the code snippets introduced in the preceding section. The modifications enable the macros to operate directly on appropriate memory locations and obviate the need for manipulating input and output variables through the registers. Obtaining the pointer to the function one wants to call becomes fairly simple because of the macros.

The HelloDll procedure first gets the pointer to the function table. It then gets the pointer to the GetStringUTFChars function to convert the String object passed by the Java method into a UTF8 string that can be handled by assembly language. The parameters required for calling GetStringUTFChars are then pushed onto the stack. Note that the right-most parameter is pushed first in accordance with the stdcall convention followed by JNI. The function puts its return value in eax. If this value is NULL, then there was an error. Otherwise, a valid pointer to a UTF8 string is available in eax, which can be used to display the message passed by the Java method. After the message is displayed, the UTF8 string should be released as shown.

The native method returns one of two strings to the calling method depending on whether it succeeded or failed in displaying the message passed to it by the Java method. However, the string generated by the native method has to be converted into a Java String object before being returned. This is done by a call to NewStringUTF. Take note of the fact that the pointer to the function table needs to be derived only once in a thread. That is why it is better to split the pointer translation process into two parts so that the first part need not be executed unnecessarily over and over again.

Once you have compiled the ShowMessage class and have created the hjwdll.lib and hjwdll.dll files, put all the three files in the same folder. If you now execute ShowMessage, you will see a message box like the one in Figure 2.

Windows MessageBox called from Java
Figure 2. Windows MessageBox called from Java

Conclusion

JNI is much more than what has been shown here. This article presents one of the ways in which interaction can take place between Java code and assembly language code. But the two issues demonstrated--accessing interface functions and type conversion--not only allow Java programs to call native code but form important building blocks for all the other types of interaction, too.

Resources

  1. Sample code, classes and assembled files for this article

  2. The book I feel is a "must read" for all developers seriously interested in JNI is "The Java™ Native Interface Programmer's Guide and Specification" by Sheng Liang. This invaluable book is a free download. It is essentially oriented to C/C++ but the detailed information provided on the workings and structure of JNI is a great help in figuring out how the assembly language approach should be developed.

  3. My preferred resource for MASM32 is Iczelion's home page. The tutorials are excellent. Tutorial number 17 deals with .dlls.

  4. There are many excellent books for Java. One such book is Thinking in Java by Bruce Eckel.

  5. If you are interested in knowing more about Windows APIs, Programming Windows by Charles Petzold is a good book to read.
  6. Some of you may be wondering why should it be necessary at all to mix Java with assembly language. For an answer to this question I would like to refer you to Tal Liron's article in JavaWorld. This article not only tells you about the situations in which it would be desirable to mix native code with Java, but it also has a built-in example. Although the article is oriented towards C/C++, much of the rationale remains valid for assembly language. Additionally, keep in mind that assembly language segments may be very useful in fine-tuning programs especially with respect to timing and other operational constraints.

width="1" height="1" border="0" alt=" " />
Biswajit Sarkar is an electrical engineer with specialization in Programmable Industrial Automation. Biswajit is the author of "LWUIT 1.1 for Java ME Developers" published by PACKT Publishing.
Related Topics >> Programming   |