4 An AST Object Primer

 4.1 AST Objects
 4.2 Object Creation and Pointers
 4.3 The Object Hierarchy
 4.4 Displaying Objects
 4.5 Getting Attribute Values
 4.6 Setting Attribute Values
 4.7 Testing, Clearing and Defaulting Attributes
 4.8 Transforming Coordinates
 4.9 Managing Object Pointers
 4.10 AST Pointer Contexts—Begin and End
 4.11 Exporting, Importing and Exempting AST Pointers
 4.12 AST Objects within Multi-threaded Applications
 4.13 Copying Objects
 4.14 C Pointer Types
 4.15 Error Detection
 4.16 Sharing the Error Status

The AST library deals throughout with entities called Objects and a basic understanding of how to handle these is needed before you can use the library effectively. If you are already familiar with an object-oriented language, such as C + +, few of the concepts should seem new to you. Be aware, however, that AST is designed to be used via fairly conventional C and Fortran interfaces, so some things have to be done a little differently.

If you are not already familiar with object-oriented programming, then don’t worry—we will not emphasise this aspect more than is necessary and will not assume any background knowledge. Instead, this section concentrates on presenting all the fundamental information you will need, explaining how AST Objects behave and how to manipulate them from conventional C programs.

If you like to read documents from cover to cover, then you can consider this section as an introduction to the programming techniques used in the rest of the document. Otherwise, you may prefer to skim through it on a first reading and return to it later as reference material.

4.1 AST Objects

An AST Object is an entity which is used to store information and Objects come in various kinds, called classes, according to the sort of information they hold. Throughout this section, we will make use of a simple Object belonging to the “ZoomMap” class to illustrate many of the basic concepts.

A ZoomMap is an Object that contains a recipe for converting coordinates between two hypothetical coordinate systems. It does this by multiplying all the coordinate values by a constant called the Zoom factor. A ZoomMap is a very simple Object which exists mainly for use in examples. It allows us to illustrate the ways in which Objects are manipulated and to introduce the concept of a Mapping—a recipe for converting coordinates—which is fundamental to the way the AST library works.

4.2 Object Creation and Pointers

Let us first consider how to create a ZoomMap. This is done very simply as follows:

  #include "star/ast.h"
  AstZoomMap *zoommap;
  
  ...
  
  zoommap = astZoomMap( 2, 5.0, "" )

The first step is to include the header file “ast.h” which declares the interface to the AST library. We then declare a pointer of type AstZoomMap to receive the result and invoke the function astZoomMap to create the ZoomMap. The pattern is the same for all other classes of AST Object—you simply prefix “ast” to the class name to obtain the function that creates the Object and prefix “Ast” to obtain the type of the returned pointer.

These functions are called constructor functions, or simply constructors (you can find an individual description of all AST functions in Appendix B) and the arguments passed to the constructor are used to initialise the new Object. In this case, we specify 2 as the number of coordinates (i.e. we are going to work in a 2-dimensional space) and 5.0 as the Zoom factor to be applied. Note that this is a C double value. We will return to the final argument, an empty string, shortly (§4.6).

The value returned by the constructor is termed an Object pointer or, in this case, a ZoomMap pointer and is used to refer to the Object. You perform all subsequent operations on the Object by passing this pointer to other AST functions.

4.3 The Object Hierarchy

Now that we have created our first ZoomMap, let us examine how it relates to other kinds of Object before investigating what we can do with it.

We have so far indicated that a ZoomMap is a kind of Object and have also mentioned that it is a kind of Mapping as well. These statements can be represented very simply using the following hierarchy:

  Object
     Mapping
        ZoomMap

which is a way of stating that a ZoomMap is a special class of Mapping, while a Mapping, in turn, is a special class of Object. This is exactly like saying that an Oak is a special form of Tree, while a Tree, in turn, is a special form of Plant. This may seem almost trivial, but before you turn to read something less dull, be assured that it is a very important idea to keep in mind in what follows.

If we look at some of the other Objects used by the AST library, we can see how these are all related in a similar way (don’t worry about what they do at this stage):

  Object
     Mapping
        Frame
           FrameSet
              Plot
        UnitMap
        ZoomMap
     Channel
        FitsChan
        XmlChan

Notice that there are several different types of Mapping available (i.e. there are classes of Object indented beneath the “Mapping” heading) and, in addition, other types of Object which are not Mappings—Channels for instance (which are at the same hierarchical level as Mappings).

