Jump to content

Ada Programming/Interfacing

From Wikibooks, open books for an open world

Ada. Time-tested, safe and secure.
Ada. Time-tested, safe and secure.

Interfacing

[edit | edit source]

Ada is one of the few languages where interfacing is part of the language standard. The programmer can interface with other programming languages, or with the hardware.

Other programming languages

[edit | edit source]

The language standard defines the interfaces for C, Cobol and Fortran. Of course any implementation might define further interfaces — GNAT for example defines an interface to C++.

Interfacing with other languages is actually provided by pragma Export, Import and Convention.

Interfacing with C

[edit | edit source]

Calling a C function from Ada

[edit | edit source]

The simplest interfacing with C is to call a function without parameters. A C function can be used in Ada by creating a "wrapper": an Ada function declaration that is used to represent the C function and its parameters.

For example, suppose the given C function say_hello:

 void say_hello() {
   printf("Hello from C\n");
 }

A wrapper in Ada for this function can be declared as follows:

 procedure Say_Hello;
 pragma Import (C, Say_Hello, "say_hello");

Another way to declare the same procedure using Aspects in Ada 2012 style:

 procedure Say_Hello
   with Import =>  True, Convention =>  C, External_Name =>  "say_hello";

Both states that Say_Hello procedure is imported under the C convention, from a function called "say_hello". The External_Name aspect is optional if the name of the function is the same in both languages. Thus, the following will also work, because the procedure name is converted to a C function name down-cased to "say_hello" automatically:

 procedure Say_Hello
   with Import =>  True, Convention =>  C;

In the following example, the Ada main procedure is C_Test in the c_test.adb file. The program prints "Hello from Ada" to standard output first, then call say_hello from C, and then prints "Again from Ada".

File: c_test.adb (view, plain text, download page, browse all)
 with Ada.Text_IO; use Ada.Text_IO;
 
 procedure C_Test is
   procedure Say_Hello;
   pragma Import (C, Say_Hello, "say_hello");
 
 begin
   Put_Line ("Hello from Ada");
   Say_Hello;
   Put_Line ("Again from Ada");
 end C_Test;

The hello.c file contains the "say_hello" function declaration and implementation. To use printf, the stdio.h library is required.

 #include <stdio.h>
 
 void say_hello() {
   printf("Hello from C\n");
 }

To compile both files the compiler must create the object (*.o) files, and then the linker must used them as input to produce the executable. In GCC/GNAT this can be accomplished by the following terminal instructions:

 gcc -c -o hello.o hello.c
 gnatmake c_test.adb -largs hello.o

The first statement compiles hello.c into an object file hello.o. The second one, compiles the c_test.adb generating its object file, and links it with hello.o. The "-largs hello.o" is a parameter passed directly to the linker.

Another tool to compile is gprbuild. It requires a project file. The following can compile the project creating the main program c_test.

 project Ctest is
   for Source_Dirs use (".");
   for Main use ("c_test.adb");
   for Languages use ("Ada", "C");
 end Ctest;

To run gprbuild and start compilation:

 gprbuild -Pctest.gpr

Interfacing in general

[edit | edit source]

The package Interfaces.C is used to define C types. C function wrappers should be used to encapsulate types and functions on the C side. This way the code is portable and forward-compatible. This is similar to the way of interfacing with C in Java's JNI. Wrappers should be used for:

  • Translating typedefs defined in C includes to types defined in Interfaces.C;
  • Using macros and exposing macro values to the Ada side;
  • Using variable parameter list functions;
  • Defining multiple function wrappers for a function that takes weakly typed parameters such as a function that takes a char_array or a null pointer;
  • Using getters and setters for C structs that depend on operating system version or other compile-type aspect;
  • Using pointer arithmetic;
  • Keeping Ada source memory-safe.

Importing C variables in Ada

[edit | edit source]

Variables can be imported from C to Ada programs. The package Interfaces.C defines several types to contain the data from C. Therefore, to use any variable defined in C in Ada, a wrapper counterpart must be declared with the Import pragma or Import =>  aspect.

For example, a C integer variable int my_number = 10 can be declared in Ada with:

 with Interfaces.C;
 ...
   My_Number : Interfaces.C.int
     with Import =>  True, Convention =>  C, External_Name =>  "my_number";

The following table shows the types in C and its corresponding Ada type. The first column shows different C data types, the second its interfaces intended to store the same data. The third column shows the Ada data type defined in the standard intended for the same use.

