Back: 14.3.15 Calling EDL functions from a modules Forward: 14.4.1 Printing out messages   FastBack: 14. Writing Modules Up: 14. Writing Modules FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.4 Additional utilities provided by fsc2

When writing a module some of the following information may be useful: first a special function for printing out messages to the user is discussed, second a function that simulates usleep() but does not share some of its shortcomings.

The third topic, exceptions, is a well-known concept implemented for example in C++. Unfortunately, C does not have this kind of mechanism, but when being a bit careful one can implement something very similar also in C using a few macros.

When writing the program I had to deal with my share of memory leaks, segmentation faults etc. and hacked together a few routines for allocation and deallocation of memory that have some build in code to help me with debugging (and which throw exceptions when an allocation fails). You may find it useful to also use these routines for your modules.

A boolean type is something that was missing until the new C99 standard (which only a few compilers support yet), so there's already a typedef for this type included in fsc2 that you can use. It's also documented here in order to avoid confusion should you accidentally try to redefine it.

Finally, there exist some utility function for rounding of double values to the different integer types as well as converting integers to types of smaller width.


Back: 14.4 Additional utilities provided by fsc2 Forward: 14.4.2 Determining the time   FastBack: 14. Writing Modules Up: 14.4 Additional utilities provided by fsc2 FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.4.1 Printing out messages

When writing a module one often has to print out messages to inform the user e.g. about invalid arguments etc. For this purpose there's the print() function in fsc2 (not to be mistaken for the built-in EDL function with the same name) that helps to deal with this and prints messages to the lower browser in the main form. Except for the first argument the function is identical to the printf() function in C, i.e. the second parameter is a format string of exactly the same format as printf() expects, followed by as many values as there are conversion specifiers in the format string.

The first parameter is an integer describing the severity of the problem. There are four levels:

To the output the EDL file name and line number (if appropriate) as well as the device and function name is prepended.

The full C declaration of this function is:

 
void print( int          severity,
            const char * fmt,
            ... );

Back: 14.4.1 Printing out messages Forward: 14.4.3 Waiting for short times   FastBack: 14. Writing Modules Up: 14.4 Additional utilities provided by fsc2 FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.4.2 Determining the time

To find out how much time has been spent since the start of the experiment the function experiment_time() may be used. It returns the time in seconds since the start of the experiment (to be precise since the start of the first exp_hook function). The time resolution should not be taken to be better than about 10 ms.

The function also can be used during the test run but in this case only a very rough estimate will be returned that easily could be off by more than an order of magnitude.

The full C declaration of this function is:

 
double experiment_time( void );

Back: 14.4.2 Determining the time Forward: 14.4.4 Assertions   FastBack: 14. Writing Modules Up: 14.4 Additional utilities provided by fsc2 FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.4.3 Waiting for short times

When writing code that deals with real devices one often needs to wait for times with a resolution of less than a second. The usual way to do this is to call either usleep() or nanosleep(). The second function, nanosleep(), may look like a bit of overkill since both functions effective time resolution is usually in the ms range, at least on Intel machines. On the other hand, usleep() is marked as obsolete in the IEEE Standard 1003.1-2001 (Single UNIX Specification, Version 3) for several reasons (mainly for its undefined interaction with the SIGALRM signal and other similar functions) and thus should not be used anymore.