The most specialised Object we have shown here is the Plot (which we will not discuss in detail until §21). As you can see, a Plot is a FrameSet… and a Frame… and a Mapping… and, like everything else, ultimately an Object.

What this means is that you can use a Plot not only for its own specialised behaviour, but also whenever any of these other less-specialised classes of Object is called for. The general rule is that an Object of a particular class may substitute for any of the classes appearing above it in this hierarchy. The Object is then said to inherit the behaviour of these higher classes. We can therefore use our ZoomMap whenever a ZoomMap, a Mapping or an Object is called for.

Sometimes, this can lead to some spectacular short-cuts by avoiding the need to break large Objects down in order to access their components. With some practice and a little lateral thinking you should soon be able to spot opportunities for this.

You can find the full class hierarchy, as this is called, for the AST library in Appendix A and you may need to refer to it occasionally until you are familiar with the classes you need to use.

4.4 Displaying Objects

Let us now return to the ZoomMap that we created earlier (§4.2) and examine what it’s made of. There is a function for doing this, called astShow, which is provided mainly for looking at Objects while you are debugging programs.

If you consult the description of astShow in Appendix B, you will find that it takes a pointer to an Object (of type AstObject) as its argument. Although we have only a ZoomMap pointer available, this is not a problem. If you refer to the brief class hierarchy described above (§4.3), you will see that a ZoomMap is an Object, albeit a specialised one, so it inherits the properties of all Objects and can be substituted wherever an Object is required. We can therefore pass our ZoomMap pointer directly to astShow, as follows:

  astShow( zoommap );

The output from this will appear on the standard output stream and should look like the following:

  Begin ZoomMap
     Nin = 2
  IsA Mapping
     Zoom = 5
  End ZoomMap

Here, the “Begin” and “End” lines mark the beginning and end of the ZoomMap, while the values 2 and 5 are simply the values we supplied to initialise it (§4.2). These have been given simple names to make them easy to refer to.

The line in the middle which says “IsA Mapping” is a dividing line between the two values. It indicates that the “Nin” value is a property shared by all Mappings, so the ZoomMap has inherited this from its parent class (Mapping). The “Zoom” value, however, is specific to a ZoomMap and isn’t shared by other kinds of Mappings.

4.5 Getting Attribute Values

We saw above (§4.4) how to display the internal values of an Object, but what about accessing these values from a program? Not all internal Object values are accessible in this way, but many are. Those that are, are called attributes. A description of all the attributes used by the AST library can be found in Appendix C.

Attributes come in several data types (character string, integer, boolean and floating point) and there is a standard way of obtaining their values. As an example, consider obtaining the value of the Nin attribute for the ZoomMap created earlier. This could be done as follows:

  int nin;
  
  ...
  
  nin = astGetI( zoommap, "Nin" );

Here, the function astGetI is used to extract the attribute value by giving it the ZoomMap pointer and the attribute name (attribute names are not case sensitive, but we have used consistent capitalisation in this document in order to identify them). Remember to use the “ast.h” header file to include the function prototype.

If we had wanted the value of the Zoom attribute, we would probably have used astGetD instead, this being a double version of the same function, for example:

  double zoom;
  
  ...
  
  zoom = astGetD( zoommap, "Zoom" );

However, we could equally well have read the Nin value as double, or the Zoom value as an integer, or whatever we wanted.

The data type you want returned is specified simply by replacing the final character of the astGetX function name with C (character string), D (double), F (float), I (int) or L (long). If possible, the value is converted to the type you want. If not, an error message will result. Note that all floating point values are stored internally as double, and all integer values as int. Boolean values are also stored as integers, but only take the values 1 and 0 (for true/false).

4.6 Setting Attribute Values

Some attribute values are read-only and cannot be altered after an Object has been created. The Nin attribute of a ZoomMap (describing the number of coordinates) is like this. It is defined when the ZoomMap is created, but cannot then be altered.

Other attributes, however, can be modified whenever you want. A ZoomMap’s Zoom attribute is like this. If we wanted to change it, this could be done simply as follows:

  astSetD( zoommap, "Zoom", 99.6 );

which sets the value to 99.6. As when getting an attribute value (§4.5), you have a choice of which data type you will use to supply the new value. For instance, you could use an integer value, as in:

  astSetI( zoommap, "Zoom", 99 );

