Ada Tutorial - Chapter 22

BINARY INPUT/OUTPUT

Any useful computer program must have a way to get data into it to operate on, and a way to get the results out to the user. In part 1 of this tutorial, we studied how to get text in and out of the computer, now we will see how to get binary data in and out of the computer. Most of the binary input and output will be to or from files because the data is always in a machine readable format, not one that a human reader can easily comprehend. A block of binary data could be transmitted to a hardware device that is designed to respond to the data, or a block of data could be input from a hardware device reporting on some process. A little more will be said about that later.

BINARY DATA OUTPUT

Example program ------> BISEQOUT.ADA

Examine the program named BISEQOUT.ADA for an example of outputting binary sequential data. Ada gives you three data input/output library packages that are defined in the LRM, and required to be available with every Ada compiler. They are listed by package name as follows;

You should refer to either your compiler documentation or the LRM for the definition of these three packages. All three are found in chapter 14 of the LRM. Spend a little time studying the procedures and functions available with each to become familiar with the capability of each package. The knowledge you have gained studying this tutorial to this point should enable you to understand most of the specifications of these three packages.

THE PACKAGE NAMED Low_Level_IO

There is another input/output package that may be available with your compiler named Low_Level_IO. This is not required by the LRM, but if it exists, it will be used for machine dependent input/output programming with your particular implementation. If it exists with your compiler, the description of how to use it will be included with your documentation, and since it will be completely different with each compiler, no attempt will be made to explain its use here.

BACK TO BISEQOUT.ADA

Although this program does very little, there are several steps that must be performed to output to a binary file. They will be taken in order, so follow along closely. First we must tell the system that we wish to use the external package Sequential_IO, which we do using a with clause in line 4. Next we define a record type to illustrate one kind of data that can be output to the file, and declare a variable of the record type in line 18.

In order to use the sequential input/output package, we must instantiate a copy of it, because it is a generic package and cannot be used directly. We do this in line 15, using the data type we wish to output to the file, then add the use clause in line 16 to make the new package readily available. We need a file name to use as our internal file name, and we define it in line 19, but with a bit of difficulty because we have an overloaded file type available. We have made Text_IO available to illustrate the problem, even though we don't use it in this program, and it contains a type named FILE_TYPE. The package named Seq_IO that we have instantiated also contains a type named FILE_TYPE, the one we wish to use. In order to tell the system which one we want, we must use the extended naming notation to differentiate between the two types. This is done in line 19 of the example program. With these steps, we are ready to begin the executable part of the program.

In a manner similar to that used for text files which we studied earlier, we create the file in line 23, in this case using the mode Out_File since we wish to write to the file. As you recall, this also ties the internal name for the file to the external name we have chosen, NAMEFILE.TXT, which must follow the conventions for our particular Ada compiler and operating system. If your operating system is substantially different, you may need to change this name. We are finally ready to actually use the output file, so we load some nonsense data into the declared record variable in lines 25 and 26, then enter a loop where we will change the Age field of the record in order to have some varying data to write to the file. This will make it more useful when we read the data from this file in another example program.

WRITING BINARY DATA TO THE FILE

We actually write data to the file in line 30, where we use the procedure Write which is part of the instantiated package we named Seq_IO earlier, but since we have defined Seq_IO in a use clause, we do not need the qualifier "dotted" to the procedure name. We mention the internal file name to tell the system which file we wish to write to, and the variable which we desire to output. The variable to be output must be of the type for which the package was instantiated, resulting in a rule that all binary records output to any given file must be of the same type. Even though they must be of the same type, they can be records of a variant record with different variants.

After writing 100 binary records, we close the file in the manner shown in line 33, and the program is complete.

We covered a lot of territory in the last few paragraphs, but it is useful information we will need in the next few example programs, and of course we will need it anytime we wish to actually use a binary file. It should be clear that we could open several files, each storing a different data type, and write to them in any order provided that we wrote the proper data type to each file.

The resulting file, named NAMEFILE.TXT, will be used to illustrate binary reading in the next two programs, so it is imperative that you compile and execute this program. After running it, you will find the file named NAMEFILE.TXT in the default directory of your system, and if you attempt to look at it with a text editor, it will have some very strange looking characters because it is a binary file. You will notice however, that much of it will be readable because of the nature of the data selected for this file.

READING A BINARY FILE

