ANSI and K&R C


Using the C Interface

To use the C interface, you must include the headers IO.h and IEEEIO.h. By default the headers are treated as K&R C (eg. no prototypes) because that is the default of for many workstation C compilers. To enable the ANSI C prototypes compile with -DANSI or use #define ANSI in your source files before you include the IO.h and IEEEIO.h headers. If you only require the IEEEIO interface, you link only with libieeeio.a using -L$IEEE_DIRECTORY/lib -lieeeio

If you also want HDF support you must also include the header HDFIO.hh and link with libhdfio.a and the usual complement of HDF libraries using -L$HDF_DIRECTORY/lib -L$IEEE_DIRECTORY/lib -lhdfio -lieeeio -lmfhdf -ldf -ljpeg -lz


C Subroutines


Opening

Just like the familiar FILE* type for C stdio operations, all IEEEIO operations require a file handle. The type of this handle is IOFile (and 8-byte integer number). The IEEEopen() and HDFIOopen() routines are used to create this handle and the IOclose() subroutine can be used to close file handles of either type.

ANSI C Prototype
IOFile IEEEopen(char *filename,char *accessmode);

filename:
The name of the IEEEIO data file to open. The typical extension for these files is .raw
accessmode:
The accessmode for the file. This is one of 3 different access modes
After you open the file handle you can use the same set of subroutines for operations on the file regardless of whether the file is HDF or IEEEIO. The libraries manage all of this internally. So the open step is the only thing that is important to differentiating between your HDF and IEEEIO files. There are plans, for example, to have a SocketIO system that allows the data to be written out to a TCP socket instead of to a file for real-time simulation-visualization systems. There are also plans to use this interface to drive an existing Parallel IO system.


	/* write an IEEEIO file */
    IOFile writer = IEEEopen("datafileout.raw","write"); 
	/* write an HDF file */
    IOFile writer = HDFIOopen("datafileout.raw","w"); /* "w" is also valid */
	/* another way to open for writing */
    IOFile writer = IEEEopenWrite("datafileout.raw");
    /* open HDF file for reading */ 
    IOFile reader = HDFIOopen("datafilein.raw","read"); 
    /* another way to open an HDF file for reading */
    IOFile reader = HDFIOopenRead("datafileout.raw");
    /* another way to open an IEEEIO file for reading */
    IOFile reader = IEEEopenRead("datafileout.raw"); 
      