and the necessary data conversion would occur. You specify the data type you want to supply simply by replacing the final character of the astSetX function name with C (character string), D (double), F (float), I (int) or L (long). Setting a boolean attribute to any non-zero integer causes it to take the value 1.

An alternative way of setting attribute values for Objects is to use the astSet function (i.e. with no final character specifying a data type). In this case, you supply the attribute values in a character string. The big advantage of this method is that you can assign values to several attributes at once, separating them with commas. This also reads more naturally in programs. For example:

  astSet( zoommap, "Zoom=99.6, Report=1" );

would set values for both the Zoom attribute and the Report attribute (about which more shortly—§4.8). You don’t really have to worry about data types with this method, as any character representation will do. Note, when using astSet, a literal comma may be included in an attribute value by enclosed the value in quotation marks:

        astSet( skyframe, ’SkyRef="12:13:32,-23:12:44"’ );

Another attractive feature of astSet is that you can build the character string which contains the attribute settings in the same way as when using the C run time library “printf” function. This is most useful when the values you want to set are held in other variables. For example:

  double zoom = 99.6;
  int report = 1;
  
  ...
  
  astSet( zoommap, "Zoom=%g, Report=%d", zoom, report );

would replace the “%” conversion specifications by the values supplied as additional arguments. Any number of additional arguments may be supplied and the formatting rules are exactly the same as for the C “printf” family of functions. This is a very flexible technique, but does contain one pitfall:

Pitfall. The default precision used by “printf” (and astSet) for floating point values is only 6 decimal digits, corresponding approximately to float on most machines, whereas the AST library stores such values internally as doubles. You should be careful to specify a larger precision (such as DBL_DIG, as defined in <float.h >) when necessary. For example:

  #include <float.h>
  
  ...
  
  astSet( zoommap, "Zoom=%.*g", DBL_DIG, double_value );

Substituted strings may contain commas and this is a useful way of assigning such strings as attribute values without the comma being interpreted as an assignment separator, for example:

  astSet( object, "Attribute=%s", "A string, containing a comma" );

This is equivalent to using astSetC and one of these two methods should always be used when assigning string attribute values which might potentially contain a comma (e.g. strings obtained from an external source). However, you should not attempt to use astSet to substitute strings that contain newline characters, since these are used internally as separators between adjacent attribute assignments.

Finally, a very convenient way of setting attribute values is to do so at the same time as you create an Object. Every Object constructor function has a final character string argument which allows you to do this. Although you can simply supply an empty string, it is an ideal opportunity to initialise the Object to have just the attributes you want. For example, we might have created our original ZoomMap with:

  zoommap = astZoomMap( 2, 5.0, "Report=1" );

and it would then start life with its Report attribute set to 1. The “printf”-style substitution described above may also be used here.

4.7 Testing, Clearing and Defaulting Attributes

You can use the astGetX family of functions (§4.5) to get a value for any Object attribute at any time, regardless of whether a value has previously been set for it. If no value has been set, the AST library will generate a suitable default value.

Often, the default value of an attribute will not simply be trivial (zero or blank) but may involve considerable processing to calculate. Wherever possible, defaults are designed to be real-life, sensible values that convey information about the state of the Object. In particular, they may often be based on the values of other attributes, so their values may change in response to changes in these other attributes. The ZoomMap class that we have studied so far is a little too simple to show this behaviour, but we will meet it later on.

An attribute that returns a default value in this way is said to be un-set. Conversely, once an explicit value has been assigned to an attribute, it becomes set and will always return precisely that value, never a default.

The distinction between set and un-set attributes is important and affects the behaviour of several key routines in the AST library. You can test if an attribute is set using the function astTest, which returns a boolean (integer) result, as in:

  if ( astTest( zoommap, "Report" ) ) {
     <the Report attribute is set>
  }

Once an attribute is set, you can return it to its un-set state using astClear. The effect is as if it had never been set in the first place. For example:

  astClear( zoommap, "Report" );

would ensure that the default value of the Report attribute is used subsequently.

4.8 Transforming Coordinates

We now have the necessary apparatus to start using our ZoomMap to show what it is really for. Here, we will also encounter a routine that is a little more fussy about the type of pointer it will accept.

The purpose of a ZoomMap is to multiply coordinates by a constant zoom factor. To witness this in action, we will first set the Report attribute for our ZoomMap to a non-zero value:

  astSet( zoommap, "Report=1" );

