From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-0.8 required=5.0 tests=BAYES_00,INVALID_DATE autolearn=no autolearn_force=no version=3.4.4 Path: utzoo!utgpu!news-server.csri.toronto.edu!clyde.concordia.ca!uunet!ogicse!decwrl!ucbvax!ESDSDF.dnet.ge.com!CSSARL From: CSSARL@ESDSDF.dnet.ge.com ("Alex Levinson: 609-866-7750 ", 8*777-7750) Newsgroups: comp.lang.ada Subject: How does one do it in Ada. Message-ID: <9003221554.AA14292@ge-dab.GE.COM> Date: 22 Mar 90 16:30:19 GMT Sender: daemon@ucbvax.BERKELEY.EDU Organization: The Internet List-Id: 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)