OC Systems

Home | Contact | Advanced Search 

Technology

This document explains how to call PowerAda-compiled native Ada code from Java using the Java Native Interface (JNI) framework.

Introduction

Ada code compiled by PowerAda can be called from a Java Virtual Machine (JVM) using the normal built-in mechanisms within both programming languages. This allows Java to reuse libraries of existing code that were written in Ada.

This technique allows Java and Ada to coexist, so Java programs can invoke native Ada object code. Java Native Interface (JNI) enables Java code to directly call non-Java native code written in Ada, C/C++, or assembly. Intermediate bridging code is not needed.

The Ada side will use pragma Export for its externally callable subprograms. The Java side will define a matching method and designate it as "native". The JVM will automatically use normal (native Ada/C) function calling conventions, including parameter passing, when calling this method.

The Ada subprogram specification needs a minor parameter change, as explained below. The Ada subprogram body can remain unchanged, and is still callable from anywhere (e.g. Ada, Java, C/C++, assembly, etc.).

The JNI framework allows bi-directional calling. It also permits native Ada code to manipulate existing Java objects, and to create new Java objects.

Examples are provided (below) showing how a Java main program running in a JVM can invoke Ada subprograms compiled by PowerAda.

The reverse scenario is also possible, in which an Ada main program can invoke a JVM and call its native methods. We won't show that here.

Prerequisites

PowerAda - You must have the PowerAda implementations of the packages Interfaces.Java, Interfaces.Java.JNI, and Interfaces.Java.JNI.Strings. These are available from OC Systems and included in PowerAda versions 5.7d and newer.

Java - You must have a 32-bit JDK (not just JRE) which includes the tools javac and javah, and the header file include/jni.h and supporting target-specific header files.

Resources

Please contact support@ocsystems.com with questions or comments.

In addition, you may find the following links helpful:

JNI Mechanism

Java has a Java Native Interface (JNI) mechanism to invoke native object code. Native means foreign Ada/C/C++/assembly code that's external and foreign to the JVM. The JNI framework is automatic, and is bi-directional.

An Ada subprogram declared with pragma Export can be called from the Java VM. The Ada subprogram specification must abide by JNI conventions for its symbol name and parameter passing. This may require slight programming changes (i.e., adding 2 parameters) to the Ada specification, but its Ada subprogram body can remain unchanged.

In Java:

       native int MyAction ();

In C:

  JNIEXPORT jint JNICALL Java_MyClass_MyAction
    (JNIEnv *, jobject);

In Ada:

       function My_Action (Env : JNI_Env_Access;
                           Obj : J_Object) return J_Int;
       pragma Export (C, My_Action, "Java_MyClass_MyAction");

A corresponding Java method with the same signature needs to be defined as "native" within a Java class. Java will treat the method's implementation (body) as externally defined, to be found at run-time via its unique global symbol name. Calls within the JVM to the native method will automatically invoke the Ada subprogram body via the JNI framework, using normal native Ada function calling conventions. Subprogram parameters and return values can be passed, both ways, between Java and Ada language environments.

The subprogram's enclosing Ada library unit (eg, package) or enclosing Java class are almost unaffected by this JNI mechanism, since their names and other content do not matter, and they do not need to coordinate. There is no connection nor correlation needed between them. The only connection is made at the method/subprogram level, and is established when the JVM loads the Ada shared object containing Ada subprogram body.

PowerAda can create the Ada shared object, containing the Ada object code. The JVM can dynamically load this shared object at run-time, and then call it.

A method in a Java class can be defined to use the JNI framework. The method's signature (its function name and parameter list) would be defined in Java, as usual, but with the additional keyword "native". This tells Java that the method's body was written in a foreign language, and that calls to the method must use the same calling conventions as native Ada/C/C++/assembly code, per the Application Binary Interface (ABI) of the target machine.

The Ada subprogram body that implements the Java method must be declared with pragma Export, and its exported symbol name must match the special name chosen by the JNI, as dictated by its method signature. The Ada subprogram's parameter profile (signature) must match that of the corresponding Java method, as augmented with the 2 extra parameters added to the Ada side.

Some data runtime representation (layout) may differ between Ada and Java. The JNI framework provides methods to convert Java data values between JVM and native representations.

Ada definitions corresponding to Java data types and JNI framework are defined in the predefined PowerAda runtime package Interfaces.Java.JNI. The package Interfaces.Java.JNI.Strings provides conversion functions to and from Ada and Java Strings.