This boolean (integer) attribute, which is present in all Mappings (and a ZoomMap is a Mapping), causes the automatic display of all coordinate values that the Mapping converts. It is not a good idea to leave this feature turned on in a finished program, but it can save a lot of work during debugging.

Our next step is to set up some coordinates for the ZoomMap to work on, using two arrays “xin” and “yin”, and two arrays to receive the transformed coordinates, “xout” and “yout”. Note that these are arrays of double, as are all coordinate data processed by the AST library:

  double xin[ 10 ] = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 };
  double yin[ 10 ] = { 0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0 };
  double xout[ 10 ];
  double yout[ 10 ];

We will now use the function astTran2 to transform the input coordinates. This is the most commonly-used (2-dimensional) coordinate transformation function. If you look at its description in Appendix B, you will see that it requires a pointer to a Mapping, so we cannot supply just any old Object pointer, as we could with the functions discussed previously. If we passed it a pointer to an inappropriate Object, an error message would result.

Fortunately, a ZoomMap is a Mapping (Appendix A), so we can use it with astTran2 to transform our coordinates, as follows:

  astTran2( zoommap, 10, xin, yin, 1, xout, yout );

Here, 10 is the number of points we want to transform and the fifth argument value of 1 indicates that we want to transform in the forward direction (from input to output).

Because our ZoomMap’s Report attribute is set to 1, this will cause the effects of the ZoomMap on the coordinates to be displayed on the standard output stream:

  (0, 0) --> (0, 0)
  (1, 2) --> (5, 10)
  (2, 4) --> (10, 20)
  (3, 6) --> (15, 30)
  (4, 8) --> (20, 40)
  (5, 10) --> (25, 50)
  (6, 12) --> (30, 60)
  (7, 14) --> (35, 70)
  (8, 16) --> (40, 80)
  (9, 18) --> (45, 90)

This shows the coordinate values of each point both before and after the ZoomMap is applied. You can see that each coordinate value has been multiplied by the factor 5 determined by the Zoom attribute value. The transformed coordinates are now stored in the “xout” and “yout” arrays.

If we wanted to transform in the opposite direction, we need simply change the fifth argument of astTran2 from 1 to 0. We can also feed the output coordinates from the above back into the function:

  astTran2( zoommap, 10, xout, yout, 0, xin, yin );

The output would then look like:

  (0, 0) --> (0, 0)
  (5, 10) --> (1, 2)
  (10, 20) --> (2, 4)
  (15, 30) --> (3, 6)
  (20, 40) --> (4, 8)
  (25, 50) --> (5, 10)
  (30, 60) --> (6, 12)
  (35, 70) --> (7, 14)
  (40, 80) --> (8, 16)
  (45, 90) --> (9, 18)

This is termed the inverse transformation (we have converted from output to input) and you can see that the original coordinates have been recovered by dividing by the Zoom factor.

4.9 Managing Object Pointers

So far, we have looked at creating Objects and using them in various simple ways but have not yet considered how to get rid of them again.

Every Object consumes various computer resources (principally memory) and should be disposed of when it is no longer required, so as to free up these resources. One way of doing this (not necessarily the best—§4.10) is to annul each Object pointer once you have finished with it, using astAnnul. For example:

  zoommap = astAnnul( zoommap );

This indicates that you have finished with the pointer. Since astAnnul always returns the null value AST__NULL (as defined in “ast.h”), the recommended way of using it, as here, is to assign the returned value to the pointer being annulled. This ensures that any attempt to use the pointer again will generate an error message.

In general, this process may not delete the Object, because there may still be other pointers associated with it. However, each Object maintains a count of the number of pointers associated with it and will be deleted if you annul the final pointer. Using astAnnul consistently will therefore ensure that all Objects are disposed of at the correct time. You can determine how many pointers are associated with an Object by examining its (read-only) RefCount attribute.

4.10 AST Pointer Contexts—Begin and End

The use of astAnnul4.9) is not completely foolproof, however. Consider the following:

  astShow( astZoomMap( 2, 5.0, "" ) );

This creates a ZoomMap and displays it on standard output (§4.4). Using function invocations as arguments to other functions in this way is very convenient because it avoids the need for intermediate pointer variables. However, the pointer generated by astZoomMap is still active, and since we have not stored its value, we cannot use astAnnul to annul it. The ZoomMap will therefore stay around until the end of the program.

