.. _Using_JNI_Directly:

******************
Using JNI Directly
******************

This Appendix explains how to use the JNI services with Ada in the
same style as with C or C++ (i.e., with the program
making explicit calls to the JNI functions).

.. _Using_JNI_Directly_Introduction:

Introduction
============

Interfacing Ada with other languages is fairly straightforward when all
languages run in the same environment and use the same memory model.
For example, C code can use Ada entities provided that these
entities have the proper convention. Likewise, Ada can access C
entities just as easily.

However, the situation is not so simple with Java. Since Java programs
are running in a completely different environment, the Java Virtual Machine,
it is not possible to access Java directly from natively compiled Ada,
or vice versa.
All communication -- method invocation, parameter passing, data referencing --
has to go through an intermediate layer, the Java Native
Interface (JNI).

JNI -- a collection of C types and functions --
has been used since Java's inception to interface Java with
C and C++. It offers several capabilities:

* Implementing native Java methods in C or C++
* Invoking Java methods (both instance and static) from C or C++
* Referencing Java fields (both instance and static) from C or C++

This Appendix describes how to obtain these capabilities in Ada,
using an Ada binding to JNI.
This is a low-level interface and is generally not as preferable as
using the GNAT-AJIS tools, but may sometimes be useful.

The Ada binding, supplied by GNAT-AJIS in the package ``JNI``,
is a 'thin' binding to the C types and functions from  ``jni.h``,
and thus the documentation provided, for example, by
http://java.sun.com/j2se/1.4.2/docs/guide/jni/
is applicable to Ada / Java interfacing.
This Appendix is mainly an introduction to using JNI in an Ada context.
For further details please refer to the above website or to texts such as
{The Java Native Interface - Programmer's Guide and Specification}, by
Sheng Liang (Addison-Wesley, 1999).

.. _Implementing_a_Native_Method_in_Ada:

Implementing a Native Method in Ada
===================================

This section illustrates how to build a Java application where a native method
is written in Ada.  The build process consists of the following steps:

* 
  Write the Java class with the native method, and compile it

* 
  Generate an Ada specification corresponding to the native method

* 
  Write the body of the native method and compile it to a shared library
  or DLL.

* 
  Run the Java application

These steps will now be described in more detail.

.. _A_Java_class_with_a_native_method:

A Java class with a native method
---------------------------------

The following example contains a native method that is to be implemented
in Ada:


::

  public class Example1 {
     native static int sum (int a, int b);
     public static void main (String[] args) {
        System.out.println (sum (10, 20));
     }
     static {
        System.loadLibrary ("Example1_Pkg");
     }
  }
  

The library containing the native method needs to be loaded before
the method is invoked; this is conventionally accomplished by
enclosing an invocation of the ``loadLibrary`` method in a
static initializer.
The designated``lib<Example1>_Pkg``, will be created at a later step.

You can compile this Java file to a class file in the usual way; e.g.:


::

  $ javac Example1.java
  

which will generate the file ``<Example1>.class``

.. _Generating_an_Ada_specification:

Generating an Ada specification
-------------------------------

Although a native method can be implemented as a library-level
subprogram, for consistency it is probably simplest to declare
it in a package:


::

  with Interfaces.Java.JNI; use Interfaces.Java.JNI;
  package Example1_Pkg is
    function Sum (Env : JNI_Env_Access; Class : J_Class; A, B : J_Int)
      return J_Int;
    pragma Export (C, Sum, "Java_Example1_sum__II");
  end Example1_Pkg;
  

The ``Sum`` function in Ada has two parameters that are not present
in the native method signature: ``Env``, a handle on the JNI environment,
and ``Class``, a handle on the class (``Example1``) in which the
native method is defined.  These parameters are mandated by the JNI
standard (although for an instance method the 2nd parameter would be an object
handle and not a class handle).

The ``A`` and ``B`` parameters correspond to the original method
profile, using the appropriate mapping of types across the two languages.

The ``Export`` pragma must include as an argument the symbol name for the
native method, here ``Java_Example1_sum__II``, derived from its signature.
More generally, the symbol name has one of the
following forms, depending on whether the method takes parameters:

``Java_<PackageName>_<ClassName>_<MethodName>``
``Java_<PackageName>_<ClassName>_<MethodName>__<ParamsSignature>``

Please note the following:

* 
  Two consecutive ``_`` (underscore) characters
  precede the ``<ParamsSignature>`` component of the name.

* 
  The ``<PackageName>_`` component is absent if the Java class is defined
  in the default (anonymous) package.

* 
  The ``<ParamsSignature>`` is derived from the JNI method descriptor
  -- ``(II)I`` in this example --
  by removing the parentheses and dropping the result type.
  Since Java does not allow overloading based on result type, there is no
  risk of different native methods in the same class yielding the
  same symbol name.

* 
  Since Java is case sensitive, the symbol name string needs to mirror the
  case of the Java identifiers.
  The casing of the Ada subprogram identifier does not need to be the same
  as the corresponding Java method name, although it will general assist
  readability if you use the same casing.

* 
  Java's case sensitivity means that you can have different native methods,
  say ``foo()`` and ``Foo()``, with the same parameter profile.
  Since Ada is not case sensitive, you will need to declare different
  names for these subprograms, e.g. ``foo_1`` and ``Foo_2``.

The last part of the exported symbol, the parameters
signature, is optional here, since there is only one method named ``sum``
in the Java class.
It is recommended style, however, to include the parameters signature
explicitly.

Each primitive Java type has a corresponding Ada type defined in the
package ``JNI`` supplied with GNAT-AJIS:



``boolean``
  ``Interfaces.Java.JNI.J_Boolean``

``byte``
  ``Interfaces.Java.JNI.J_Byte``

``char``
  ``Interfaces.Java.JNI.J_Char``

``short``
  ``Interfaces.Java.JNI.J_Short``

``int``
  ``Interfaces.Java.JNI.J_Int``

``long``
  ``Interfaces.Java.JNI.J_Long``

``float``
  ``Interfaces.Java.JNI.J_Float``

``double``
  ``Interfaces.Java.JNI.J_Double``

Writing the JNI-compliant Ada specification manually is tedious;
GNAT-AJIS includes the ``javastub`` tool to automate this step
by generating an appropriate Ada spec from a Java class file
containing a native method to be implemented in Ada:


::

  $ javastub Example1.class
  

This command, the Ada analog to ``javah -jni`` for C, will generate the
package spec shown above.

.. _Implementing_the_native_method:

Implementing the native method
------------------------------

The Ada implementation of the native method is straightforward:


::

  package body Example1_Pkg is
    function Sum (Env : JNI_Env_Access; Class : J_Class; A, B : J_Int)
      return J_Int is
    begin
      return A + B;
    end Sum;
  end Example1_Pkg;
  

Since the ``Sum`` implementation does not need to access any entities
from the Java environment, it ignores the ``Env`` and ``Class``
parameters.

Ada semantics apply to the execution of the function.
For example, if ``A+B`` overflows, the ``Constraint_Error``
exception is raised in the
native code. Unless it is handled locally, the exception is either lost or
results in a JVM failure. Thus, reliable Ada code called from Java should always
contain an exception handler.

.. _Compiling_to_a_shared_library_or_DLL:

Compiling to a shared library or DLL
------------------------------------

The standard way to compile the Ada code is to use the ``gprbuild`` capabilities
for compilation of shared libraries. Assuming that the source files for the
code are located in a directory named ``src``, the project file will look
like:


::

  with "jni";
  with "ajis";

  project Test is

     for Object_Dir use "obj";

     for Source_Dirs use ("src");

     for Library_Name use "test";
     for Library_Kind use "dynamic";
     for Library_Dir use "lib";
     for Library_Auto_Init use "false";
     for Library_Interface use ("Example1_Pkg");

     package Compiler is
        for Default_Switches use AJIS.Compiler'Default_Switches;
     end Compiler;

     case AJIS.OS is
        when "Windows_NT" =>
           for Shared_Library_Prefix use "";
        when others =>
           null;
     end case;
  end Test;
  

Note that we're reusing the flags provided by the AJIS installation directly,
rather than defining them ourselves. In addition to the usual libraries option
described in the GNAT User's Guide, we need to say that, on Windows, the
library prefix is empty, as opposed to ``lib``.
``lib`` is the default behavior, but it would complicate the load of the
library here.

Compiling the library with ``gprbuild`` is now straightforward:


::

  $ gprbuild -P test.gpr
  

.. _Using_JNI_Directly_Running_the_program:

Running the program
-------------------

Once you have all of the components in place -- the Java class file and the
native library -- you can run the application:


::

  $ java Example1
  

results in execution of the Java statement


::

  System.out.println (Example1.sum (10, 20));
  

which displays ``30`` on the screen.

.. _Interfacing_to_an_Existing_Ada_API:

Interfacing to an Existing Ada API
==================================

The style of interfacing illustrated in the previous section
is the most direct way of using JNI to call Ada
subprograms from Java. However,
when interfacing to an existing API, you will need to supply Ada 'wrappers'
that satisfy the JNI requirements for the parameters in the
C function prototypes corresponding to native methods.

For example, suppose you would like to invoke the following Ada
subprogram from Java:


::

  function Addition (A, B : Positive) return Positive;
  

A corresponding Java native method declaration is:


::

  class Example2 {
     static native int addition (int a, int b);
  }
  

and then a 'wrapper' in Ada is necessary, corresponding to the subprogram
that is actually called when the native method is invoked:


::

  function Addition_Wrapper (Env   : JNI_Env_Access;
                             Class : J_Class;
                             A, B  : J_Int)
     return J_Int;
  pragma Export (C, Addition_Wrapper, "Java_Example2_addition__II");

  function Addition_Wrapper (Env   : JNI_Env_Access;
                             Class : J_Class;
                             A, B  : J_Int)
     return J_Int is
  begin
     return J_Int (Addition (Positive (A), Positive (B)));
  end Addition_Wrapper;
  

As a point of style, when invoking a native Ada method whose formal
parameters are constrained (here of subtype ``Positive``) you should
ensure that the actual parameters satisfy the constraints.
Otherwise the resulting constraint violation will either fail silently or
crash the JVM.

In the above example, the wrapper function is ignoring the ``Env`` and
``Class`` parameters.  Later examples will show how these parameters
can be used, when the Ada subprogram needs to access entities from
the Java side.

.. _Calling_a_Java_Method_from_Ada:

Calling a Java Method from Ada
==============================

The ``JNI`` package allows you to
invoke Java methods from Ada. For example:


::

  class Example3 {
     static int addition (int a, int b) {
        return a + b;
     }
  }
  

The natural corresponding Ada subprogram has the profile:


::

  function Addition (A, B : J_Int) return J_Int;
  

Implementing this subprogram to invoke the Java method requires
dealing with several issues.

First, the code has to execute properly in the
context of the current Java thread, and for this to happen a call to
``Attach_Current_Thread`` is needed if it hasn't been done yet. This
call also requires a handle on the virtual machine itself that is
represented by the variable ``Main_VM``:


::

     Attach_Current_Thread (Main_VM, Env'Access, System.Null_Address);
  

Second, you need to obtain a handle on the Java method and then
invoke the method through the handle.
A method handle is of type ``J_Method_ID``. It is initialized through
the function ``Get_Method_ID``, declared as follows:


::

  function Get_Method_ID
     (Env     : JNI_Env_Access;
      Class   : J_Class;
      Name    : String;
      Profile : String) return J_Method_ID;
  

A handle to the class is needed as well. It can be obtained
via ``Find_Class``, declared as follows:


::

  function Find_Class
     (Env : JNI_Env_Access; Name : String) return J_Class;
  

Thus, the call sequence starts with:


::

     Class := Find_Class (Env, "LExample3;");
     Addition_ID := Get_Method_ID (Env, Class, "Addition", "(II)I");
  

Note the differences between the class name above and the relevant
part of the Linker_Name in the export Pragma for procedure
``Addition_Wrapper`` in the previous section. ``Example3`` appears as
``Example3`` in one case and ``LExample3;`` in the other.
Similarly, the profile
appears as ``II`` in one case and ``(II)I`` in the other.
Those differences are
explained in the official JNI documentation.

The final step is to invoke one of the JNI functions for calling Java
methods. There are a several of these, each of them handling a special kind
of return type. Here, we are interested in
``Call_Static_Int_Method_A``, which returns a ``J_Int`` and
works on static subprograms. Its profile is:


::

  function Call_Static_Int_Method_A
     (Env        : JNI_Env_Access;
      Object     : J_Class;
      Method_ID  : J_Method_ID;
      Args       : J_Value_Array) return J_Int;
  

Parameters are passed to the method using a
``J_Value_Array``, which is an array of ``J_Value`` elements. A
``J_Value`` is a discriminated record that can hold any of the
``J_`` types. Two integers can be passed with the following code:


::

      Result := Call_Static_Int_Method_A
        (Env, Class, Addition_ID, J_Value_Array'((Jint, 23), (Jint, 42)));
  

Here is the complete code for the Ada wrapper function:


::

     function Addition (A, B : Integer) return Integer is
        Env : aliased JNI_Env_Access;
        Class : J_Class;
        Addition_ID : J_Method_ID;
        Result : J_Int;
     begin
        Result      := Attach_Current_Thread
                         (Main_VM, Env'Access, System.Null_Address);
        Class       := Find_Class (Env, String'("LExample3;"));
        Addition_ID := Get_Method_ID (Env, Class, "addition", "(II)I");
        Result      := Call_Static_Int_Method_A
                         (Env,
                          Class,
                          Addition_ID,
                          ((Jint, J_Int (A)), (Jint, J_Int (B))));
        return Integer (Result);
     end Addition;
  

.. _Using_Ada_Objects_from_Java:

Using Ada Objects from Java
===========================

Consider the following Ada record:


::

  type Storage is record
     A, B, C : Integer;
  end record;
  

Suppose we would like to manipulate objects of this type in Java. Let's
consider the following API:


::

  function Create return Storage;
  --  Return an object of type Storage.

  function Compute (S : Storage) return Integer;
  --  Return the sum of the elements stored in Storage.
  

The first issue is how to pass an Ada object to Java.
Given the fundamental difference in execution environments, objects
cannot simply be passed by reference as is commonly done in Ada/C
interfacing. There are two possible approaches:
either marshall/unmarshall values using
an intermediate form, such as a string, each time the language boundary is
crossed, or else manipulate the object in its native language while
the other language accesses it through a handle. Since the first possibility
is both complex and costly, let's look at the second alternative.

On the Ada side, a handle is represented as an access value pointing to a
heap-allocated object. On the Java side, it cannot be represented as a
Java reference, because the Java heap is managed differently from
the Ada heap -- most importantly, the Java heap is garbage collected.
Therefore, unchecked conversion is used to convert in both directions between
the Ada access value and a Java ``int`` (``J_Int``).

(Note: in this example, we assume that access values are 32 bits,
which is not always the case. A real example would need to deal
with this issue.)

Here is the Java interface corresponding to the above API:


::

  class Storage {
     public native static int Create ();
     public native static int Compute (int S);
  }
  

This can be used naturally as:


::

  int myStorageObject = Storage.Create ();
  int result = Storage.Compute (myStorageObject);
  

Let's see the glue code needed to make this work. First, let's create the Ada
analogs of the Java routines above using the methods shown in previous
sections:


::

  function Create (Env : JNI_Env_Access; Class : J_Class) return J_Int;
  pragma Export (C, Create, "Java_Storage_Create__");

  function Compute
     (Env : JNI_Env_Access; Class : J_Class; S : J_Int) return J_Int;
  pragma Export (C, Compute, "Java_Storage_Compute__I");
  

Since the original Ada function ``Create`` directly returns a
value as opposed to a handle on this value, the wrapper function has
to create an instance of this object that can be referenced. Here is a
possible implementation:


::

  type Storage_Access is access all Storage;
  procedure Convert is new Ada.Unchecked_Conversion (Storage_Access, J_Int);

  function Create (Env : JNI_Env_Access; Class : J_Class) return J_Int is
     Obj : Storage_Access := new Storage'(Create);
  begin
     return Convert (Obj);
  end Create;
  

The code allocates the object on the heap, initialized with the result of the
original ``Create`` function. In a real application, the API
would need to be augmented with a routine that reclaims the
memory when the object is no longer used.

The implementation of the ``Compute`` wrapper illustrates
how the handle can be converted back and used in its native context:


::

  procedure Convert is new Ada.Unchecked_Conversion (J_Int, Storage_Access);

  function Compute
    (Env : JNI_Env_Access; Class : J_Class; S : J_Int) return J_Int is
     Obj : Storage_Access := Convert (S);
   begin
     return J_Int (Compute);
  end Compute;
  

One issue with this approach is that type safety is not preserved
when crossing the language boundary. The ``Compute`` function accepts any
parameter of type ``int``, but it can only process properly
those ``int``s that are returned by ``Create``. The
situation can be slightly improved, at least on the Java side, by providing
the following overloadings of ``Create`` and ``Compute``:


::

  class Storage {
     private int addr;

     public void Create () {
        addr = Create;
     }

     public int Compute () {
        return Compute (addr);
     }

     private native static int Create;
     private native static int Compute (int S);
  }
  

which can be used as follows:


::

  Storage myStorageObject = new Storage ();
  myStorageObject.Create ();
  int result = myStorageObject.Compute ();
  

Now it is guaranteed that ``Compute`` will be used only with
objects created by ``Create``.

Using Java Objects from Ada
===========================

Let's examine the opposite direction, where a Java class is used from Ada:


::

  class Storage {
     int A, B, C;

     public static Storage Create () {
        Storage obj = new Storage;

        obj.A = 1;
        obj.B = 2;
        obj.C = 3;

        return obj;
     }

     public int Compute () {
        return A + B + C;
     }
  }
  

We would like to create an object of this type in Ada and
call its primitives such as the ``Compute`` subprogram.

Let's first create Ada wrappers around ``Create`` and ``Compute``.
Once again,
we need to find the proper representation for the handle to the actual object.
Conveniently, JNI offers a build-in type, ``J_Object``, which  represents
references to any Java objects. Therefore, here is what
``Create`` would look like:


::

  function Create return J_Object is
     Env         : aliased JNI_Env_Access;
     Class       : J_Class;
     Create_ID   : J_Method_ID;
     Parameters  : J_Value_Array (1 .. 0);
     Result      : J_Object;
  begin
     Attach_Current_Thread (Main_VM, Env'Access, System.Null_Address);
     Class  := Find_Class (Env, "LStorage;");
     Create_ID
            := Get_Method_ID (Env, Class, "Create", "()LStorage;");
     Result := Call_Static_Object_Method_A
       (Env, Class, Addition_ID, Parameters);
     return Result;
  end Addition;
  

The structure of this subprogram is very close to the one shown in the
previous section. Here it directly returns an object reference
instead of an integer representing the address. This is
why the parameter profile is a bit different: the returned type is a
``Storage`` instance. Furthermore, the calling method is
``Call_Static_Object_Method_A`` instead of
``Call_Static_Int_Method_A``.

Similarly, the wrapper for the ``Compute`` function looks like:


::

  function Compute (This : J_Object) return J_Int is
     Env          : aliased JNI_Env_Access;
     Class        : J_Class;
     Compute_ID   : J_Method_ID;
     Parameters   : J_Value_Array (1 .. 0);
     Result       : J_Int;
  begin
     Attach_Current_Thread (Main_VM, Env'Access, System.Null_Address);
     Class  := Find_Class (Env, "LStorage;");
     Compute_ID
            := Get_Method_ID (Env, Class, "Compute", "()I");
     Result := Call_Integer_Method_A (Env, This, Compute_ID, Parameters);
     return Result;
  end Addition;
  

Here is how this API can be used on the Ada side:


::

  declare
     My_Storage_Object : J_Object;
     Result : J_Int;
  begin
     My_Storage_Object := Create;
     Result := Compute (My_Storage_Object);
  end;
  

Note once again the loss of type safety in crossing the language boundary.
There is no static check ensuring that a ``Storage`` object is
indeed passed to ``Compute``. Here is a possible way to reintroduce
partial type safety:


::

  type Storage is new J_Object;

  function Create return Storage;
  function Compute (S : Storage) return J_Int
  
