Next: , Up: Mixed Language Programming


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.