Section 16.2 - Interfacing with C

Since there are many useful utilities that can be called from C it's a good idea to know how to call them from Ada. This section assumes you know the C language to some basic level; if you don't know C you can skim this section.

First, here are some general rules on how Ada and C correspond, based on the RM B.3(63):

  1. An Ada procedure corresponds to a void-returning C function.
  2. An Ada function corresponds to a non-void-returning C function.
  3. An Ada array corresponds to a C pointer to the first element.
  4. Simple scalar types (integers, floats, and access/pointer types) correspond to the obvious type in the other language.

Ada 95 provides a set of predefined packages that make it easier to interface with C. The primary package is named "Interfaces.C", which contains definitions for C types in Ada. These include C's types int, long, unsigned, and double. The C type float is called "C_float" in Ada so that it isn't confused with Ada's type Float (Ada Float and C float are probably identical, but that's not necessarily true).

The type "char_array" mimics C character arrays. Many C functions assume that character arrays are terminated with the special character "nul" (written in C as '\0'). Since Ada strings aren't normally nul-terminated, functions To_C and To_Ada convert between Ada String types and C char_array types.

There are additional packages called Interfaces.C.Strings and Interfaces.C.Pointers that provide additional types and operations on C-style strings and C pointers. In particular, package "Interfaces.C.Strings" defines the type "chars_ptr", which corresponds to the typical C type "char*" when used to point to a C string (i.e. a pointer to an array of characters). The package also defines:

  1. constant Null_Ptr, which corresponds to C's (char*)NULL,
  2. procedure Free, which corresponds to C's free(), and
  3. function Value, which takes a chars_ptr and returns a normal Ada String. This function raises an exception Dereference_Error if passed a null pointer.

Let's work through a real-life example so you can see how this really works. This example is from "package CGI", an Ada binding to the World Wide Web Common Gateway Interface (CGI). Let's say that you want to get the value of an environment variable from the Operating System, and you want to get this value via a pre-existing C function that does this. In C this function is called "getenv" and it has the following C definition (see [Kernighan and Ritchie 1988, edition 2, page 253]):

  char *getenv(char *name);

This can be pretty straightforwardly translated into Ada as:

  function getenv(Variable : chars_ptr) return chars_ptr;
  pragma Import(C, getenv);

That works, but it's inconvenient to have to keep translating values in and out of type "chars_ptr" in an Ada program. It's probably better to write a wrapper program that translates the Ada Strings to C strings (chars_ptr) and back for us. Let's define an Ada function to do that for us:

with Interfaces.C.Strings; use Interfaces.C.Strings;
-- ...

 function Get_Environment(Variable : String) return String is
 -- Return the value of the given environment variable.
 -- If there's no such environment variable, return an empty string.
 
   function getenv(Variable : chars_ptr) return chars_ptr;
   pragma Import(C, getenv);
   -- getenv is a standard C library function; see K&R 2, 1988, page 253.
   -- it returns a pointer to the first character; do NOT free its results.
 
   Variable_In_C_Format : chars_ptr := New_String(Variable);
   Result_Ptr : chars_ptr := getenv(Variable_In_C_Format);
   Result : String := Value_Without_Exception(Result_Ptr);

 begin
  Free(Variable_In_C_Format);
  return Result;
 end Get_Environment;

Notice that a lot of string manipulation is happening in the declaration section. That's an easy way to get things done, because simple Ada Strings have a fixed length once they're declared. There's a call to some function called Value_Without_Exception; that's because normally an attempt to turn a null C pointer into a string will raise an exception, and we just want to turn it into an empty string instead. That means we'll have to define such a function; here's a definition:

 function Value_Without_Exception(S : chars_ptr) return String is
 -- Translate S from a C-style char* into an Ada String.
 -- If S is Null_Ptr, return "", don't raise an exception.
 begin
   if S = Null_Ptr then return "";
    else return Value(S);
   end if;
 end Value_Without_Exception;
 pragma Inline(Value_Without_Exception);

Now we can easily get environment variables in Ada. For example, to get the value of environment variable REQUEST_METHOD, use:

  Request_Method_Text : String := Get_Environment("REQUEST_METHOD");

One thing we haven't covered are C structs. Ada records and C structs clearly correspond, but how exactly should they correspond? The Ada RM advises, but does not require, that Ada records always be passed to C as pointers to the beginning of the corresponding C struct. For those (relatively rare) cases where a C function expects to be passed a structure by value (a copy instead of the more common pointer-to-structure), you could create a new C function that converts a pointer into the actual structure and then call that new C function from Ada. However, this is simply advice, and the GNAT compiler does not follow this advice - instead, GNAT sends Ada records by value (copies). Both approaches are reasonable, but unfortunately they are different. The safest approach for passing Ada records is to always pass "access to record" values - since they are scalar, they are guaranteed to pass correctly in all Ada compilers.


There is no quiz question for this section.

You may go to the next section.


You may also:

PREVIOUS Go back to the previous section

OUTLINE  Go up to the outline of lesson 16

David A. Wheeler (dwheeler@dwheeler.com)

The master copy of this file is at "http://www.adahome.com/Tutorials/Lovelace/s16s2.htm".