3.11.1 Interfacing to C

Interfacing Ada with a foreign language such as C involves using compiler directives to import and/or export entity definitions in each language – using extern statements in C, for instance, and the Import, Export, and Convention pragmas in Ada. A full treatment of these topics is provided in Appendix B, section 1 of the Ada Reference Manual.

There are two ways to build a program using GNAT that contains some Ada sources and some foreign language sources, depending on whether or not the main subprogram is written in Ada. Here is a source example with the main subprogram in Ada:

/* file1.c */
#include <stdio.h>

void print_num (int num)
{
  printf ("num is %d.\\n", num);
  return;
}
/* file2.c */

/* num_from_Ada is declared in my_main.adb */
extern int num_from_Ada;

int get_num (void)
{
  return num_from_Ada;
}
--  my_main.adb
procedure My_Main is

   --  Declare then export an Integer entity called num_from_Ada
   My_Num : Integer := 10;
   pragma Export (C, My_Num, "num_from_Ada");

   --  Declare an Ada function spec for Get_Num, then use
   --  C function get_num for the implementation.
   function Get_Num return Integer;
   pragma Import (C, Get_Num, "get_num");

   --  Declare an Ada procedure spec for Print_Num, then use
   --  C function print_num for the implementation.
   procedure Print_Num (Num : Integer);
   pragma Import (C, Print_Num, "print_num");

begin
   Print_Num (Get_Num);
end My_Main;

To build this example:

The last three steps can be grouped in a single command:

$ gnatmake my_main.adb -largs file1.o file2.o

If the main program is in a language other than Ada, then you may have more than one entry point into the Ada subsystem. You must use a special binder option to generate callable routines that initialize and finalize the Ada units (Binding with Non-Ada Main Programs). Calls to the initialization and finalization routines must be inserted in the main program, or some other appropriate point in the code. The call to initialize the Ada units must occur before the first Ada subprogram is called, and the call to finalize the Ada units must occur after the last Ada subprogram returns. The binder will place the initialization and finalization subprograms into the b~xxx.adb file where they can be accessed by your C sources. To illustrate, we have the following example:

/* main.c */
extern void adainit (void);
extern void adafinal (void);
extern int add (int, int);
extern int sub (int, int);

int main (int argc, char *argv[])
{
   int a = 21, b = 7;

   adainit();

   /* Should print "21 + 7 = 28" */
   printf ("%d + %d = %d\\n", a, b, add (a, b));

   /* Should print "21 - 7 = 14" */
   printf ("%d - %d = %d\\n", a, b, sub (a, b));

   adafinal();
}
--  unit1.ads
package Unit1 is
   function Add (A, B : Integer) return Integer;
   pragma Export (C, Add, "add");
end Unit1;
--  unit1.adb
package body Unit1 is
   function Add (A, B : Integer) return Integer is
   begin
      return A + B;
   end Add;
end Unit1;
--  unit2.ads
package Unit2 is
   function Sub (A, B : Integer) return Integer;
   pragma Export (C, Sub, "sub");
end Unit2;
--  unit2.adb
package body Unit2 is
   function Sub (A, B : Integer) return Integer is
   begin
      return A - B;
   end Sub;
end Unit2;

The build procedure for this application is similar to the last example’s:

Depending on the circumstances (for example when your non-Ada main object does not provide symbol main), you may also need to instruct the GNAT linker not to include the standard startup objects by passing the -nostartfiles switch to gnatlink.