Question

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?

Was it helpful?

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!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top