A simple way to avoid this problem is to enclose all use of AST functions between invocations of astBegin and astEnd, for example:

  astBegin;
  astShow( astZoomMap( 2, 5.0, "" ) );
  astEnd;

When the expansion of astEnd (which is a macro) executes, every Object pointer created since the previous use of astBegin (also a macro) is automatically annulled and any Objects left without pointers are deleted. This provides a simple solution to managing Objects and their pointers, and allows you to create Objects very freely without needing to keep detailed track of each one. Because this is so convenient, we implicitly assume that astBegin and astEnd are used in most of the examples given in this document. Pointer management is not generally shown explicitly unless it is particularly relevant to the point being illustrated.

If necessary, astBegin and astEnd may be nested, like blocks delimited by “{…}” in C, to define a series of AST pointer contexts. Each use of astEnd will then annul only those Object pointers created since the matching use of astBegin.

4.11 Exporting, Importing and Exempting AST Pointers

The astExport function allows you to export particular pointers from one AST context (§4.10) to the next outer one, as follows:

  astExport( zoommap );

This would identify the pointer stored in “zoommap” as being required after the end of the current AST context. It causes any pointers nominated in this way to survive the next use of astEnd (but only one such use) unscathed, so that they are available to the next outer context. This facility is not needed often, but is invaluable when the purpose of your astBegin…astEnd block is basically to generate an Object pointer. Without this, there is no way of getting that pointer out.

The astImport routine can be used in a similar manner to import a pointer into the current context, so that it is deleted when the current context is closed using astEnd.

Sometimes, you may also want to exempt a pointer from all the effects of AST contexts. You should not need to do this often, but it will prove essential if you ever need to write a library of functions that stores AST pointers as part of its own internal data. Without some form of exemption, the caller of your routines could cause the pointers you have stored to be annulled—thus corrupting your internal data—simply by using astEnd. To avoid this, you should use astExempt on each pointer that you store, for example:

  astExempt( zoommap );

This will prevent the pointer being affected by any subsequent use of astEnd. Of course, it then becomes your responsibility to annul this pointer (using astAnnul) when it is no longer required.

4.12 AST Objects within Multi-threaded Applications

When the AST library is built from source, the build process checks to see if the POSIX threads library (“pthreads”) is available. If so, appropriate pthreads calls are inserted into the AST source code to ensure that AST is thread-safe, and the AST__THREADSAFE macro (defined in the “ast.h” header file) is set to “1”. If the pthreads library cannot be found when AST is built, a working version of the AST library will still be created, but it will not be thread-safe. In this case the AST__THREADSAFE macro will be set to “0” in ast.h. The rest of this section assumes that the thread-safe version of AST is being used.

Note, some AST functions call externally specified functions (e.g. the source and sink functions used by the Channel class or the graphics primitives functions used by the Plot class). AST does not know whether such functions are thread-safe or not. For this reason, invocations of these functions within a multi-threaded environment are serialised using a mutex in order to avoid two or more threads executing an external function simultaneously.

If an application uses more than one thread, the possibility arises that an Object created by one thread may be accessed by another thread, potentially simultaneously. If any of the threads modifies any aspect of the Object, this could lead to serious problems within the other threads. For this reason, some restrictions are placed on how Objects can be used in a multi-threaded application.

4.12.1 Locking AST Objects for Exclusive Use

The basic restriction is that a thread can only access Objects that it has previously locked for its own exclusive use. If a thread attempts to access any Object that it has not locked, an error is reported.

The astAnnul function is the one exception to this restriction. Pointers for Objects not currently locked by the calling thread can be annulled succesfully using astAnnul. This means that a thread that has finished with an Object pointer can unlock the Object by passing the pointer to astUnlock (so that other threads can use the Object via their own cloned pointers), and can then annul the pointer using astAnnul. Note, however, that an error will be reported by astAnnul if the supplied pointer has been locked by another thread using astLock.

When an Object is created, it is initially locked by the calling thread. Therefore a thread does not need to lock an Object explicitly if it was created in the same thread.

If the Object pointer is then passed to another thread, the first thread must unlock the Object using astUnlock and the second thread must then lock it using astLock. Once an object has been locked or unlocked by a thread using a particular pointer, the locked or unlocked state of the Object will also be visible through any other cloned pointers to the same Object.

If a thread attempts to lock an Object that is already locked by another thread, it can choose to report an error immediately or to wait until the Object is available.