Example program ------> BISEQIN.ADA

The example program named BISEQIN.ADA illustrates how to read from a binary file in a sequential mode. Everything in the declaration part of the program is identical to the last program except for the instantiation of an integer I/O package in line 15 for use later.

The only differences in the executable part is the use of the Open procedure from the Seq_IO package, which uses the In_File mode of file opening. We can now read from the file we wrote in the last program, but we cannot write to it from this program. We execute a loop 100 times where we read a record from the binary file each time through the loop, the record being identical to the record used to write the binary file. If the record is different in structure or in data types, you may get anything upon reading, and it probably will not appear to have anything to do with the original data written, because the bytes will be mixed around.

All 100 elements are read in and those with the Age field greater than or equal to 82 are displayed to illustrate that the data really did get written. Finally, the binary file is closed and the program terminated.

WHAT ABOUT PORTABILITY?

Suppose you wrote the binary file with one Ada compiler, and attempted to read it using a different compiler. It would be indeterminate whether or not it would work, because each implementor is free to define the internal bit patterns of the various types to fit his particular compiler. Using such simple fields as those in this illustration would lead to a very good chance of portability, but using more elaborate records or arrays, would almost certainly cause incompatibility problems.

The solution to this problem is to read a file with the same compiler that was used to write it. Of course a file written in a text format, using Text_IO, is portable and can be read with a different system.

Compile and execute this program and verify that the data really did get output as desired. If you did not compile and execute the last example program, you did not generate the file named "NAMEFILE.TXT", and you cannot successfully execute this program.

RANDOM INPUT AND OUTPUT

Example program ------> BIRANDIO.ADA

Examine the file named BIRANDIO.ADA for an example of random input and output. Random file access means that we can output data to the file, or read data from the file just as if the file were an array. The elements of the file do not have to be accessed in order. We will illustrate all of this in this example program.

The declaration part of this program should look familiar to you since it is nearly identical to the last two example programs. The biggest difference is the use of the Direct_IO package instead of the Sequential_IO package. We instantiate a copy of this called Ran_IO, and use it to declare the internal filename, My_In_Out_File. Next, as part of the declaration part of the program, we declare a function that will be used to output data in a neat form for us.

READING FROM THE RANDOM FILE

Before we can do anything with the file, we must open it, and since we intend to read from and write to this file, we open it with the InOut_File mode. Of course we use the internal filename we defined in line 21, and the external filename we have already written to. In line 38, we use the procedure Read, reading from the file we have opened, and we read the data into the record variable named Myself. The thing that is really new here is the use of the number 37 as the third actual parameter of the procedure call. This tells the system that we wish to read the 37th record from the designated file. We use our Display_Record procedure to display the record read, then tell the system that we wish to read record number 25, and display it. In line 42, we don't tell the system which record we want to read explicitly, so it returns the next record, the 26th, which we display.

WRITING TO THE RANDOM FILE

We fill the three fields of the record variable with nonsense data in lines 46 through 48, and write the modified record to records 91, 96, and 97. We write to record 97 because we don't specify a record and the system will default to the next successive record number. In line 53 we call the procedure Set_Index with the value of 88 as the value to set, and as you may guess, it sets the record pointer to 88, which is the next record that will be read by default if no record number is stated. The loop in lines 54 through 57 read and display all records from 88 through the last, because we keep reading until we find an end of file. You will see when you compile and execute this program that we did modify records numbered 91, 96, and 97.

You will find binary Input/Output to be very useful for temporary storage of large amounts of data, but you must keep in mind that any data you write using this technique may or may not be readable by some other system. For this reason, binary output should not be used except for data that is meant to be read by another program compiled with the same Ada compiler as the data generator. Be sure to compile and execute this program.

PROGRAMMING EXERCISES

  1. Modify BISEQOUT.ADA to write the same data to two different files, except when Age is between 50 and 60. During this range, one of the characters should be changed for one of the files.
  2. Modify BISEQIN.ADA to read both files output from exercise 1 and list the differences in the two files.
  3. Combine BISEQOUT.ADA and BISEQIN.ADA in such a way that the file is written in one loop, then read back in and displayed in a successive loop.


Copyright © 1988-1996 Coronado Enterprises - Last update, April 25, 1996
Gordon Dodrill - dodrill@swcp.com -
Please email any comments or suggestions.