As a replacement for usleep() (which you shouldn't use in a module) there is a function

 
int fsc2_usleep( unsigned long us_duration,
                 bool          quit_on_signal );

It takes two parameters, an unsigned long, which is the duration (in milliseconds) to wait (just like usleep()), and a boolean value that indicates if the function is supposed to return immediately if a signal gets caught or if it should wait for the specified time even on signals. The function is, except its dealing with signals, nothing more than a wrapper around nanosleep(), so you can use nanosleep() yourself if you prefer.


Back: 14.4.3 Waiting for short times Forward: 14.4.5 Error handling with exceptions   FastBack: 14. Writing Modules Up: 14.4 Additional utilities provided by fsc2 FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.4.4 Assertions

At least during development it usually is helpful to also test for conditions that should never happen but may anyway due to bugs. In this case often the standard C macro assert() is used. fsc2 comes with two variants of this macro, fsc2_assert() and fsc2_impossible(). The first one is basically identical to assert(), the only difference being that in case the assertions tests true not only a text, showing the the file and line number, gets printed to the standard error channel but also an email is sent (including the text of the currently running EDL script and some more relevant information) to whatever email address has been set during the installation as the one for receiving such mails about crashes and other fatal states of the program. The macro fsc2_impossible() is for the same purpose but doesn't take an expression as its argument, it is meant for situations in the code where one would end up in in a branch that (at least theoretically) can never be reached - e.g. if you test in a switch all possible cases you could add a default: case to catch a situation where you actually forgot about a possible case.

If, as in the case of the assert() macro, the macro NDEBUG is defined (before you include `fsc2_module.h') both functions don't do anything at all. You don't have to worry about cases where one of these macros is used as the only statement following for example an else like in

 
    if ( x == 1 )
        do_something_1( );
    else if ( x == 2 )
        do_something_2( );
    else
        fsc2_impossible( );

The macros are written in a way that they form an (empty) block also when NDEBUG is defined.

The only thing you've got to worry about with fsc2_assert() is the same thing you have to with the assert() macro: if the expression has side effects then the programs behaviour may differ depending on whether NDBUG is defined and you thus may have intoduced a Heisenbug.


Back: 14.4.4 Assertions Forward: 14.4.5.1 More on programming with exceptions   FastBack: 14. Writing Modules Up: 14.4 Additional utilities provided by fsc2 FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.4.5 Error handling with exceptions

One of the most important but also most tedious things in programming is rigorous error handling. In order to make a program failsafe in every place where there is even the remotest chance something may go wrong one has to include error handling code everywhere. This is especially annoying when an error can happen with deeply nested function calls where the type of the error may have to "bubble up" several levels before it is finally can be dealt with.

In order to alleviate this problem several newer programming languages like C++ introduced a concept called "exceptions". Unfortunately, C doesn't have them but in fsc2 I use a method to "fake" exceptions(26). An exception can be seen as a kind of flag that can be raised at any instance in the program and leads to the flow of control being immediately transfered to a place were the error can be dealt with (another way to see it is as a non-local goto which knows all by itself were to go to).

There's one restriction with fsc2's exceptions: you may not use them from within code that might get called asynchronously, like signal handlers. But this is probably not an important restriction when writing a module.

As far as raising exceptions in a module is concerned it's usually very simple. If you run into an error that you can't handle within the module just use

 
if ( non_recoverable_error )
    THROW( EXCEPTION );

and fsc2 will take care of all error handling and the stop the running experiment (or the test run). That's all you need to know about exceptions for nearly all cases that have to be dealt within modules.


Back: 14.4.5 Error handling with exceptions Forward: 14.4.5.2 Problems with exceptions   FastBack: 14. Writing Modules Up: 14.4.5 Error handling with exceptions FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.4.5.1 More on programming with exceptions

Of course, when an exception is 'thrown', there must be a place where it gets 'caught', otherwise the exception will simply kill the program. You don't have to care about catching exceptions, fsc2 will do this for you. But in some situations you may prefer to do it yourself. So lets assume that you have a function foo() that might run into a non-recoverable error that can't be handled within the module itself. However you may still need to do some cleaning-up before you pass it up to fsc2. The following example demonstrates how to catch an exception in the calling function:

 
TRY
{
    foo( );
    TRY_SUCCESS;         /* never forget this ! */
}
CATCH( EXCEPTION )
{
    ...                  /* the error handling code goes here */
}
OTHERWISE
    RETHROW;

With TRY the program is told that the following code may throw an exception. If everything works out well and no exception is thrown the CATCH() and OTHERWISE blocks are never executed and in this case TRY_SUCCESS must be called to do some cleaning up (this is different from e.g. C++ where the programmer does not have to care about this since it's done automatically). But if an error happens and an exception (of whatever type - there are more than the simply named EXCEPTION exception) gets thrown the flow of control is changed immediately from the function the exception is thrown in to the CATCH() line.

The CATCH section can be used to catch a specific exception and you can have several CATCH blocks, one for wach different type of exceptions you want to treat in a specific way. This should always followed by a OTHERWISE section that deals with exceptions of types not having been dealt with already (unless you're completely sure you've already handled every possible type). Of course, it's perfectly ok to just have a OTHERWISE block if you don't want to handle different types of exceptions differently. Within the OTHERWISE block you can just rethrow the exception by calling RETHROW if you're not interested in it or are unable to handle it yourself .

In cases where you aren't interested in a special type of exception but want to catch every exception, e.g. to just do some cleaning up before bailing out to pass the problem on to some higher level routines, you can use just an OTHERWISE block without a CATCH(). Here's another example:

 
TRY
{
    do_something_error_prone();
    TRY_SUCCESS;
}
OTHERWISE
{
    do_local_cleanup();       /* e.g. deallocate memory */
    RETHROW;
}

There are three types of exceptions that may be relevant when writing a module:

 
EXCEPTION
OUT_OF_MEMORY_EXCEPTION
USER_BREAK_EXCEPTION

EXCEPTION is a kind of catch-all exceptions not covered by the other two types. OUT_OF_MEMORY_EXCEPTION gets only thrown by fsc2s special functions for memory allocation (see next section), so don't throw it yourself without a very good reason. A USER_BREAK_EXCEPTION can be thrown from within a module when the module is doing something rather time consuming (e.g. waiting for a device to become ready or doing some calibration) and the user has pressed the STOP button. In many cases it's probably simpler not to throw the USER_BREAK_EXCEPTION directly but use the function

 
void stop_on_user_request( void );

It will detect if the user has pressed the STOP button and, if she did, will throw an USER_BREAK_EXCEPTION all by itself. This works from all parts of the module except when running the end_of_exp_hook and exit_hook functions because these need to run without the user intervening. Thus you must make sure that these clean-up functions don't call other functions that may rely on user intervention.

But if you don't want to use stop_on_user_request() but need to do some cleanup within the module when the STOP button has been pressed you can check if the button has been pressed by calling the function

 
void check_user_request( void );

It will return true if the STOP button has been pressed. You then should do your cleanup and immediately afterwards throw a USER_BREAK_EXCEPTION yourself.

Here's some code taken from the module for a digitizer. It waits indefinitely in a loop for the digitizer to become finished with a measurement. To allow the user to get out of this loop (when, for example, he realizes that he forgot to connect the trigger input to the digitizer, so the function never returns) stop_on_user_request() is called each time the loop is repeated. When the user presses the STOP button the function will break out of the loop by throwing an USER_BREAK_EXCEPTION.

 
while ( 1 )                   /* loop forever */
{
    stop_on_user_request( );
    fsc2_sleep( 100000, UNSET );   /* give the device a bit of time */

    length = 40;
    if ( gpib_write( tds754a.device, "BUSY?\n", 6 ) == FAILURE ||
         gpib_read( tds754a.device, reply, &length ) == FAILURE )
        THROW( EXCEPTION );

    if ( length > 0 && reply[ 0 ] == '1' )  /* break when digitizer is ready */
        break;
}

Back: 14.4.5.1 More on programming with exceptions Forward: 14.4.6 Functions for memory allocation   FastBack: 14. Writing Modules Up: 14.4.5 Error handling with exceptions FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.4.5.2 Problems with exceptions

There is a catch when using exceptions. The exception mechanism is using the standard C functions setjmp() and longjmp() to realize TRY and CATCH. These functions have one problem: when an exception is thrown the data stored in CPU registers are not necessarily saved. But an optimizing compiler usually stores values of often used variables in CPU registers, i.e. the value of a variable in memory is not necessarily identical to its 'real' value (or variables might even not exist in memory, they may have gotten optimized out.). When the program now reaches the CATCH() part the values of these variables can be completely bogus and, if you would try to use their values, hard to find errors may result.

Fortunately, when the compiler gets invoked with its warning level set to a suitable level it will recognize such potential problems and emit a warning message like the following (this example is taken from gcc):

 
module.c:123: warning: variable `i' might be clobbered by
                       `longjmp' or `vfork'

You might get this warning for code like this:

 
long ** foo( size_t count,
             size_t len )
{
    long ** buffer;
    size_t  i;

    TRY
    {
        for ( i = 0; i < count; i++ )
            buffer[ i ] = T_malloc( len * sizeof ** buffer );
        TRY_SUCCESS;
    }
    CATCH( OUT_OF_MEMORY_EXECPTION )
    {
        for ( --i; i >= 0; i-- )
            T_free( buffer[ i ] )
        RETHROW;
    }

    return buffer;
}

Chances are high that the compiler will use a register for the variable i and buffer to speed up execution. But when an exception is thrown the values i and buffer had in the loop of the TRY block may get lost in the process, even though they're still needed.

Fortunatley, there's a simple way to take care of this problem: all you need to do is qualify the variables as volatile. If it's a normal variable use e.g.

 
int volatile i = 0;

and if it's a pointer use

 
long ** volatile buffer;

Back: 14.4.5.2 Problems with exceptions Forward: 14.4.7 The bool type   FastBack: 14. Writing Modules Up: 14.4 Additional utilities provided by fsc2 FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.4.6 Functions for memory allocation

There are special function for fsc2 for allocating memory. These functions not only allocate memory but also check that the allocation really returned as much memory as you asked for (on failure the program is stopped and an appropriate error message is printed). That means that you don't have to care for error handling - if these memory allocation functions return everything is ok, otherwise they won't return at all.

The first of these functions called T_malloc() (think about it as tested malloc). And, of course, there is also a replacement for realloc() and calloc(), called T_realloc() and T_calloc(). There's an also additional function for reallocation, T_realloc_or_free() which does the same as T_realloc() but additionally frees already allocated memory, so you don't have to catch failures of allocation and deallocate the original memory it was called with yourself.

For the duplication of strings you should use T_strdup() instead f the normal strdup(). And, to make things complete, the replacement for free() is called T_free().

All functions accept the same input and return values as their normal counterparts, i.e. they have the prototypes:

 
void * T_malloc( size_t size );

void * T_calloc( size_t nmemb,
                 size_t size );

void * T_realloc_or_free( void * ptr,
                          size_t size );

void * T_realloc( void * ptr,
                  size_t size );

char * T_strdup( const char * string );

void * T_free( void * ptr );

For T_free() there's is small deviation from the behavior of the normal free() function. T_free() returns a void pointer, which is always NULL.

There might be cases where you need a call of one of the functions for allocation of memory to return even if it fails. In this case you have to call the function from within a TRY block and be prepared to catch the OUT_OF_MEMORY_EXCEPTION exception that gets thrown when the memory allocation fails. Here's some example code:

 
TRY
{
    array = T_malloc( length );
    TRY_SUCCESS;
}
CATCH( OUT_OF_MEMORY_EXCEPTION )
{
    ...                  /* your error handling code goes here */
}

Back: 14.4.6 Functions for memory allocation Forward: 14.4.8 Numerical conversions and comparisons   FastBack: 14. Writing Modules Up: 14.4 Additional utilities provided by fsc2 FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.4.7 The bool type

fsc2 already has a typedef for the bool type, i.e. for variables that can have only two values, either 1 or 0. It is declared as

 
typedef enum
{
    false = 0,
    true  = 1
} bool;

You can use either the macros SET, OK or TRUE instead of 1 and UNSET, FAIL or FALSE instead of 1. Use this type to do things like

 
bool is_flag;

flag = SET;
...
if ( ! flag )
{
    do_something( );
    flag = UNSET;
}
...
if ( flag == SET )
    do_something_else( );

Back: 14.4.7 The bool type Forward: 14.5 Writing modules for pulsers   FastBack: 14. Writing Modules Up: 14.4 Additional utilities provided by fsc2 FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.4.8 Numerical conversions and comparisons

It's a rather common task to round floating point numbers to some type of integer (signed or unsigned). Therefore, a set of utility functions exists for the more common of these tasks. They are:

 
short int srnd( double a );
unsigned short usrnd( double a );
int irnd( double x );
unsigned int uirnd( double x );
long lrnd( double x );
unsigned long ulrnd( double x );

srnd(), usrnd(), irnd(), uirnd(), lrnd() and ulrnd() round a double value to the next short int, unsigned short int, int, unsigned int, long int or unsigned long int value, respectively. If the double value is smaller than the smallest value of the target range (0 for conversion to an unsigned integer type) the turn value is the smallest value in the target range and errno is set to ERANGE. If the value is too large to fit into the traget range the return value is the largest possible value in the target range and errno is also set to ERANGE (if the conversion does not result in an out-of-range value errno is not modified). If you plan to check for an out-of-range error you thus have to zero-out errno before calling the functions.


Footnotes

(26)

The basic ideas for the exceptions code came from an article by Peter Simons in the iX magazine (http://www.heise.de/ix/), No. 5, 1998, pp. 160-162, but also several other people implemented similar solutions. My version has benefited a lot from the very constructive criticism by Chris Torek (nospam@elf.eng.bsdi.com) on news:comp.lang.c (which isn't meant to say that he would be responsible for its shortcomings in any way).


Back: 14.4.7 The bool type Forward: 14.5 Writing modules for pulsers   FastBack: 14. Writing Modules Up: 14.4 Additional utilities provided by fsc2 FastForward: A. Installation

This document was generated by Jens Thoms Toerring on September 6, 2017 using texi2html 1.82.