Gem #56: Creating Ada to Java calls using GNAT-AJIS
by Quentin Ochem —AdaCore
Let's get started…
Consider the following API:
package API is type A_Callback is access procedure (Str : String; Val : Integer); procedure Process (Str : String; C : A_Callback); end API; package body API is procedure Process (Str : String; C : A_Callback) is begin C.all (Str, 1); C.all (Str, 2); end Process; end API;
In this example, A_Callback is called twice from Process. Our goal here will be to have these calls invoke Java code.
Generating the binding code
As in the previous Gem, generating the binding code is done using the ada2java tool:
ada2java api.ads –b test –o ada –c java –L my_lib
Details on the meaning of the command arguments can be found in the earlier Gem, or in the ada2java documentation.
Writing the Java code
Looking at the generated code, you’ll see that the function Process has been bound as follows:
public final class API_Package { […] static public void Process (test.Standard.AdaString Str, test.API.A_Callback C){ […] }
with test.API.A_Callback being:
public abstract class A_Callback { […] abstract public void A_Callback_Body (test.Standard.AdaString Str, int Val); […] }
Now, all that's needed to implement the Ada access-to-subprogram in Java, is to derive from A_Callback to create a concrete class, implement its A_Callback_Body member, and then give an instance of that object to the Process function.
For example:
import test.API.A_Callback; import test.API.API_Package; import test.Standard.AdaString; public class My_Main { static class My_Callback extends A_Callback { public void A_Callback_Body (AdaString Str, int Val) { System.out.println (“Call “ + Val + “ of “ + Str); } } public static void main (String [] argv) { API_Package.Process (new AdaString (“Hello”), new My_Callback ()); } }
And just that easily, you’ve established a callback, with Java calling Ada and then calling back to Java!
Compiling and running
As in the earlier gem, we’ve generated the build project file with the –L switch. So following these steps, on Linux, you need to do:
gprbuild –p –P ada/my_lib.gpr LD_LIBRARY_PATH=`pwd`/ada/lib/:$LD_LIBRARY_PATH export LD_LIBRARY_PATH CLASSPATH=`pwd`:`pwd`/java:<your AJIS installation>/lib/ajis.jar:$CLASSPATH javac My_Main.java java My_Main
Now, if you try to run the program, it doesn't work quite as expected. It turns out that an exception is raised at the point of the call to Process, on the Java side. Let’s have a look at that in more detail.
Fixing the escapement problem
The problem that we have is that it’s not possible to store objects of class A_Callback that have been created from Java – they are not actual library-level access-to-subprogram values, but rather are Java objects. You may want to have a look at the generated Ada glue code if you need more details on this, but the important point is that such objects cannot be used by the native code after the call to Process, so they should not be stored. We’ll say that they cannot be "escaped".
Unfortunately, we have no way to ensure that the objects are indeed not escaped, so the AJIS run-time just refuses to pass such objects, in order to prevent the developer from escaping them by mistake. In this very case, we just make two calls. No object will get stored, and calls to Process are perfectly safe. We can give this information to the binding generator by using one of the AJIS annotations:
with AJIS.Annotations; package API is type A_Callback is access procedure (Str : String; Val : Integer); procedure Process (Str : String; C : A_Callback); pragma Annotate (AJIS, Assume_Escaped, False, Process, “C”);
The pragma indicates that the C parameter of Process should not be assumed as potentially escaped – in other words, the Ada developer takes the responsibility of not allowing it to escape. The AJIS run-time will then allow it to pass non-escapable objects as parameters.
Note that we now have a reference to the AJIS run-time in our working sources. The easiest way to reference that run-time is to use a project file to manage the sources, for example by using the following project file:
with “ajis”; project API is end API;
and then the binding can be regenerated:
ada2java api.ads –b test –o ada –c java –L my_lib –P api.gpr
After recompiling the Ada and Java sources, we relaunch the Java application and calls from Ada to Java now work fine!
In the next Gem we'll look at how to do cross-language dispatching by extending an Ada tagged type in Java.