Implementing Input/Output Channels for Ada.Text_Io
IntroductionA number of our customers have asked for the ability to have Ada.Text_Io bypass communication with the host in favor of some other device (like a serial port). Starting with release 4.2.0 we provide a way to for you to implement your own I/O channel and plug it into Ada.Text_Io. The new design allows for you to replace the target-to-host I/O implementation. The new design also allows for multiple I/O implementations to be used at once, so you can keep the target-to-host implementation and add your own implementation for a serial port, or some other device.
Design OverviewThe Apex implementation of Ada.Text_Io mostly lives in the predefined.ss subsystem. When your program makes calls to Ada.Text_Io subprograms, such as "Put_Line" or "Open", there are several layers of code in predefined.ss to process the parameters and perform the required actions. One of those layers is encapsulated in the package Apex_Predefined_Io_Implementation.Os_Dependent_Io. The Ada spec of this file lives in predefined.ss and is named:
apex_predefined_io_implementation.os_dependent_io.1.ada
The body used to live in predefined.ss as well but has now been moved to a new subsystem called lower_predefined.ss. This package is the key point at which new I/O channels can be introduced into your system so we will take a look at some of the code in it to understand how this works.
Note the type declarations at the beginning of the spec:
type Control_Block is abstract tagged private; type Os_Control_Block is access all Control_Block'Class;
The use of abstract tagged types should alert you that an object oriented approach has been taken in the redesign of this package. Note that many of the subprograms in this package (such as Read, Write, Close, End_Of_File) are declared abstract, but there are also a few subprograms (most notably Open and Create) which are not declared abstract.
Because type Control_Block is abstract and tagged, the abstract subprograms (like Read, Write, etc.) will need to be defined on a "concrete" type derived from Control_Block. Calls to Read, Write, etc. will dispatch at runtime to the right concrete implementation. This is the essence of how the new system works: there can be any number of concrete types derived from type Control_Block defining the abstract subprograms like Read and Write. When calls are made to those subprograms at runtime using an abstract Control_Block handle, the correct implementation is automatically chosen.
To better understand this, look at the following example. There is a new subsystem called serial_io.ss which contains code for a serial port implementation of Ada.Text_Io. The package Serial_Io_Dispatch contains the type derived from type Control_Block. Note the first two lines of the spec:
type Serial_Control_Block is new Control_Block with private; type Serial_Control_Block_Ptr is access all Serial_Control_Block;
Note that subprograms such as Read and Write are not declared abstract. In fact their implementations are present in the body of Serial_Io_Dispatch.
Going back to package, Apex_Predefined_Io_Implementation.Os_Dependent_Io, what about the subprograms which were not declared abstract in the spec? Those subprograms must be implemented in the body of that package. Why are some of the subprograms abstract and others not? To understand let's take a look at the body of Apex_Predefined_Io_Implementation.Os_Dependent_Io. Remember that this package body is not in predefined.ss but in the new subsystem lower_predefined.ss.
Note: There a several available views of lower_predefined.ss. There is a lower_predefined.ss view associated with each BSP (like krn_conf.ss and usr_conf.ss views) because you might want to configure files in lower_predefined.ss on a per BSP basis to implement drivers for serial ports or other devices found on a particular board. But by default a general (non BSP-specific) view of lower_predefined.ss is imported and provides an implementation using host based I/O.
Open and Create are special in the sense that when you are opening or creating a "file" (or in our case a Control_Block which represents a file or I/O device) the decision about what I/O code will support that file has not been made yet. In fact that decision needs to be made inside Open/Create.
We have adopted a model similar to the UNIX one where the filesystem is a namespace containing real (on disk) files as well as device files (like /dev/ttya) which provide a name and file-like interface to a real hardware device.
In the bodies of package
Apex_Predefined_Io_Implementation.Os_Dependent_Io subprograms Open and Create you can define a mapping from file names to actual files of different types. In fact you can base the mapping on more than just the name. You can use all four parameters passed to Open and Create to decide what type of Control_Block will be opened or created and returned. For reference, the four parameters (and their types) are:
Mode : in Apex_Predefined_Io_Implementation.File_Mode Kind : in Apex_Predefined_Io_Implementation.File_Kind Name : in String Form : in Apex_Predefined_Io_Implementation.Form_Parameter.Data
We already looked at package Serial_Io_Dispatch, which is a sample implementation of I/O for serial_ports. Inside of Open and Create you will see references to Cross_Io_Dispatch. This is the implementation that we have traditionally provided which uses the host filesystem for I/O. The implementation of Cross_Io_Dispatch lives in the new subsystem file_support.ss.
Frequently Asked QuestionsQ) How can I replace the implementation of standard input, output and error?
A) Standard input, output and error are set up during program elaboration. The main "tricky" part about overriding the standard implementations is that the Name field in Open/Create is not used (the Name parameter is an empty String). Instead the Kind parameter is the crucial one. Also note that Create is used for standard input and Open for standard output and error.
initialization (..........parameters...........) stream call Mod, Kind, Name --------------------------------------------------------------- stdin Create in_file, standard_input, "" stdout Open out_file, standard_output, "" stderr Open out_file, standard_error, ""Q) What's the difference between calls Open and Create in package Apex_Predefined_Io_Implementation.Os_Dependent_Io?
A) There are a few things to point out here:
- Open and Create exist as separate procedures because given a persistent storage device (like a hard drive), there is a difference between opening an existing file and creating a new one.
- Open and Create in Apex_Predefined_Io_Implementation.Os_Dependent_Io are going to be called ultimately after a call to Open or Create at the very top level: Ada.Text_Io. So a call to Ada.Text_Io.Open will ultimately result in a nested call to Apex_Predefined_Io_Implementation.Os_Dependent_Io.Open, and similarly for Create.
- For simple devices like a serial port, it does not matter whether you use Open or Create or both. You can implement your own policy, not only with file names, but with the behaviors associated with I/O implementations. So you can decide, for example, that Create has no meaning for serial ports (and throw an exception), or do something else.
- Note the question above about standard files. Standard Input is initialized with a call to Create, and the other two via calls to Open.
Q) Other than Open and Create, there are some other subprograms in Apex_Predefined_Io_Implementation.Os_Dependent_Io which are not declared abstract. Here a list:
function Identical_Files ... function Is_Posix_Compliant ... function Does_Nonblocking_Io ... function File_Ptr_For_Kind ...
A) Function Identical_Files is an interesting case. To determine whether two files are identical becomes more complicated when those files may have different I/O implementations. If the files have different I/O implementations then they are obviously different files and we can check for that in the concrete implementation of Apex_Predefined_Io_Implementation.Os_Dependent_Io.Identical_Files. However if our check indicates that the files have the same I/O implementation then we will need to dispatch to an abstract Identical_Files function for the particular implementation. So implementing function Identical_Files requires a two level approach.
For the three remaining functions, Is_Posix_Compliant, Does_Nonblocking_Io and File_Ptr_For_Kind, note that these three do not take or return a value of type Control_Block or Os_Control_Block, so there is nothing to dispatch with. Thus, these three functions can be considered global (system-wide) parameters. It is possible that this could be considered a design flaw, but the code has been left this way to maintain compatibility with other existing code.
Q) The purpose of Read, Write, Close and many of the other abstract subprograms in Apex_Predefined_Io_Implementation.Os_Dependent_Io seems obvious, but a few of them look strange. How should I implement these?
- procedure Acquire
- procedure Release
- function Immediately_Available_Bytes
- procedure Reset
- function Is_Interactive
- procedure Enable_Line_Editing
- procedure Disable_Line_Editing
A) Many of these are present to support a persistent storage device. For a simple device like a serial port, a dummy implementation will probably suffice. However, more information may be present in the comments for the abstract declarations of these subprograms. Also remember that the implementation of our host based filesystem I/O implementation is available in file_support.ss and may be a useful reference.
Rational Software Corporation http://www.rational.com support@rational.com techpubs@rational.com Copyright © 1993-2002, Rational Software Corporation. All rights reserved. |