No convertion in numbers are necessarily required. The Interfaces.C package defines them as scalars types, which inherits several attributes and operations from other scalars. This feature allows to use numbers variables as usual. However, other types (mostly characters and strings) my require a function to convert. In this case, the function is usually called To_Ada (...) to convert from interface types to Ada, and To_C (...) from Ada to interface types.

Types in Ada used to store C data.
C Ada interface Ada
int Interfaces.C.int Integer
short Interfaces.C.short Short_Integer
long Interfaces.C.long range...
float Interfaces.C.C_float Float
double Interfaces.C.double Standard.Long_Float
char Interfaces.C.char Character
char[N] Interfaces.C.char_array String
char* (strings) Interfaces.C.Strings.chars_ptr String
nul
NULL System.Null_Address

A string in C can be created with two forms of declarations: as an array (ex.: char mystring[] = "TEXT"), and as a pointer (ex.: char* mystring = "TEXT"). In both cases, the zero character is considered as part of the string. In Ada, there are two types for each declaration form: Interfaces.C.char_array for the C array declaration, and Interfaces.C.Strings.chars_ptr for the C pointer declaration. Using them interchangeably may lead to store garbage information or to raise Storage_Error (stack overflow) exceptions.

The following code C and Ada code are a complete example. The C code defines several variables of different types, and the Ada program imports them and prints them on output. The intention is to show an example of the interface code, along with the data stored when executed.

 #include <stdio.h>
 
 char text1[] = "Another hello from C";
 char* text2 =  "Hello pointer from C";
 int number = 123;
 float f_number = 123.4567;
