Ada Programming/Interfacing
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_Hellowith
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_Hellowith
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".
with
Ada.Text_IO;use
Ada.Text_IO;procedure
C_Testis
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.intwith
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.
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;
with
Ada.Text_IO;use
Ada.Text_IO
;with
Interfaces.C
;use
Interfaces.C
;with
Interfaces.C.Strings
;use
Interfaces.C.Strings
;procedure
Data_Testis
Text1 : char_array (0 .. 20)with
Import => True, Convention => C, External_Name => "text1"; Text1_Pointer : chars_ptrwith
Import => True, Convention => C, External_Name => "text1"; Text2 : char_array (0 .. 20)with
Import => True, Convention => C, External_Name => "text2"; Text2_Pointer : chars_ptrwith
Import => True, Convention => C, External_Name => "text2"; Number : intwith
Import => True, Convention => C, External_Name => "number"; F_Number : floatwith
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
Mainis
procedure
W32_Open_File_Dialogis
package
Crenames
Interfaces.C;use
C;type
OPENFILENAMEis
new
System.Address;type
Window_Typeis
new
System.Address;function
GetOpenFileName (p : OPENFILENAME)return
C.int;pragma
Import (C, GetOpenFileName, "ada_getopenfilename");function
Allocatereturn
OPENFILENAMEwith
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 = 0then
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'Addressuse
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.