PowerAda Linking Ada Into Non-Ada Programs

From OC Systems Wiki!
< PowerAda:Interface to Other Languages
Revision as of 20:43, 12 May 2021 by Swn (talk | contribs) (Example)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search


Ada code can be linked and used in other programs that are not written in Ada. You do this by "pre-binding" the Ada code into a single partially linked object module or by creating an Ada shared library. This is only supported on AIX.

In order to bind an Ada object module or shared library, you must write (or automatically generate -- see ashared below) an Ada main program that includes all the units that you want to call from the non-Ada program. The Ada main program will not be called at runtime (unless you export it and call it from the non-Ada program), but its WITHs help to identify the set of Ada library units that are needed and determine an elaboration order for the Ada units.

Statically Linking with Ada

Use the -r option on the Ada bind to specify that only a partially linked object module should be created for the Ada main. (Note that if you use -r, the resulting file will not be runnable by itself.)

All subprograms that you intend to call from foreign code should be exported using pragma Export; otherwise the name of the subprogram will be the "mangled" Ada name, and the calling conventions will not be compatible with the calling subprogram. Also, be sure that the external name used for each pragma export is unique across the entire program, otherwise you will likely end up calling the wrong subprogram.

Before any Ada subprograms in the partially-linked object module are called, the non-Ada program must call the C callable routine adainit() to initialize the Ada environment and elaborate the Ada library units. The non-Ada program must also call the C callable routine adafinal() after the last Ada subprogram is called before exiting the subprogram to finalize the Ada environment.

