[comp.lang.ada] How does one do it in Ada.

CSSARL@ESDSDF.dnet.ge.com ("Alex Levinson: 609-866-7750 ", 8*777-7750) (03/23/90)

The following is in response to Terry J. Westley on his
discussion on generic instantiation of SEQUENTIAL_IO.

We were struggling with basically the same issue while
developing a a large distributed real-time operating system for
the Navy.

First let me preface that the goal of preserving strong typing
is a noble goal, however, the reality of it is that it is not
always possible to enforce strong typing across a communication
interface. 

To begin with, one can always compile the code running on
different processors against different type declarations (I do
not necessarily mean Ada-like type declarations, the code may be
developed using different languages, or developed by different
vendors using the same language but different compile vendors or
revisions of the language). In other words, on a large project
with a distributed architecture, it is *very* difficult to abide
by the Ada's strong typing rules and have the compiler enforce
those rules.

The underlying low-level message passing mechanisms are not
aware of the message semantics; if they were then you'd have an
undesirable tight coupling between the operating system and the
applications. From the operating system's point of view,
messages are just a stream of bytes with some header information
appended to it. Messages are represented by a canonical form of
a message body of the type of array of bytes and the message
header (containing a discriminant) of known (to the OS)
semantics. 

In other words, there is an implied type conversion between the
user visible message type to the underlying message delivery
mechanism message type. The issue is then *where* this type
conversion takes place. 

If you take the Ada route and say that you are going to define a
generic message send and receive procedures and instantiate them
against the user defined Ada message type, then the conversion
is done inside of the send/receive procedural interfaces. The
advantage of it is that it pleases the Ada purists and does not
give (on the surface) the end users the chance to muck around
with type conversions. Given a perfect compiler technology, this
is definitely the preferred method. However, our current Ada
compiler implementation replicates code on generic instantiation
(to be precise, some amount of code is shared).

The upshot is that when we tried to instantiate a generic
Inter-Program Communication package against a significant number
of message types (it is a very large system) the result was less
than palatable to even the fiercest of Ada purists. We attempted
to reduce the number of unique message types by using the
variant semantics (sort of what Terry is suggesting) but it did
not have a significant impact on code replication). 

In short, the generic instantiation (a la SEQUENTIAL_IO) route
was set aside in favor of letting the user to be exposed to the
explicit type conversion from an application specific message
data types to the OS "transport" data type - an array of bytes.
In this case, there is no need to instantiate a generic, the
SEND/RECEIVE procedures are defined in terms of an access (to an
array of bytes) to a transport buffer and buffer length. 

It then became a necessity to develop a method to move Ada
record objects (messages) in and out of a byte array in a way
that satisfies the Ada purists. We learned a way to use
unchecked conversion on the address of the Ada record object to
convert it to access object. By converting the address of the
records object to an access object that could refer to the byte
array, we were able to use a loop statement to copy index
positions from the record object to the byte array (Mittag).

For example:

with SYSTEM;
with UNCHECKED_CONVERSION;
procedure BUFFER_DATA (
  Data_Record_Address       : in SYSTEM.ADDRESS;
  Data_Record_Size_In_Bytes : in NATURAL) is
  type BYTE_TYPE is range 0..255;
  for BYTE_TYPE'size use 8;
  Transfer_Fuffer_Size : NATURAL := 512;
  Data_Record_Size_In_Bytes : NATURAL :=
    Data_Record_Size_In_Bytes / BYTE_SIZE'size;
  type TRANSFER_BUFFER_TYPE is array (1..Transfer_Buffer_Size)
    of BYTE_TYPE;
  type RECORDING_BUFFER_POINTER_TYPE is array
    (1..Data_Record_Size_In_Bytes) of BYTE_TYPE;
  type RECORDING_BUFFER_POINTER_TYPE is access
    RECORDING_BUFFER_TYPE;
  Blank_Transfer_Buffer : TRANSFER_BUFFER_TYPE := (others => 0);
  Transfer_Buffer       : TRANSFER_BUFFER_TYPE :=
    Blank_Transfer_Buffer;
  Data_REcord_Pointer      : RECORDING_BUFFER_POINTER_TYPE;
  Recording_Buffer_Pointer : RECORDING_BUFFER_POINTER_TYPE; :=
    new RECORDING_BUFFER_TYPE;
  Last : NATURAL;
  Loop_Size : NATURAL;

  function ADDRESS_TO_RECORDING_BUFFER_POINTER is new
    UNCHECKED_CONVERSION (SYSTEM.ADDRESS,
                          RECORDING_BUFFER_POINTER_TYPE);

  begin -- procedure BUFFER_DATA

    Data_Record_Pointer := ADDRESS_TO_RECORDING_BUFFER_POINTER (
      Data_Record_Address);
    for I in 1..Data_Record_Size_In_Bytes loop
      Recording_Buffer_Pointer (I) := Data_Record_Pointer (I);
    end loop;
    Loop_Size := Data_Record_Size_In_Bytes /
      Transfer_Buffer_Size + 1;
    for Number_Of_Loops in 1..Loop_Size loop
      if Number_Of_Loops /= Loop_Size then
        Last := Transfer_Buffer_Size;
      else
        Transfer_Buffer := Blank_Transfer_Buffer;
        Last := Data_Record_Size_In_Bytes - 
          (Transfer_Buffer_Size * (Number_Of_Loops - 1));
      end if;
      for I in 1..Last loop
        Transfer_Buffer (I) :=
          Recording_Buffer_Pointer (I + 
            (Transfer_Buffer_Size * (Number_OF_Loops - 1 )));
      end loop;
    end loop;
  exception
    when others =>
      TEXT_IO.PUT_LINE (
        "ERROR: BUFFER ");
  end BUFFER_DATA;

There might be some typing errors in the above (forgive the
pun). In the example, the data record is accessed by a pointer
and then moved to an array of bytes for subsequent transfer.
This approach works for any data object for which the "address"
and "size" attributes are defined. This includes record objects
and arrays but not access types unless they appear as components
of a record.

This approach may draw criticism (flames are welcomed), but we
believe it gives the best of both worlds. The unsavory unchecked
conversion is defined explicitly, the data movement would have
been done by the compiler anyway...

Reference:

Conversions in Ada, Larry Mittag (Embedded Systems
Programming, Volume 2, Number 12, December 1989)