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?
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.
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.
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.
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:
gnatmake -c
to generate a hello.ali
and a hello.o
;hello.ali
with gnatbind
with the -n
switch. This will generate b~hello.adb
and b~hello.ads
which contain binding code;b~hello.adb
into an object file: b~hello.o
.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.
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!