Additionally, if there are command-line parameters to the program, the C callable function adainitargs() must be called to record the parameters before the first reference to Ada.Command_Line operations. Otherwise, the Ada.Command_Line operations will assume 0 arguments (and so probably raise CONSTRAINT_ERROR, and will return "" for Command_Name.

(Note: On AIX PowerAda 5.5 and newer, on AIX 5.2 and newer, calling adainitargs() is not necessary; adainitargs() is called implicitly with information taken from the /proc information for the process when the runtime library initializes. However, a call to adainitargs() may still be used, and its arguments overrride the arguments obtained from AIX.)

The C prototype for adainitargs is the same as for main:

void adainitargs(int argc, char *argv[], char *envp[])

For example, suppose that you wish to use the following (trivial) package of Ada subprograms:

package Ada_Library is
        function Double_It (Num : Integer) return Integer;
        pragma Export(C, Double_It, "double_it");
        function Halve_It (Num : Integer) return Integer;
        pragma Export(C, Halve_It, "halve_it");
end;
package body Ada_Library is
        function Double_It (Num : Integer) return Integer is
        begin
                return 2 * Num;
        end;
        function Halve_It (Num : Integer) return Integer is
        begin
                return Num / 2;
        end;
end;

You must first construct an Ada main program that WITHs the library units containing the exported Ada subprograms:

with Ada_Library;
procedure Ada_Library_Main is
begin
        null;
end;

Compile this as you would any other unit (except: if a unit you need to WITH is a private child unit, you will need to compile this unit with the -q with_private option or the WITH clause will be rejected.)

After compiling all the Ada code, you then link the above main using the -r option:

$ ada -r -b ada_library_main -o ada.o

Your C or C++ "main" function must call adainit() before any Ada code is called, and should be structured as follows:

extern "C" {
  void adainitargs(int argc, char *argv[], char *envp[]);
  void adainit();
  void adafinal();
}
int main(int argc, char* argv[], char* envp[])
{
        adainitargs(argc, argv, envp);
        adainit();
        your_ada_entry_point;
        adafinal();
        return 0;
}

However, if you don't need to reference the command-line arguments from Ada, or you're using PowerAda 5.5 or newer on AIX 5.2 or newer, you need not call adainitargs().

For the example above, you may link ada.o into a C program and call Double_It and Halve_It as follows.

#include <stdio.h>;
extern void adainit();
extern void adafinal();
extern int double_it(int);
extern int halve_it(int);
void main ()
{
        adainit();
        printf("Double 5 = %d\n", double_it(5));
        printf("Half 5 = %d\n", halve_it(5));
        adafinal();
}

which may be compiled and linked with the command:

cc main.c ada.o -o c_ada.exe

Note: You can statically include only one Ada object module in a non-Ada program so bind all your Ada code that you wish to use into a single object module rather than producing separate object modules. If you wish to include more than one Ada module, this must be done using Shared Ada libraries, described below.

Shared Ada Libraries

You may wish to bind your Ada code into one or more shared object libraries so the code is not duplicated in multiple programs. Shared libraries also allow you to replace the shared library with a newer or alternative version of the library without having to rebind all the programs that use its features.

Starting with PowerAda version 5.5, this process has been significantly improved. The shell script ashared is now provided in $POWERADA/bin that subsumes and extends the functionality of the scripts $POWERADA/examples/scripts/build_shared and gen_exp_list used in earlier versions. (These scripts still exist for compatibility, implemented as invocations of ashared.) This document describes how things work in 5.5; contact OC Systems for help with earlier versions.

Binding an Ada shared library is significantly different from binding an static Ada object module to be included directly a program, because you must:

  • explicitly identify the symbols being provided by the library when it is built; and
  • provide that list of symbols when referencing that library during the bind of a new module.

These two aspects, Building and Using, are described below.

Building an Ada Shared Library

There are two approaches you may want to take: custom, and automatic, depending on the degree of control you want.

Custom: using ada -s

To bind a shared library, the minimum you must do is:

  • use the ada -s option
  • use an output name like libadashr.so rather than ada.exe
  • identify the symbols to be exported, e.g. ada ... -i-bE:libadashr.exp

The exports file named in the list step is simple: just a list of symbols, such as

adainit
adafinal
double_it
halve_it

for the above ada_library_main program. Then the command would be:
$ ada -s -b ada_library_main -i-bE:libadashr.exp -o libadashr.so

The ld option "-bE:libadashr.exp" is used to specify the exports file to the AIX linker. Note that there should be no spaces between "-i" and "-bE:adashr.exp".

This approach is really only applicable if you will have all your Ada code in a single shared library. When you start needing to have an Ada module reference another Ada shared library, you need an ada imports list (AIL) file, which is best generated automatically.

Automatic: Using ashared

The 'ashared' program can do all the steps for you:

  • construct and compile an Ada main that WITHs the units in your library
  • generate the linker exports file, e.g., libadashr.exp
  • generate the Ada imports list file, e.g., libadashr.ail
  • invoke the 'ada -s -b ... -o libadashr.so' command

Example:

Let's say you have an application structured as:
libutil.so - an common Ada library of utilities app1, app2, app3 - Ada applications which use libutil.so
where all the utilites are in the util directory of your project $PROJ. Then:
cd adaproj/util ashared -a libutil.ail libutil
would:

  • create libutil.adb to 'WITH' all non-runtime units
  • compile it to create unit sec/libutil
  • compute the Ada imports into libutil.ail
  • compute the set of exports into libutil.exp
  • bind libutil.so, exporting the contents of libutil.exp

The key here is specifying "-a libutil.ail". This tells ashared that you plain to import the resulting shared library into another Ada module, for which the ada import file libutil.ail is required. Without this option, ashared will assume you wish to include and export the entire runtime library and there are no elaboration order considerations outside this library.

With additional options to ashared, you can:

  • limit the main and export files to specific sublibraries (-s)
  • specify an existing main Ada file or assume the main unit exists (-m)
  • generate the .exp and .ail files without binding
  • provide an existing linker export file to use (-E)
  • name other Ada import files that libutil depends on (-i)

See the ashared man page for complete usage. Now you've built the libutil.so library and its accompanying files identifying its linker symbols (libutil.exp) and Ada units (libutil.ail) you can build your applications that use it:
cd $PROJ/app1 ada -b app1 -o app1 -qimport=$PROJ/util/libutil.ail

As it turns out, that's all you need, because the Ada import file includes a reference to the libutil.exp file which is all the linker needs to resolve symbols, and implicitly includes the shared Ada runtime library and its symbols.

(Of course in practice you'll likely have many other bind and linker options (e.g., -qreinstantiate=yes, -i-Lsomewhere) but the point is that the .ail file takes care of the strictly Ada dependencies.)

The .ail file is an easy-to-read text file in a format similar to adalib.imports and adalib.file. In particular you can add additional linker: options, or replace paths with environment variables for use in other environments.

Ada Elaboration and Shared Libraries

If you have a non-Ada main, but have statically linked Ada code into the same main module (using -r as described above) then you must call adainit() to force library unit elaboration. As long as you bound the Ada part with -qimport naming all applicable .ail files, the shared modules will be elaborated appropriately via adainit.

If you do _not_ have any Ada code in your main module, but you are referencing Shared ada modules for which you have generated .ail files, your C or C++ main will have to explicitly call the "adainit symbol" (e.g., libutil__INIT) named in each .ail file.

If you have a single Ada shared module built "Custom" as described above, then it will be elaborated automatically when the library loads and you do not need to call adainit. However, libraries built with 'ashared -a ...' do _not_ elaborate themselves, since there may be a specific module elaboration order that cannot be maintained by automatic elaboration.

Running With Shared Libraries

Since shared libraries are dynamically loaded into your program each time the program is run, AIX will need to know where to look for the shared library at runtime. AIX generally expects the shared libraries to be in the directory /lib. If you wish for AIX to look in a different directory for a supporting shared library, you can specify the -L option when you bind.

For example, to bind the above simple C program with the adashr.o and have AIX look for the shared library in the directory /mylibs/ada:
$ cc main.c -L /mylibs/ada -lutil

The Shared Ada runtime

Starting with PowerAda 5.5, $POWERADA/lib/libada_r.a (and $POWERADA/adarte/lib/libada_r.a) contains a shared PowerAda runtime.

Note that this shared Ada runtime contains support for tasking and tasking is implemented using AIX threads. Therefore, any program bound against a shared library that contains the Ada tasking runtime should be bound with the AIX thread libraries. Use the -t option to do this for Ada programs and the "_r" version of the C compiler command (e.g., cc_r) to do this for programs in other languages. If you attempt to elaborate the Ada tasking runtime from a program that is not bound with the AIX thread libraries, the program will likely crash and dump core.

Example

  1. Verify that the Ada shared runtime files are present:
       aix> ls $POWERADA/lib/libada_r.*    
       libada_r.a - link to the shared runtime library in $POWERADA/adarte/lib    
       libada_r.ail - the Ada Imports List for libada_r.a    
       libada_r.exp - the linker exports list
    
  2. Build a shared Ada library, libutil.so with main unit "libutil" which imports the runtime and exports everything in the "util/adalib" sublibrary

        
       aix> cd /your/app/util    
       aix> ashared -a -l -s util/adalib libutil
    

    This produces the following files in the current directory:

       libutil.so   - the new shared runtime library
       libutil.exp  - the linker exports file 
       libutil.ail  - the Ada imports list file; this implicitly includes
                      $POWERADA/lib/libada_r.ail and (because of -l)
                      automatically links with libutil.exp
    
  3. Build a partially-linked static Ada object file that will be linked with a C++ main.

        
       aix> cd /your/app/ada_main    
       aix> ada -r -b ada_w_main -o ada_main.o -qimport=../util/libutil.ail
    

    This produces ada_main.o in the current directory which includes only object code that is not found in the shared libraries.

  4. Build your C++ main application
        
       aix> cd /your/app/cpp_main    
       aix> xlC -o cpp_main $CPP_SRC ../ada_main/ada_main.o \    
       aix>   -L/your/app/util -lutil -L$POWERADA/lib -lada_r
    

The Ada Import List (AIL) File Format

An Ada Import List file is a plain text file similar in format to adalib.files, adalib.imports. A line may be blank, a comment line, or of the form:

[ Kind [ : ] ] Value [ -- comment ] where Kind is one of the following, lower-case, keywords: "module", "adainit", "unit", "linker", "include", or "include". The "unit" keyword may be omitted. The meaning of Value depends on the keyword, as follows:

module Name - Name is the name of a shared library such as "libada_r.a(shr.o)"

- all other lines that follow are assumed to apply to this module, up until the next module, missing, or end-of-file.
- MULTIPLE: multiple modules may appear in a file
- REQUIRED

adainit Symbol - Symbol is the symbol to be called to initialize this module.

- SINGLE: can only appear once per module
- OPTIONAL: If no symbol is provided, no init symbol will be called.

[unit] Unit - Unit is a PowerAda compilation unit (e.g., lib/foo, sec/bar) which is provided by this module

- MULTIPLE: it is expected that many units will associated with a module.
- REQUIRED: each module must provide at least one unit

linker Options - Options are options to be passed to ld or xlC by the ada bind process, exactly as in an adalib.imports file.

- options are taken verbatim and appended together in the order seen.
- environment variables are not expanded until link time.
- These appear after options given with -i on the ada command-line.
- MULTIPLE: each "linker:" line appears as a line in the Ada.Link script
- OPTIONAL: no linker options are required

include File - File is another Ada Import List (.ail) file to be included at that point.

- The AIL file generated by ashared always includes libada_r.ail.
- MULTIPLE, OPTIONAL: may include zero or more other files.

missing Name - Name is a name that appears in a module line, to be followed by units which may be missing from some instances of that module, and so should not be used. See ailmerge.

- MULTIPLE, OPTIONAL: same as the module kind.

Example .AIL File

module: libutil_ada.so
adainit: libutil_ada__INIT
-- Local units needed by libutil_ada:
lib/util_api
lib/id_support
sec/id_support
lib/time_support
sec/time_support
sec/util_api
-- Linker options needed by this module:
linker: /work1/tjf/test/aix/eram/shared/util/main/libutil_ada.exp
-- Other modules imported by this module:
include: $POWERADA/lib/libada_r.ail

Managing "Switchable" Ada Shared Libraries

One advantage of shared libraries is the ability to change the functionality provided by a shared library without rebuilding all its client applications. This might be done by building two or more variants of the library, e.g. production vs. debugging, or by patching a shared library and re-deploying it.

The danger with this is that the interfaces provided by the two libraries may be different and you want to be sure that no client application uses a unit that's not in all variants of the library. The AIL file can help to enforce this, but "merging" the .ail file with its variant(s) to identify units which missing from one or or more of them, and causing warnings to be emitted when client applications use such "missing" units.

The tool ailmerge is used to merge .ail files which represent the same module. The first file listed defines the adainit symbol and linker options, if any, for the output. For each module in the first file, units which appear only in that file but not in later ones, or (conversely) units which are missing from the first file but appear in later ones, are listed as "missing" from that module in the output .ail file.

When referencing a merged .ail file at bind time using ashared or ada ... -qimport, units listed as "missing" will result in a warning.

The merged .ail file will contain the "linker" directives (if any) from only the first file. If this points to a linker exports file specific to the first module, and that isn't right, you'll have to replace this with a custom imports file representing the common symbols from both libraries; computation of this common linker imports list is _not_ provided by ailmerge or any PowerAda tool.

Ashared Tool Description

NAME

ashared - bind a PowerAda program as a shared library

SYNOPSIS

  ashared [ options ] main [ -- bind_options ...]

OPTIONS

-h - gives this usage info
-l - generate a line in the .ail file to link in the exports file
-r - generate shared PowerAda runtime
-v - verbose progress messages
-x - only produce exports_file, don't bind
-a [ail_file] - generate Ada imports file (default: main.ail)
-E exports_file - exports_file is an EXISTING exports file to use
-e exports_file - generate exports_file with all symbols from main (default: main.exp)
-f so_name - shared object name to appear at top of exports file default: main.so
[-i] ail_file - import ada imports file (may occur multiple times) (The -i isn't needed if ail_file ends in ".ail")
-L library - library containing object code (default: library in current directory)
-m main_ada_file - create & compile source for main in main.adb default: main.ada)
-o output_file - shared module list to be generated (default: main.so); NOTE: libx.a => libx.a(shr.o)
-s sublibrary - export symbols from the sublibrary identified by the regular expression (default: entire library) (can be repeated multiple times)
-- bind_options - any other options to be passed to the 'ada' command

PARAMETERS

main - name of main subprogram (e.g., 'main') that "withs" all the units to be included in the shared library.

DESCRIPTION

ashared is a script which facilitates the building and management of PowerAda shared libraries.

FILES

$POWERADA/bin/ashared - the ashared script
$POWERADA/tools/bin/Ashared - the AIL file management tool

ENVIRONMENT VARIABLES

POWERADA - This must be defined and indicate the powerada/ada95 directory in the PowerAda installation.

EXAMPLES

1. To bind all your Ada code plus the Ada shared runtime into a single shared library libwidget_ada.so with main unit libwidget_ada, to be accessed by a C driver:

   ashared libwidget_ada
   cc widget_main.c -L `pwd` libwidget_ada.exp

2. To bind your Ada code so it may be shared by other Ada modules:

   ashared -a libwidget_ada
   ada -m widget_main.ada -L `pwd` -qimport=libwidget_ada.ail

where the file libwidget_ada.ail is the "Ada imports list" file produced by using ashared -a.

SEE ALSO

ada for use of -qimport option. ailmerge for merging two or more .ail files representing the same library

Ailmerge Tool Description

NAME

ailmerge - merge/intersect Ada Import List (AIL) files.

SYNOPSIS

  ailmerge first.ail second.ail [...]

PARAMETERS

main - name of main subprogram (e.g., 'main') that "withs" all the units to be included in the shared library.
first.ail - result file, modified to reflect intersection with
second.ail ... - .ail file(s) to be intersected with the first

DESCRIPTION

ailmerge is a script identifies units missing from one or more Ada Import List (AIL) files, and writes a modified file to stdout. The reason to do this is if first.ail, second.ail represent two versions of a library with the same API, whose clients are restricted to units in the intersection of these two libraries.

FILES

$POWERADA/bin/ailmerge - the ailmerge script
$POWERADA/tools/bin/Ashared - the AIL file management tool

ENVIRONMENT VARIABLES

POWERADA - This must be defined and indicate the powerada/ada95 directory in the PowerAda installation.

SEE ALSO

ada for use of -qimport option. ashared for use building .ail files