You can test if the file was opened successfully using the IOisValid() function.

    IOFile fileID = IEEEopen("datafileout.raw","write");
    if(!IOisValid(fileID))
       puts("The file you specified does not exist or is not in a
readable format");
      

To close the file, you simply use IOclose.
   IOclose(IOFile filehandle);
      

Writing

To write data you simply use the method write().

ANSI C Prototype
int IOwrite(IOFile filehandle,int numbertype,int rank,int *dimensions,void *data);

filehandle
An open filehandle for the datafile.
numbertype:
The type of the data being stored (datatype definition). It can be one of
rank
Number of dimensions of the dataset
dimensions:
An array of rank integers that give the dimensions of the dataset
data:
Your data array.
So to write a sample array of data.
	float myarray[40][50][60]; /* our bogus data array */
	int i,rank=3;
	int dims[3]={60,50,40}; /* notice these are reversed. */
		/* this is because ieeeio assumes f77 order for data 
		  and c/c++ use exactly the opposite ordering for data in */
memory.
	IOFile writer=IEEEopen("datafile.raw","write"); /* create a outfile */
	for(i=0;i<ndatasets;i++){
		. . . . computation . . .
		IOwrite(writer,FLOAT32,rank,dims,myarray); /* write a dataset */
		. . . . you can write as many datasets as you want
	}
	IOclose(writer); /* then close the file */
      

Reading Data

Reading is a two step process. First you get information on the size and type of the data you intend to read. This allows you to allocate an array of the proper size and type for the reading. Then you actually read the data into a pre-allocated array. The methods for this are IOreadInfo() and IOread().

ANSI C Prototype
int IOreadInfo(IOFile filehandle,int *numbertype,int *rank,int *dims,int maxdims);

filehandle
A filehandle open for reading or appending.
numbertype:
The type of the data being stored (datatype definition). It can be one of
rank
Number of dimensions of the dataset
dimensions:
An array of rank integers that give the dimensions of the dataset
maxdims:
The maximum size of the dimensions array you given it. This prevents array overruns if the dataset has more dimensions than you were anticipating. It can be any positive integer.
This retrieves information about the datatype, rank, and dimensions of the dataset to be retrieved. By default the maximum size of the dimensions array is 3, but you can set it to be larger.

ANSI C Prototype
int IOread(IOFile filehandle,void *data);

This actually reads the dataset into the preallocated array data.

Another useful utility function is IOsizeOf() which returns the number of bytes in a give IO datatype in manner analogous to the standard C sizeof() operator. So for instance, to read a simple dataset, you would do

	int rank;
	int numbertype;
	int dims[3];
	float *data; /* assumes float data */
	IOFile infile = IEEEopen("dataset.raw","read");
	IOreadInfo(infile,&numbertype,&rank,dims);
	/* OK, we are assuming a 3D IO::Float32 array, 
	 but you can be more sophisticated... */
        data = (float*)malloc(IOnBytes(numbertype,rank,dims));
        /* nBytes() is a convenience function... you could also use
           data = (float*)malloc(dims[0] * dims[1] * dims[2] * IOsizeOf(numbertype)); */
	IOread(infile,data); /* read in the data */
      

Since multiple datasets can be stored in a file, you can retrieve them in the order they were written (there is a seek() function that allows random access as well). The method readInfo() implies reading the next dataset stored in the file. The method nDatasets() tells how many datasets are in a file. So typically if you want to read all datasets in a file in order, you would use code similar to;

	IOFile infile = IEEEopen("dataset.raw","r");
	for(int i=0;i< IOnDatasets(infile);i++){
	  .....lots of code....
	  IOreadInfo(infile,&numbertype,&rank,dims); /* increments to next dataset */
	  .....more code....
	}
      

Random Access to Datasets (Seeking)

You can select specific datasets in a file using the seek() method.

ANSI C Prototype int IOseek(IOFile filehandle,int index)

filehandle:
Handle to a file open for reading.
index:
The index of the dataset you want to read from. This can be any number from 0 to (number_of_datasets - 1).

Writing Attributes

Attributes allow you to attach extra information to each dataset stored in the file. Each attribute has a name and an array of data (of any of the standard IO datatypes) stored with it. These attributes can be retrieved by name or by integer index in the order in which they were stored. A typical attribute would typically be parameters that describe the grid or the data like, "origin" which would be the 3-vector of floats which locates of the origin of a grid in 3-space. The method used to write these attributes is IOwriteAttribute().

ANSI C Prototype
int IOwriteAttribute(IOFile filehandle,char *name,int numbertype,int length,void *data)

filehandle
An open filehandle for the datafile.
name:
Name of the attribute (like "origin" or "coordinates")
numbertype:
The type of the data array associated with the attribute (datatype definition)
length:
The number of elements in the data array.
data:
The attribute data.

So to write an attribute named origin along with a 3-vector float for the coordinates of the origin, you would use;

	float origin[3]={5.1,0.3,0.5};
	/* and assuming the file is already open for writing
	 the following attribute will be attached to the last 
         written dataset. (you must have write data before adding
         attribs) */
	IOwriteAttribute(writer,"origin",FLOAT32,3,origin);
      

Reading Attributes

The attributes can be retrieved in the order they were written or they can be retrieved by their name. To retrieve the attributes in order, you would utilize the IOnAttributes() method to determine how many attributes are attached, IOreadIndexedAttributeInfo() to get the size and type of the attribute based on its index (use IOreadAttributeInfo() to get size and type for the attribute based on its name), and IOreadAttribute() to read the attribute data.

ANSI C Prototype
int IOnAttributes()

filehandle
An open filehandle for the datafile.
returnvalue:
Number of attributes in the file

ANSI C Prototype
int IOreadIndexedAttributeInfo(IOFile filehandle,int index,char *name,int *numbertype,int *length,int maxnamelength);

filehandle
An open filehandle for the datafile.
index:
The index of the attribute which can be 0 to (nattributes-1)
name:
A buffer in which the name of the attribute will be placed.
numbertype:
The type of the attribute data (datatype definition)
length:
The number of elements in the attribute data.
maxnamelength:
The maximum size of a name that can be stored in the name buffer. It can be any positive integer.

ANSI C Prototype
int IOreadAttribute(IOFile filehandle,int index,void *data);

filehandle
An open filehandle for the datafile.
index:
The index of the attribute data to read
data:
The array into which the attribute data is copied.
So for example, to read the attributes in order, you can use
	for(int i=0;i<IOnAttributes(infile);i++){
	    char name[128];
	    int length,datatype;
	    ...
	    IOreadIndexedAttributeInfo(infile,i,name,&datatype,&length,128);
	    ... allocate some data for storage
	    IOreadAttribute(infile,i,data);
	}
      

The attributes can also be retrieve by name. In fact, the is the most likely way you will use the attributes interface. The IOreadAttributeInfo() method is overloaded to allow retrieval by name as well. It returns the index of the attribute if one is found with a matching name: it returns -1 if one is not found.

ANSI C Prototype
int IOreadAttributeInfo(char *name,int *numbertype,int *length);

filehandle
An open filehandle for the datafile.
returnvalue:
The index of the attribute if found or -1 if no attribute with matching name is found.
name:
The name of the attribute to find.
numbertype:
Returns the numbertype of the stored attribute data (datatype definition)
length:
he length of the stored attribute data.
So a typical use of this interface would be to find an attribute named "origin" and retrieve its data if it exists.
	int index = IOreadAttributeInfo(infile,"origin",&datatype,&length);
	if(index>=0) /* the attribute exists */
		IOreadAttribute(infile,index,data);
	else
		puts("The attribute origin could not be found");
      

Writing Annotations

An annotation is a text string which can be used to describe a dataset. To write an annotation, you use the writeAnnotation() method.

ANSI C Prototype
int IOwriteAnnotation(IOFile filehandle,char *annotationtext)

filehandle
An open filehandle for the datafile.
annotationtext:
A null terminated string of the annotation text
The annotation will be attached to the last written dataset. You can store more than one annotation per dataset and the annotations can be of arbitrary length.


Reading Annotations

The annotations are stored in the order they are written. The method nAnnotations() is used to find out how many attributes are attached to a dataset. The method readAnnotationInfo() is used to find the length of the annotation and readAnnotatin() reads the actual annotation text.

ANSI C Prototype
int IOnAnnotations(IOFile filehandle);

filehandle
An open filehandle for the datafile.
returnvalue:
Number of annotations attached to current dataset.

ANSI C Prototype
IOreadAnnotationInfo(IOFile filehandle,int index,int *length)

filehandle
An open filehandle for the datafile.
index:
Index of the annotations which can be 0 to (nannotations-1)
length:
Length in characters of the annotation. This includes the null-terminating character.

Writing and Reading in Chunks

For distributed-memory programming paradigms like HPF, MPI, or PVM, it is often not unfeasible to write data to disk in a single operation. For this reason, a chunking interface is provided which allows you to write data in blocks to the disk.

To begin a chunk writing operation, you must first reserve a data chunk in the file. This is accomplished using IOreserveChunk() ANSI C Prototype
int IOreserveChunk(IOFile filehandle,int datatype,int rank,int *dims);

Once space has been allocated in the datafile, you can write blocks of data specified by their dimensions and origin using IOwriteChunk() ANSI C Prototype
int IOwriteChunk(IOFile filehandle,int *dims,int *origin,void *data);

Likewise, it is possible to read chunks from the disk as well. No special procedure is required to select a record to read in chunks. Simply use IOreadInfo() to get the dimensions and type of the dataset and then use IOreadChunk() in place of IOread() in order to read-in the data. ANSI C Prototype
int IOreadChunk(IOFile filehandle,int *dims,int *origin,void *data);


John Shalf
Last modified: Thu Feb 4 21:58:57 CST 1999