The astThread function can be used to determine whether an Object is locked by the running thread, locked by another thread, or unlocked.

If two or more threads need simultaneous access to an Object, a deep copy of the Object should be taken for each thread, using astCopy, and then the copies should be unlocked and passed to the othe threads, which should then lock them. Note, if a thread modifies the Object, the modification will have no effect on the other threads, because the Object copies are independent of each other.

4.12.2 AST Pointer Contexts

Each thread maintains its own set of nested AST contexts, so when astEnd is called, only Objects that are locked by the current thread will be annulled.

If an Object is unlocked by a thread using astUnlock, it is exempted from context handling so that subsequent invocations of astEnd will not cause it to be annulled (this is similar to using astExempt on the Object). When the Object is subsequently locked by another thread using astLock, it will be imported into the context that was active when astLock was called.

4.13 Copying Objects

The AST library makes extensive use of pointers, not only for accessing Objects directly, but also as a means of storing Objects inside other Objects (a number of classes of Object are designed to hold collections of other Objects). Rather than copy an Object in its entirety, a pointer to the interior Object is simply stored in the enclosing Object.

This means that Objects may frequently not be completely independent of each other because, for instance, they both contain pointers to the same sub-Object. In this situation, changing one Object (say assigning an attribute value) may affect the other one via the common Object.

It is difficult to describe all cases where this may happen, so you should always be alert to the possibility. Fortunately, there is a simple solution. If you require two Objects to be independent, then simply use astCopy to make a copy of one, e.g.:

  AstZoomMap *zoommap1, *zoommap2;
  
  ...
  
  zoommap2 = astCopy( zoommap1 );

This process will create a true copy of any Object and return a pointer to the copy. This copy will not contain any pointers to any component of the original Object (everything is duplicated), so you can then modify it safely, without fear of affecting either the original or any other Object.

4.14 C Pointer Types

At this point it is necessary to confess to a small amount of deception. So far, we have been passing Object pointers to AST functions in order to perform operations on those Objects. In fact, however, what we were using were not true C functions at all, but merely macros which invoke a related set of hidden functions with essentially the same arguments. In practical terms, this makes very little difference to how you use the functions, as we will continue to call them.9

The reason for this deception has to do with the rules for data typing in C. Recall that most AST functions can be used to process Objects from a range of different classes (§4.3). In C, this means passing different pointer types to the same function and most C compilers will not permit this (at least, not without grumbling) because it usually indicates a programming error. In AST, however, it is perfectly safe if done properly. Some way is therefore needed of circumventing the normal compiler checking.

The normal way of doing this in C is with a cast. This approach quickly becomes cumbersome, however, so we have adopted the strategy of wrapping each function in a macro which applies the appropriate cast for you. This means that you can pass pointers of any type to any AST function. For example, in passing a ZoomMap pointer to astShow:

  AstZoomMap *zoommap;
  
  ...
  
  zoommap = astZoomMap( 2, 5.0, "" );
  astShow( zoommap );

we are exploiting this mechanism to avoid a compiler warning, because the notional type of astShow’s parameter is AstObject (not AstZoomMap).

We must still guard against programming errors, however, so every pointer’s type is checked by the enclosing macro immediately before any AST function executes. This allows pointer mis-matches (in the more liberal AST sense—i.e. taking account of the class hierarchy, rather than the stricter C sense) to be detected at run-time and a suitable error message will be reported. This message should also identify the line where the error occurs.

A similar strategy is used when pointers are returned by AST functions (i.e. as the function result). In this case the pointer is cast to void, although we retain the notional pointer type in the function’s documentation (e.g. Appendix B). This allows you to assign function results to pointer variables without using an explicit cast. For example, the astRead function returns an Object pointer, but might be used to read (say) a ZoomMap as follows:

  AstChannel *channel;
  AstZoomMap *zoommap;
  
  ...
  
  zoommap = astRead( channel );

Strictly, there is a C pointer mis-match here, but it is ignored because the operation makes perfect sense to AST.

There is an important exception to this, however, in that constructor functions always return strongly-typed pointers. What we mean by this is that the returned pointer is never implicitly cast to void. You must therefore match pointer types when you initially create an Object using its constructor, such as in the following:

  AstZoomMap *zoommap;
  
  ...
  
  zoommap = astZoomMap( 2, 5.0, "" );

