How can I use an Ada module in a Perl program using XS?
ada, gcc, gnat, perl, xs
Solution
Yes!
In fact, any language that can be called from C can be used from Perl using XS. Here's a solution to how to do it with an Ada module and ExtUtils::MakeMaker.
Setting things up
Module tree
Let's start by creating a module tree using `h2xs`:
$ h2xs -A -n MyAdaModule
Then let's create a subdirectory to hold our Ada files:
$ cd MyAdaModule
$ mkdir src
Here is the module's specification: src/hello.ads
procedure hello;
... and the body: src/hello.adb
with Ada.Text_IO;
use Ada.Text_IO;
procedure hello is
begin
Put_Line("Hi from Ada!");
end;
Don't forget to update the MANIFEST.
Writing the XS file
Let's write the body of MyAdaModule.xs now. It's pretty much like using a function from a C library:
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
extern void adainit();
extern void adafinal();
MODULE = MyAdaModule PACKAGE = MyAdaModule
void say_hello()
CODE:
adainit();
hello();
adafinal();
From the gnat documentation we know that we need to call `adainit()` and `adafinal()` to initialise and then clean up. These calls surround `hello()` here but they would probably be in a better place in some other function in your XS file. They would then be called from a BEGIN and END block in your Perl module.
Time to compile!
Ada library
First, we don't want to delegate all the magic linking and binding to MakeMaker so let's create a makefile in the src/ directory that will compile our Ada code into a static library.
To make this library, `hello.a`, we just have to follow the gnat documentation:
- use `gnatmake -c` to generate a `hello.ali` and a `hello.o`;
- use `hello.ali` with `gnatbind` with the `-n` switch. This will generate `b~hello.adb` and `b~hello.ads` which contain binding code;
- compile `b~hello.adb` into an object file: `b~hello.o`.
- group `hello.o` and `b~hello.o` together into an archive with `ar`
So, in short, we will use this makefile:
all: hello.a
hello.a: hello.o b~hello.o
ar rcs $@ $^
hello.o: hello.adb hello.ads
gnatmake -c -o $@ $<
b~hello.o: b~hello.adb b~hello.ads
gnatmake -c -o $@ $<
b~hello.adb: hello.ali
gnatbind -n $<
hello.ali: hello.o
clean:
rm -rf *.o *.ali *.a b~*
Don't forget to update the MANIFEST.
Makefile.PL
Finally, the MakeFile.PL file needs some editing. It has to call the above makefile to build our library and then use it in the final linking phase. This is done by setting `MYEXTLIB` to `src/hello.a` and by adding a rule in the `postamble` section.
In our case, we also need to link with libgnat (for `Ada.Text_IO`) which should reside somewhere on your system. This is done by editing `LIBS`. In this example the path is hardcoded but you should probably figure out a more portable way to find libgnat.
use 5.018001;
use ExtUtils::MakeMaker;
# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
WriteMakefile(
NAME => 'MyAdaModule',
VERSION_FROM => 'lib/MyAdaModule.pm', # finds $VERSION
PREREQ_PM => {}, # e.g., Module::Name => 1.1
($] >= 5.005 ? # Add these new keywords supported since 5.005
(ABSTRACT_FROM => 'lib/MyAdaModule.pm', # retrieve abstract from module
AUTHOR => 'A. U. Thor <author@nonet>') : ()),
DEFINE => '', # e.g., '-DHAVE_SOMETHING'
INC => '-I.', # e.g., '-I. -I/usr/include/other'
LIBS => ['-L/usr/lib/gcc/i686-pc-linux-gnu/4.8.2/adalib/ -lgnat'],
MYEXTLIB => 'src/hello.a',
);
sub MY::postamble {
join("\n",
"\$(MYEXTLIB)::",
"\tmake -C src/",
"",
"clean::",
"\tmake -C src/ clean",
);
}
Now try
$ perl Makefile.PL
$ make
$ make test
And surprise: the test doesn't pass! The `hello()` symbol doesn't exist. Inspecting the `MyAdaLib.so` generated by make with the `nm` tool reveals that some symbols have been renamed. In my case, they were prefixed with `_ada_`. So I'd have to call `_ada_hello()` instead of `hello()`. This can be corrected in `src/ada.ads` using the `Export` pragma:
pragma Export
(Convention => C,
Entity => hello,
External_Name => "hello" );
From what I understood, this should be done for all public symbols as it ensures the representation of types, records, etc, is understood from a C program.
Now, you should be able to call `hello()` from the XSUB. Enjoy!
Problem
I started learning Ada recently and knowing that Ada and C object files can be linked together to build a multilingual program or library, is it possible to call Ada code from Perl using XS?