The JNI mechanism does impose some run-time overhead, which may affect JVM performance. In general, native code runs faster than JVM code. There is no JNI overhead on the Ada side.

JNI Method Name Resolution

The JNI chooses a unique special name for each native method, and requires the corresponding foreign subprogram to use that name. In Ada, the special name must be specified in the subprogram's pragma Export.

The JNI native method naming convention is the concatenation of the following:

  • the prefix "Java_"
  • the fully qualified class name
  • an underscore "_" separator
  • the method name
  • OPTIONAL: two underscores "__" and encoded argument signature of the method.

Briefly, the JNI native method naming convention looks like this:

  • Java_classname_methodname or
  • Java_classname_methodname__signature

The "__signature" part can be omitted unless the Java methods are overloaded.

The JNI uses the Java VM's representation of argument types in signatures. The encoded argument signature uses the following character (or character sequence) to represent each Java argument of the native method:

Java_Type Description Signature_Encoding_Char
jboolean unsigned 8 bits Z
jbyte signed 8 bits B
jchar unsigned 16 bits C
jshort signed 16 bits S
jint signed 32 bits I
jlong signed 64 bits J
jfloat 32 bits F
jdouble 64 bits D
void   V
jarray   [ element-type
jobject   L fully-qualified-classname ;

These last 2 argument signatures encodings are special because they require further info.

An array is denoted by the "[" character followed by the signature encoding character of the array's element-type. For example, "[I" means an jint array, and "[F" means an array of jfloats.

A jobject is denoted by the "L" character followed by the fully qualified classname and then a semicolon. This uniquely denotes the name of its class. For example, the signature "Ljava/lang/String;" denotes the class java.lang.String.

Some characters are invalid for C function names, so JNI uses a Unicode character translation for these situations. The underscore "_" character followed by a digit (0 through 3) serves as an escape-sequence for this translation:

Escape-Sequence Denotes
_0XXXX a Unicode character XXXX
_1 the character "_"
_2 the character ";" in signatures
_3 the character "[" in signatures

Here's an example of the above JNI naming rules (MyClass.java):

  class MyClass
  {
     native int MyAction ();

     static
     native void SayHello (String[] S, byte X, float T, int N);
     native void SayHello (String[] S, byte X, float T, int N, int K);
     native void Greeting (String[] S, byte X, float T, int N, int K);
  }

The "javah MyClass" command will produce a file (MyClass.h) containing these C function prototypes. Notice that function names include a signature part only for overloaded methods, along with Unicode translation to create valid C function names:

  /*
   * Class:     MyClass
   * Method:    MyAction
   * Signature: ()I
   */
  JNIEXPORT jint JNICALL Java_MyClass_MyAction
    (JNIEnv *, jobject);

  /*
   * Class:     MyClass
   * Method:    SayHello
   * Signature: ([Ljava/lang/String;BFI)V
   */
  JNIEXPORT void JNICALL Java_MyClass_SayHello___3Ljava_lang_String_2BFI
    (JNIEnv *, jclass, jobjectArray, jbyte, jfloat, jint);

  /*
   * Class:     MyClass
   * Method:    SayHello
   * Signature: ([Ljava/lang/String;BFII)V
   */
  JNIEXPORT void JNICALL Java_MyClass_SayHello___3Ljava_lang_String_2BFII
    (JNIEnv *, jobject, jobjectArray, jbyte, jfloat, jint, jint);

  /*
   * Class:     MyClass
   * Method:    Greeting
   * Signature: ([Ljava/lang/String;BFII)V
   */
  JNIEXPORT void JNICALL Java_MyClass_Greeting
    (JNIEnv *, jobject, jobjectArray, jbyte, jfloat, jint, jint);

Notice the "__signature" part exists only when Java methods are overloaded, and is usually omitted otherwise.

Also, the "_3" sequence is the JNI Unicode character translation for the UTF character "[" within signatures. Similarly, "_2" sequence is for Unicode character ";" within signatures.

JNI Parameter Passing

Parameters can be passed directly between Java and Ada, per ABI. This is same as usual, similar to Ada and C/C++ parameter passing.

All (non-Java) native methods called via JNI require 2 mandatory parameters in addition to those specified by the Java method's signature: the JNI Environment reference and the method or (static) class reference. These 2 parameters are absent (implicit) on the Java side, but are required on the Ada side.

The JNI Environment is always the first parameter. It's a pointer to another pointer, which in turn points to a (per-thread) function-table structure. The function-table contains pointers to each of the special predefined JNI interface functions that can be called from the foreign (non-Java) code. The JNI specification defines the order and contents of the function-table.

The second argument differs depending on whether the native method is static or nonstatic. The 2nd argument to a static native method is a reference to its Java class. The 2nd argument to a nonstatic native method is a reference to the Java object whose method was called. It's similar to a C++ "this" parameter.

For example, here are static and nonstatic Java native methods and their matching Ada functions:

In Java:

      static native int MyAction1 (int i);
             native int MyAction2 (int i);
In Ada:
      function My_Action1 ( Env : JNI_Env_Access;
                            Cls : J_Class;             -- for static method
                            Num : J_Int) return J_Int;
      function My_Action2 ( Env : JNI_Env_Access;
                            Obj : J_Object;            -- for nonstatic method
                            Num : J_Int) return J_Int;

The JNI always passes these 2 extra mandatory parameters before the other parameters of the method. They provide access to the JVM internal database and access to certain Java interface (callback) functions that are useful for mapping Java data values to native Ada (or C/C++) representation.

You may have to consider possibly different data type representation on Ada vs. Java side. This occurs in any heterogenious programming environment, such as Ada vs. C.

Ada elementary (scalar and access) types have the same runtime representation as their corresponding Java primitive types, but composite (array and record) types do not. Notice that each Java char value is 16-bit Unicode, which in Ada corresponds to Standard.Wide_Character, not Standard.Character. Passing parameters of Ada elementary types is simplest, and requires no special treatment. They are passed by copy. Parameters of other types, such as Ada composite types and Java classes, are passed by reference, so you must call the appropriate JNI interface function to "pin" a Java object and fetch its referenced contents.

Strings in Java are a particular concern because their Java layout differs from both Ada strings and C/C++ strings. The JNI and JVM use modified UTF-8 strings. The JNI framework provides interface functions to convert them to the appropriate layout, and to pin them so the Java garbage collector won't move them while the native is still using them.

n general, Java objects are thread-specific local references when they are either passed to a native method or returned by JNI functions. Local references are valid only within the current thread, and valid only for the duration of the native method call. The Java objects are automatically garbage collected after the native method returns. This means the native (Ada/C/C++) must be careful to avoid holding onto, and reusing, a Java local reference.

The JNI provides a method to create global references from local references. Global references remain valid until they are explicitly freed. A native function may return either a local or global reference.

The JNI provides methods to access arrays of primitive types, or copy them to an Ada buffer, so that Ada functions can operate most efficiently.

JNI Other Issues

Garbage collection by the JVM is limited to the JVM side, so the native Ada/C/C++ code is responsible for freeing up any memory resources it acquires.

The JNI automatically keeps track of all Java objects that have been passed to the native code, to prevent the JVM garbage collector from reclaiming or moving the objects while still in use by the native code. The native code, in turn, must inform the JVM (via JNI callbacks) when it no longer needs an object, to resume its garbage collection.

A JNI interface environment pointer value is valid only for the current thread, but not for any other threads. New threads must call AttachCurrentThread() to obtain their own JNI interface environment pointer before using it, and must call DetachCurrentThread() upon thread exit.

Signal handling requires chaining the native Ada signal handlers, to forward any signals intended for the JVM.

The JNI does not check for programming errors, such as bad parameter values.

The JNI defines many callback functions, to allow Ada native programs to interact with the JVM. For example, there are callbacks for handling or throwing exceptions, for dynamically locating methods or fields of a Java class, for registering native methods with a Java class, for fetching JVM info in multi-threaded applications, and so on.

JNI function table callbacks allow native code to raise or handle arbitrary Java exceptions. Ada exceptions won't cross into Java, but a corresponding Java exception can be thrown from an Ada exception handler.

JNI Example 1 : Passing Primitive Types

This example declares an Ada specification (foo_jni.ads) and body (foo_jni.adb) as an exported procedure to be called from Java. Notice its 2 extra parameters, as required by Java JNI, even if the Ada body won't use them.

  with Interfaces.Java.JNI;
  use  Interfaces.Java.JNI;
  package Foo_JNI is

    procedure Bar (Env : JNI_Env_Access;
                   Obj : J_Object;
                   P1  : J_Int);

    pragma Export (Convention    => C,
                   Entity        => Bar,
                   External_Name => "Java_Foo_bar__I");  -- name chosen by JNI
  end Foo_JNI;

  with Text_Io;
  package body Foo_JNI is

    procedure Bar (Env : JNI_Env_Access;
                   Obj : J_Object;
                   P1  : J_Int) is
    begin
      Text_Io.Put_Line ("Hello from Ada Foo_JNI.  P1 =" & J_Int'Image( P1 ));
    end Bar;

  end Foo_JNI;

Use PowerAda to compile and link the Ada code (above) into a shared object. This example uses the following dummy main (shared_main_foo.adb) to create the Ada shared object, named either libfoo.so (on Linux) or else libfoo.a (on AIX):

   with Foo_JNI;
   procedure Shared_Main_Foo is   -- dummy main for linking Ada shared object
   begin
      null;
   end;
This linker exports list is also needed when linking the Ada shared object:
  $ echo "Java_Foo_bar__I" > shared_ada_foo.exports_list
PowerAda compilations:
  $ ada foo_jni.ads foo_jni.adb   ### pkg Foo_JNI spec & body
Linking Ada shared object on AIX:
  $ ada -sm shared_main_foo.adb -o libfoo.a \     ### create Ada shared object
      -i-bE:shared_ada_foo.exports_list

Linking Ada shared object on Linux:

  $ ada -sm shared_main_foo.adb -o libfoo.so      ### create Ada shared object

On the Java side, the corresponding Java method was declared to be "native", and with only 1 parameter. This example also defines a public main() method solely for the purposes of creating a stand-alone Java application.

  class Foo
  {
     native void bar (int i);  // This foreign function was written in Ada
                               // its dynamic symbol name will be
                               "Java_Foo_bar"

     static
     {
        // Link with this Ada shared object (libfoo.so / libfoo.a), at runtime:
        System.loadLibrary("foo");
        // or:  System.load("/mypath/libfoo.so")
     }

     public static void main (String[] args)
     {
        System.out.println("In Java main.");
        new Foo().bar (100);        // call Ada function
     }
  }

Compile and run the Java application (get execution results):

  $ javac Foo.java              ### compile
  $ java  Foo                   ### run Foo.class in JVM

  In Java main.
  Hello from Ada Foo_JNI.  P1 = 100

To check the JNI symbol names for Java methods within class Foo, use the "javah" command line tool. For example, "javah -jni Foo" generates a file named "Foo.h" containing this C prototype:

  #include <jni.h>
  JNIEXPORT void JNICALL Java_Foo_bar (JNIEnv *, jobject, jint);

Notice the keywords (JNIEXPORT and JNICALL) are only for the C/C++ compiler, not for PowerAda. Notice the JNI chose a native function name "Java_Foo_bar", and added the two extra input parameters ahead of the method's original integer parameter. The "__I" suffix would be required by the JNI only if the method was overloaded, but is optional otherwise. So, in the absence of overloading, the corresponding Ada pragma Export could optionally use either the simpler "Java_Foo_bar" or the full name "Java_Foo_bar__I".

JNI Example 2 : Accessing & Creating Java Objects

This example declares an Ada specification (foobar_jni.ads) and body (foobar_jni.adb) as an exported procedure to be called from Java. Its 2 extra JNI parameters will allow this Ada body to make callbacks to Java VM to perform String conversion and storage management. The details of these callbacksare taken care of by the supporting package Interfaces.Java.JNI.Strings, whose implementation is shown after this example.

  with Interfaces.Java.JNI; use Interfaces.Java.JNI;
  package Foobar_JNI is
    --
    --  Java Class:     Foobar
    --  Java Method:    hello
    --  Java Signature: (Ljava/lang/String;I)Ljava/lang/String;
    --
    function Hello (Env : JNI_Env_Access;
                    Obj : J_Object;
                    You : J_String;
                    Age : J_Int) return J_String;

    pragma Export (Convention    => C,
                   Entity        => Hello,
                   External_Name => "Java_Foobar_hello");
  end Foobar_JNI;

  --------------------------------------------------

  with Text_Io;
  with Interfaces.Java.JNI.Strings;
  package body Foobar_JNI is
 
     -- string conversion functions:
     package JStrings renames Interfaces.Java.JNI.Strings;
 
    -- Example of bi-directional calls between Java VM and Ada
 
    function Hello (Env : JNI_Env_Access;
                    Obj : J_Object;
                    You : J_String;
                    Age : J_Int) return J_String
    is
    begin
      Text_Io.Put_Line ("Ada Foobar_JNI (start)" );
 
      -- Convert C_Name to an Ada style string and print it, along with Age:
      Text_Io.Put_Line ("Ada Foobar_JNI says hello to " &
                        JStrings.To_Ada_String(Env, You) &
                        ", age =" & J_Int'Image (Age));
 
      -- pass J_String reference back to JVM
      return JStrings.To_J_String(Env, "cool beans!");
    end Hello;
 
  end Foobar_JNI;

PowerAda will create the Ada shared object, as either libfoobar.so (on Linux) owerAda will create the Ada shared object, as either libfoobar.so (on Linux) or else libfoobar.a (on AIX):

  with Foobar_JNI;
  procedure Shared_Main_Foobar is   -- dummy main for linking Ada shared object
  begin
    null;
  end;

This linker exports list is also needed when linking the Ada shared object:

  $ echo "Java_Foobar_hello" > shared_ada_foobar.exports_list

The PowerAda compilations are:

  $ ada foobar_jni.ads            ### Foobar_JNI pkg spec
  $ ada foobar_jni.adb            ### Foobar_JNI pkg body

Linking Ada shared object on AIX:

  $ ada -sm shared_main_foobar.adb -o libfoobar.a \
      -i-bE:shared_ada_foobar.exports_list

Linking Ada shared object on Linux:

  $ ada -sm shared_main_foobar.adb -o libfoobar.so

On the Java side, the corresponding Java method "hello()" was declared to be "native". This example also defines a public main() method solely for the purposes of creating a stand-alone Java application. The JVM will call method hello(), which is an Ada function body, and will print the function results.

class Foobar
{
   // This foreign function was written in Ada
   // its dynamic symbol name will be "Java_Foobar_hello"

   native String hello (String name, int age);

   static
   {
      // Link this Ada shared object (libfoobar.so / libfoobar.a), at runtime:
      System.loadLibrary("foobar");
   }

   public static void main (String[] args)
   {
      String reply = null;
      Foobar fb    = new Foobar();

      System.out.println ("In Java main.");
      reply = fb.hello ("JVM_caller", 42);            // call Ada function
      System.out.println ("JVM received " + reply);
   }
}

To check the foreign JNI symbol names for Java methods within class Foobar, use the "javah" command line tool. For example, "javah -jni Foobar" generates a file named "Foobar.h" containing this C prototype:

  #include <jni.h>
  JNIEXPORT jstring JNICALL Java_Foobar_hello(JNIEnv *, jobject, jstring,
  jint);

Here, the JNI chose the function name "Java_Foobar_hello", which was used in the corresponding Ada subprogram's pragma Export.

Compile and run the Java application (get execution results):

  $ javac Foobar.java                     ### compile
  $ java  -Djava.library.path=.  Foobar   ### run Foobar.class in JVM

  In Java main.
  Ada Foobar_JNI (start)
  Ada Foobar_JNI says hello to JVM_caller, age = 42
  JVM received cool beans!

Ada/Java String Conversions

The functions used in the example above may be of technical interest, so their implementation is shown here:

  package body Interfaces.Java.JNI.Strings is
 
     package CStrings renames Interfaces.C.Strings;
      
     -- return an Ada string equivalent in content
     -- to the given J_String
     function To_Ada_String(Env          : JNI_Env_Access;
                            JStr         : J_String)
        return String
     is
        C_Str   : CStrings.Chars_Ptr;
        Is_Copy : J_Boolean_Star;
      begin
        -- JNI callback to fetch C style of string value from the JVM:
        C_Str := Env.all.Get_String_UTF_Chars (Env     => Env,
                                               Str     => JStr,
                                               Is_Copy => Is_Copy);
        declare
           Result : constant String := CStrings.Value (C_Str);
        begin
           -- Tell JVM it's OK to move or add these to garbage collection:
           Env.all.Release_String_UTF_Chars (Env, JStr, C_Str);
           return Result;
        end;
      end To_Ada_String;
 
 
     -- return a Java String containing the characters
     -- in the given Ada, for passing back to Java
     function To_J_String(Env  : JNI_Env_Access;
                          AStr : String)
       return J_String
     is
        C_Str  : CStrings.Chars_Ptr := CStrings.New_String (AStr);
        Result : J_String;
      begin
        -- JNI callback to create a Java style of string value within the JVM:
        Result := Env.all.New_String_UTF (Env, C_Str);
        CStrings.Free (C_Str);   -- deallocate memory on Ada side
        return Result;          -- pass J_String reference back to JVM
      end To_J_String;
 
  end Interfaces.Java.JNI.Strings;

Notice how the Ada subprogram body made callbacks to Java using the JNI extra parameters. Most importantly, the Ada function body used the JNI function table to request further reformatted data from the Java VM, to release a JVM object for garbage collection, and to create a new JVM object, before finally returning a reference to the new JVM object.


Last updated 2013-11-20