If the variable receiving the pointer is of a different type, an appropriate cast should be used, as in:

  AstMapping *mapping;
  
  ...
  
  mapping = (AstMapping *) astZoomMap( 2, 5.0, "" );

This is an encouragement for you to declare your pointer types consistently, since this is of great benefit to anyone trying to understand your software.

Finally, we should also make one more small confession—AST pointers are not really pointers at all. Although they behave like pointers, the actual “values” stored are not the addresses of C data structures. This means that you cannot de-reference an AST pointer to examine the data within (although you can use astShow instead—§4.4). This is necessary so that AST pointers can be made unique even although several of them might reference the same Object.

4.15 Error Detection

If an error occurs in an AST function (for example, if you supply an invalid argument, such as a pointer to the wrong class of Object), an error message will be written to the standard error stream and the function will immediately return.

To indicate than an error has occurred, an AST error status value is used. This integer value is stored internally by AST and is initially clear (i.e. set to zero10 to indicate no error). If an error occurs, it becomes set to a different error value, which allows you to detect the error, as follows:

  zoommap = astZoomMap( 2, 5.0, "Title=My ZoomMap" );
  if ( !astOK ) {
     <an error has occurred>
  }

The macro astOK is used to test whether the AST error status is still OK. In this example it would not be, because we have attempted to set a value for the Title attribute of a ZoomMap and a ZoomMap does not have such an attribute. The actual value of the AST error status can be obtained using the astStatus macro, as follows:

  int status;
  
  ...
  
  
  status = astStatus;

A consequence of the AST error status being set is that almost all AST functions will subsequently cease to function and will instead simply return without action. This means that you do not need to use astOK to check for errors very frequently. Instead, you can usually simply invoke a succession of AST functions. If an error occurs in any of them, the following ones will do nothing and you can check for the error at the end, for example:

  astFunctionA( ... );
  astFunctionB( ... );
  astFunctionC( ... );
  if ( !astOK ) {
     <an error has occurred>
  }

There are, however, a few functions which do not adhere to this general rule and which will attempt to execute if the AST error status is set. These functions, such as astAnnul, are concerned with cleaning up and recovering resources. For example, in the following:

  zoommap = astZoomMap( 2, 5.0, "" );
  
  astFunctionX( ... );
  astFunctionY( ... );
  astFunctionZ( ... );
  
  zoommap = astAnnul( zoommap );
  if ( !astOK ) {
     <an error has occurred>
  }

astAnnul will execute normally in order to recover the resources associated with the ZoomMap that was created earlier, regardless of whether an error has occurred in any of the intermediate functions. Functions which behave in this way are noted in the relevant descriptions in Appendix B.

If a serious error occurs, you will probably want to abort your program, but sometimes you may want to recover and carry on. Because very few AST functions will execute once the AST error status has been set, you must first clear this status by using the astClearStatus macro, as follows:

  astClearStatus;

This will restore the AST error status to its OK value, so that AST functions execute normally again.

Occasionally, you may also need to set the AST error status to an explicit error value (see §15.14 for an example). This is done using astSetStatus and can be used to communicate to AST that an error has occurred in some other item of software, for example:

  int new_status;
  
  ...
  
  astSetStatus( new_status );

The effect is that most AST routines will subsequently return without action, just as if an error had occurred within the AST library itself.

4.16 Sharing the Error Status

In some software, it is usual to maintain a single integer error status variable which is accessed by each function as it executes. If an error occurs, this status variable is set and other functions can detect this and take appropriate action.

If you use AST in such a situation, it can be awkward to have a separate internal error status used by AST functions alone. To remedy this, AST is capable of sharing the error status variable used by any other software, so long as they use the same conventions (i.e. a C int with the same “OK” value). To enable this facility, you should pass the address of your status variable to astWatch, as follows:

  int my_status;
  int *old_address;
  
  ...
  
  old_address = astWatch( &my_status );

Henceforth, instead of using its own internal error status variable, AST will use the one you supply, so that it can detect errors flagged by other parts of your software. The address of the original error status variable is returned by astWatch, so you can restore the original behaviour later if necessary.

Note that this facility is not available via the Fortran interface to the AST library.

9About the only difference is that you cannot store a pointer to an AST “function” in a variable and use the variable’s value to invoke that function again later.

10We will assume throughout that the “OK” value is zero, as it currently is. However, a different value could, in principle, be used if the environment in which AST is running requires it. This is why a simple interface is provided to isolate you from the actual value of the error status.