Having given an overview of the NDF_ system, the mechanism by which NDF data structures are accessed by an application and subsequently referred to by means of identifiers is now discussed in detail.
The first task to perform before any processing of NDF data can take place is to gain access to an NDF data structure. The creation of new NDFs (e.g. to contain output from an application) is left until later (see §13.2), when some of the concepts involved should be clearer. To start with, we assume that an NDF structure exists and that we are going to write an application which needs to access it.
The normal method of obtaining access to an NDF for input is via a parameter, using the routine NDF_ASSOC which associates an NDF with the parameter:
Here, the ‘IN’ is being used to obtain ‘READ’ access to an NDF data structure. This means that we will be able to read values from the NDF, but not to modify it. An integer variable INDF is supplied to receive the returned NDF identifier value.
The effect of this call to NDF_ASSOC is that the programming environment will attempt to find a suitable pre-existing NDF data structure, most probably by prompting the person running the application to type in its name. If you are using ADAM, you have considerable control over exactly how the data structure is obtained, but none of this need be specified in the application. Instead, a separate interface file (with a file type of .ifl) is used for this purpose. Interface files are discussed in SUN/101 and full details can be found in the reference document SUN/115. Here, only a very simple interface file is given as an example:
Other parameter entries may also be present in the same file (the text beginning with the ‘#’ signs on the right is commentary and need not be included). In combination with the call to NDF_ASSOC, this interface file would result in a prompt, to which the user could respond with the name of an NDF data structure, most probably simply the name of an HDS container file, as follows:
Assuming that the data structure is a valid NDF, NDF_ASSOC will return an identifier for it via its INDF argument and this may then be passed to other NDF_ routines to access the NDF’s values. If the data structure is not valid, then an error message will appear and the prompt will be repeated until a valid response is given or the user decides to give up (in ADAM this would be done by typing ‘!!’ – the abort response). In this latter case, NDF_ASSOC will return with its STATUS argument set to an error value and INDF will be set to the value of the symbolic constant NDF__NOID, a universal value (defined in the include file NDF_PAR) which indicates that an NDF identifier is not valid.
Note that the routine NDF_EXIST may also be used to access NDF data structures for input, but is more typically used during the creation of new NDFs. Its use is described later in §13.3.
As has been illustrated above, the NDF_ routines refer to NDFs by means of values held in integer variables called NDF identifiers. Of course, these integers are not NDF data structures themselves; they simply identify the data structures, the internal details of which are hidden within the NDF_ system.
Each NDF identifier has a unique value which will not be re-used, and this property makes it possible to tell at any time whether an integer value is a valid NDF identifier or not. An identifier’s validity depends on a number of things, such as its actual value (the value NDF__NOID is never valid for instance) and the previous history and current state of the NDF_ system (an identifier which refers to a data structure which has been deleted will no longer be valid). Note that identifier values should not be explicitly altered by applications, as this may also cause them to become invalid.
Identifier validity can be determined by using the routine NDF_VALID, which returns a logical value of .TRUE. via its VALID argument if the identifier supplied is valid:
This is the only NDF_ routine to which an invalid identifier may be passed without provoking an error.
The number of NDF identifiers available at any time is quite large but is, nevertheless, limited. Each identifier also consumes various computer resources which may themselves be limited, so it is important to ensure that identifiers are annulled once they have been finished with, i.e. when access to the associated NDF data structure is no longer required. This is particularly important when an application is to be run from a programming environment where the same executable program continues to run indefinitely (e.g. when using the ADAM command language ICL). Failure to annul every NDF identifier before the application finishes can result in the program eventually being unable to continue, an event which may be accompanied by strange error messages.
Annulling an NDF identifier renders it invalid and resets its value to NDF__NOID. It differs from simply setting the identifier’s variable to this value, however, because it ensures that all resources associated with it are released and made available for re-use. An identifier is annulled using the routine NDF_ANNUL, as follows:
Note that annulling an invalid identifier will produce an error, but this will be ignored if the STATUS argument is not set to SAI__OK when NDF_ANNUL is called (i.e. indicating that a previous error has occurred). This means that it is not usually necessary to check whether an identifier is valid before annulling it, so long as the only possible reason for it being invalid is a previous error which has set a STATUS value.
Unfortunately, it is all too easy to neglect to annul an identifier, especially if the number of NDFs used by an application is large. It is a particular problem if an application is fairly complex and can potentially terminate at many points with an unknown number of NDF identifiers allocated. In such cases, it is often far easier simply to say “annul all the identifiers I’ve used in this part of the program”. The NDF_BEGIN and NDF_END routines allow this.
These routines are used in pairs to delimit a section of an application, rather like a Fortran 77 IF...END IF block, although often they will enclose the entire application. When NDF_END is called, it will annul all the NDF identifiers issued since the matching call to NDF_BEGIN, for example:
As with IF...END IF blocks, matching pairs of calls to NDF_BEGIN and NDF_END may be nested, every call to NDF_END being accompanied by a corresponding call to NDF_BEGIN. This makes it possible to annul only those identifiers acquired within a certain part of an application if required. So long as no NDF identifiers are acquired outside the outermost BEGIN...END block, this type of program structure provides a complete safeguard against forgetting to annul an identifier. Explicit calls to NDF_ANNUL can then largely be eliminated.
Since an NDF identifier only refers to an NDF data structure and is not itself a data structure, it is possible to have several identifiers referring to the same NDF. A duplicate identifier for an NDF may be derived from an existing one by a process called cloning, which is performed by the routine NDF_CLONE, as follows:
This returns a second identifier INDF2 which refers to the same NDF data structure as INDF1.
Cloning is not required frequently, but it can occasionally be useful in allowing an application to “hold on” to an NDF when an identifier is passed to a routine which will annul it; i.e. you simply pass the original identifier and keep the cloned copy. An example of this can be found in the section on matching NDF attributes (§17.3).