Inspirel banner

Ada-Python Binding


The concept of multi-language development allows developers to build their systems in a way that reaps the benefits of various programming languages without necessarily tying one's hands with any single solution. When one size does not fit all, the combination of paradigms and approaches can prove to be a very effective alternative.

Most of the programming languages provide some means for interfacing with code written in other languages. This article presents the scaffolding for binding between Ada and Python - the particular use case involves a utility library that is written in Ada and that is used from Python. This scheme reflects the typical usage patterns of these two languages, where Ada can provide reliable infrastructure foundations for a bigger system that is "glued" together by a set of Python scripts.

The technique presented here builds on the ability of Ada to expose C-style interfaces and the ability of Python to call functions from dynamically loaded libraries. An important property of this technique is that language concepts are not mixed across the language boundary - that is, there is nothing specific to Python in the Ada layer and that there is nothing specific to Ada in Python scripts, so that both sides can be independently reused or replaced by other solutions.

The example was tested on Mac OS X and Linux Debian systems with the GNAT compiler and should be easy to reproduce on other similar systems.

The Ada library

A very simple Ada library is enough to show the concepts - let's suppose a single package exposing an important "hello" functionality:

-- file:

package Ada_Module is

   procedure Say_Hello;
   pragma Export (C, Say_Hello, "say_hello");

end Ada_Module;

In the above specification the Say_Hello procedure is marked as exported, which forces the compiler and linker to use the C calling convention as well as create the appropriate entry in the resulting library file.

The implementation of this package should not be surprising:

-- file: ada_module.adb

with Ada.Text_IO;

package body Ada_Module is

   procedure Say_Hello is
      Ada.Text_IO.Put_Line ("Hello from Ada!");
   end Say_Hello;

end Ada_Module;

The library has to be built as a shared library in order to later allow the Python interpreter to dynamically load it. The following GNAT Project Manager script takes care of proper compilation:

-- file: ada_module.gpr

project Ada_Module is
   for Source_Dirs use (".");
   for Object_Dir use "obj";
   for Library_Name use "ada_module";
   for Library_Dir use "lib";
   for Library_Kind use "dynamic";
   for Library_Interface use ("Ada_Module");
   for Library_Auto_Init use "False";
end Ada_Module;

An important setting above is the Library_Interface value, which defines the set of package names that are to be treated as a public interface of the library - this is what makes the whole a stand-alone library and what additionally forces the binder to include the module intialization and finalization routines that will be called later on to properly set up the library in the enclosing process.

The build process can be invoked with the command:

$ gnatmake -Pada_module

On the Mac OS X system the result of the build is a new file libada_module.dylib in the lib directory. On other POSIX-like systems the likely file extension will be .so instead.

The Python layer

Having the Ada library properly compiled and assuming that the DYLD_LIBRARY_PATH (that will be LD_LIBRARY_PATH on Linux) environment variable is set up so that both the dynamic module and the GNAT run-time libraries are on the list, it should be possible for Python to load the new module.

One of the most useful standard packages in Python is ctypes, which allows to dynamically load libraries and to create callable entities based on the given symbol name.

The following single script demonstrates the use of ctypes package with the previously compiled Ada module:

>>> import ctypes
>>> ada_module = ctypes.CDLL("libada_module.dylib")

After importing the ctypes package, the Ada module is explicitly loaded and a new "handle" is created that represents the whole library - this handle can be used to call module functions by name.

There are two "magic" functions that should be executed in order to properly initialize and finalize the Ada module - these are ada_moduleinit and ada_modulefinal. It should be noted that these functions return nothing and that fact has to be properly communicated at the Python level in order to prevent Python from using the non-existing return value. The following snippet initializes the Ada part:

>>> ada_module.ada_moduleinit.restype = None
>>> ada_module.ada_moduleinit()

The Say_Hello procedure in Ada has no parameters and returns nothing - similarly to the initialization function, that fact has to be stated:

>>> ada_module.say_hello.restype = None

At this point, the Python layer has enough knowledge to properly call the Ada function:

>>> ada_module.say_hello()
Hello from Ada!
>>> for i in range(5):
...     ada_module.say_hello()
Hello from Ada!
Hello from Ada!
Hello from Ada!
Hello from Ada!
Hello from Ada!

In principle, the module should be finalized before being unloaded. Even though in the majority of cases it is not strictly necessary to do so at the end of the Python script (or the interpreter interactive session), the following commands finalize the Ada module:

>>> ada_module.ada_modulefinal.restype = None
>>> ada_module.ada_modulefinal()

Moving forward

Obviously, the above example is very simple. It does, however, present the conceptual scaffolding that allows to build much more elaborate systems. The part that requires particular attention is the set of types that can be used for parameter passing in the Ada module - in principle these should be limited to the set that is foreseen for C interfacing in Ada.

Another important aspect of inter-language interfacing is the use of exceptions - these should be avoided across the language boundary, as it is not likely that they will be properly interpreted in a foreign language context.

The Annex B of the Ada Reference Manual is the relevant source of information related to interfacing between Ada and other languages. See the ctypes documentation for details on handling dynamically loaded native libraries at the Python level.

Last but not least - read also the next part of this article: Ada-Python Demo.

Did you find this article interesting? Share it!

Bookmark and Share