File: data_test.adb (view, plain text, download page, browse all)
 with Ada.Text_IO; use Ada.Text_IO;
 with Interfaces.C; use Interfaces.C;
 with Interfaces.C.Strings; use Interfaces.C.Strings;
 
 procedure Data_Test is
   Text1 : char_array (0 .. 20)
       with Import => True, Convention => C, External_Name => "text1";
   Text1_Pointer : chars_ptr
       with Import => True, Convention => C, External_Name => "text1";
 
   Text2 : char_array (0 .. 20)
       with Import => True, Convention => C, External_Name => "text2";
   Text2_Pointer : chars_ptr
       with Import => True, Convention => C, External_Name => "text2";
 
   Number : int
       with Import => True, Convention => C, External_Name => "number";
   F_Number : float
       with Import => True, Convention => C, External_Name => "f_number";
 
 begin
     Put_Line ("Text1 is: " & To_Ada (Text1)); --
       if Text1 (Text1'Last) = Char'Val (0) then
         Put_Line ("  Text1 ends with zero character.");
     else
         Put_Line ("  Text1 does not end with zero character.");
     end if;
     --  The following raises a Storage_Error exception.
     --  Put_Line (Interfaces.C.Strings.Value (Text1_Pointer, 19));
 
     Put_Line ("Text2 is: " & To_Ada (Text2)); --   Prints garbage.
       Put_Line ("Text2_Pointer is:" & Value (Text2_Pointer));
     declare
         Tmp : Char_Array := Value (Text2_Pointer);
     begin
         if Tmp (Strlen (Text2_Pointer)) = Char'Val (0) then
             Put_Line ("  Text2_Pointer ends with zero character.");
         else
             Put_Line ("  Text2_Pointer does not end with zero character.");
         end if;
     end;
 
     Put_Line ("Number: " & Number'Image);
     Put_Line ("F_Number: " & F_Number'Image);
 end Data_Test;

Example

[edit | edit source]
with Interfaces.C;
with System;
with Ada.Text_IO;

procedure Main is
   procedure W32_Open_File_Dialog
   is
      package C renames Interfaces.C;
      use C;
      
      type OPENFILENAME is new System.Address;
      type Window_Type is new System.Address;
      
      function GetOpenFileName (p : OPENFILENAME) return C.int;
      pragma Import (C, GetOpenFileName, "ada_getopenfilename");
      
      function Allocate return OPENFILENAME with
        Import => True,
        Convention => C,
        External_Name => "ofn_allocate";
      
      procedure Set_Struct_Size (X : OPENFILENAME) with
        Import => True,
        Convention => C,
        External_Name => "ofn_set_struct_size";
      
      procedure Set_Owner (X : OPENFILENAME; Owner : Window_Type) with
        Import => True,
        Convention => C,
        External_Name => "ofn_set_owner";
      
      procedure Set_File (X : OPENFILENAME; File : char_array; Length : C.int) with
        Import => True,
        Convention => C,
        External_Name => "ofn_set_file";
      
      procedure Set_Filter (X : OPENFILENAME; Filter : char_array);
      pragma Import (C, Set_Filter, "ofn_set_filter");
      
      procedure Set_Filter_Index (X : OPENFILENAME; N : C.int) with
        Import => True,
        Convention => C,
        External_Name => "ofn_set_filter_index";
      
      function Get_File (X : OPENFILENAME) return System.Address;
      pragma Import (C, Get_File, "ofn_get_file");
      
      function Get_File_Length (X : OPENFILENAME) return C.size_t;
      pragma Import (C, Get_File_Length, "ofn_get_file_length");
      
      procedure Free (X : OPENFILENAME) with
        Import => True,
        Convention => C,
        External_Name => "ofn_free";
      
      OFN : OPENFILENAME;
      Ret : C.int;
      File : aliased C.char_array := "test.txt" & C.nul;
      Filter : aliased C.char_array := "All" & C.nul & "*.*" & C.nul & C.nul & C.nul;
   begin
      OFN := Allocate;
      Set_Struct_Size (OFN);
      Set_Owner (OFN, Window_Type (System.Null_Address));
      Set_File (OFN, File, 256);
      Set_Filter (OFN, Filter);
      Set_Filter_Index (OFN, 0);
      Ret := GetOpenFileName (OFN);
      if Ret = 0 then
         Free (OFN);
         Ada.Text_IO.Put_Line ("No file selected.");
         return;
      end if;
      declare
         Selected_File_Address : System.Address := Get_File (OFN);
         Selected_File_Length : C.size_t := Get_File_Length (OFN);
         Selected_File : char_array (1 .. Selected_File_Length + 1);
         for Selected_File'Address use Selected_File_Address;
      begin
         Ada.Text_IO.Put_Line (To_Ada (Selected_File, Trim_Nul => True));
      end;
      Free (OFN);
   end W32_Open_File_Dialog;
begin
   W32_Open_File_Dialog;
end Main;



#include <windows.h>
#include <stdlib.h>

OPENFILENAME *ofn_allocate()
{
    OPENFILENAME *ofn;
    ofn = (OPENFILENAME *) malloc(sizeof(OPENFILENAME));
    if (ofn == NULL)
        return NULL;
    memset(ofn, 0, sizeof(OPENFILENAME));
    return ofn;
}

void ofn_set_struct_size(OPENFILENAME *ofn)
{
    ofn->lStructSize = sizeof(OPENFILENAME);
}

void ofn_set_owner(OPENFILENAME *ofn, void *owner)
{
    ofn->hwndOwner = (HWND) owner;
}

void ofn_set_file(OPENFILENAME *ofn, char *file, int length)
{
    if (ofn->lpstrFile)
        free(ofn->lpstrFile);
    ofn->lpstrFile = (char *)malloc (length);
    if (ofn->lpstrFile == NULL) {
        ofn->nMaxFile = 0;
        return;
    }
    strncpy(ofn->lpstrFile, file, length);
    ofn->nMaxFile = length;
}

void ofn_set_filter(OPENFILENAME *ofn, char *filter)
{
    ofn->lpstrFilter = filter;
}

void ofn_set_filter_index(OPENFILENAME *ofn, int n)
{
    ofn->nFilterIndex = n;
}

void ofn_free(OPENFILENAME *ofn)
{
    if (ofn->lpstrFile)
        free(ofn->lpstrFile);
    free(ofn);
}

int ada_getopenfilename(OPENFILENAME *ofn)
{
    return (int) GetOpenFileNameA(ofn);
}

char *ofn_get_file(OPENFILENAME *ofn)
{
    return ofn->lpstrFile;
}

size_t ofn_get_file_length(OPENFILENAME *ofn)
{
    return (size_t) lstrlen (ofn->lpstrFile);
}

The following project file (getopenfilename.gpr) shows how to link to comdlg32.dll:

project Getopenfilename is
   for Languages use ("Ada", "C");
   for Source_Dirs use ("src");
   for Object_Dir use "obj";
   for Main use ("main.adb");

   package Linker is
      for Default_Switches ("ada") use ("-lComdlg32");
   end Linker;
end Getopenfilename;

Hardware devices

[edit | edit source]

Embedded programmers usually have to write device drivers. Ada provides extensive support for interfacing with hardware, like using representation clauses to specify the exact representation of types used by the hardware, or standard interrupt handling for writing Interrupt service routines.

See also

[edit | edit source]

Wikibook

[edit | edit source]

Ada Reference Manual

[edit | edit source]

Ada 95 Rationale

[edit | edit source]

Ada Quality and Style Guide

[edit | edit source]