RAVE
Introduction to the RAVE C APIs

A lot of work done in RAVE is number crunching in different ways.

Sometimes it can be re-projecting from one area to another, other times it's more demanding algorithms that must be performed.

So, why have we decided to go for C instead of high-level languages like Python and Java. The easiest answer is that we had an old legacy that we have refined, but fortunately there are other reasons as well. C is fast and since some algorithms will always be demanding it's better to prepared for the worst. Another reason is that it is fairly easy to add both Python and Java APIs on top of C by using the native interface support in those languages.

Let's take a look at the core of the transform library (<ravesrc>/librave/transform).

rave_object.h

C might be a bit awkward to use since you need to be careful about memory management. We have tried to make parts of this processing more easily manageable by using a concept where we are using reference counters and objects instead of pure memory allocation. This is not completely true since you still will need to allocate memory at times, but we try to facilitate this.

First, we have rave_object.h. This file is essential for manipulating objects within RAVE. It is somewhat similar to how Python has implemented its object support.

There are five macros that are really necessary and will be used quite a lot.

  • RAVE_OBJECT_NEW This is the macro that is used for creating the objects RAVE provides.
      PolarScan_t* scan = RAVE_OBJECT_NEW(&PolarScan_TYPE);
    
  • RAVE_OBJECT_RELEASE This is the macro to use when you are finished with an object.
      RAVE_OBJECT_RELEASE(scan);
    
  • RAVE_OBJECT_COPY This is the macro to use when copying a reference to an object.
      RaveScan_t* copy = RAVE_OBJECT_COPY(scan);
    
  • RAVE_OBJECT_CLONE This is the macro to use when cloning an an object. This will create a completley new object and it is up to the object to support the copy constructor. This means that it is essential to test if the clone has been created or not, cloning might not be supported for the provided object.
      RaveScan_t* clone = RAVE_OBJECT_CLONE(scan);
    
  • RAVE_OBJECT_CHECK_TYPE This macro is used for testing what type of object you are working with.
      if (RAVE_OBJECT_CHECK_TYPE(scan, &PolarScan_TYPE))
    

NOTE: Regardless if you are using RAVE_OBJECT_NEW, RAVE_OBJECT_COPY or RAVE_OBJECT_CLONE you always should call RAVE_OBJECT_RELEASE when you are done with the object.

  PolarScan_t* src = RAVE_OBJECT_NEW(&PolarScan_TYPE);   // Create a new instance
  PolarScan_t* copy = RAVE_OBJECT_COPY(src);             // same object as src (but reference count is increased)
  PolarScan_t* clone = RAVE_OBJECT_CLONE(copy);          // identical copy but not same

  // if (src == copy) evaluates to true but
  // if (src == clone) will evaluate to false.
  
  RAVE_OBJECT_RELEASE(src);                              // decrease reference count
  RAVE_OBJECT_RELEASE(copy);                             // decreases reference count and object is destroyed
  RAVE_OBJECT_RELEASE(clone);                            // decreases reference count and object is destroyed

If you are uncertain if you have managed to release all objects when the application terminates or if you are curious on how many pending objects you have at a particular time, the function RaveCoreObject_printStatistics will print it out on stderr. If you want such statistics to be printed when the application terminates, add this call during initialization of your program.

  if (atexit(RaveCoreObject_printStatistics) != 0) {
    fprintf(stderr, "Could not set atexit function");
  }

If the Python API modules are used, this has already been done so you do not need to bother.

There are a few more macros that might be useful when wrapping the objects for other languages or similar situations but that usage must be used with caution.

  • RAVE_OBJECT_BIND Used for binding an object with some meta data
      RAVE_OBJECT_BIND(scan, boundobject);
    
  • RAVE_OBJECT_UNBIND Used for removing a binding. This function takes the same boundobject as was used for binding, the reason for this is to at least avoid removing a binding that not was done by the owner. Of course, it is just to get the binding and pass it in but then you are actively causing errors and you are at your own.
      RAVE_OBJECT_UNBIND(scan, boundobject);
    
  • RAVE_OBJECT_ISBOUND Tests if the object has a binding or not
      if (RAVE_OBJECT_ISBOUND(scan))
    
  • RAVE_OBJECT_GETBINDING Returns the current binding or NULL if there is none. As you see, in the code example we are using GETBINDING for keeping track on Python objects but it can be used for other things as well.
      PyPolarScan* this = (PyPolarScan*)RAVE_OBJECT_GETBINDING(scan);
    

We will investigate the bindings later on in the RAVE Python API documentation. But for now, just be careful if you decide to use it. By the way, RAVE_OBJECT_COPY will leave the binding as is but RAVE_OBJECT_CLONE will not pass it on.

rave_alloc.h

This is the other API that provides some help when it comes to memory allocation. These macros basically debug all allocations you perform (if you use them). Since this is a quite heavy operation, we only debug memory if RAVE has been built with the compile-flag -DRAVE_MEMORY_DEBUG, otherwise it will basically be the same as calling the standard C-functions directly.

If you have enabled memory debugging, you will get notifications if you are accessing memory out of bounds, if you try to free memory != NULL that already has been freed, and so forth. However, you will still not get a dump of lost memory. To be able to get that, you will need to do one thing manually in your main program or module.

  if (atexit(rave_alloc_print_statistics) != 0) {
    fprintf(stderr, "Could not set atexit function");
  }

This little code segment will call the function rave_alloc_print_statistics when the program exits. This is automatically supported if you are using our Python modules, but if you prefer to write your own program using the transform library you need to do this.

rave_debug.h

The final header file that provides some useful macros is rave_debug.h. As it sounds, it contains some debug macros. Since we do not know if the system will be compiled with a compiler that can manage macros with variable argument lists, we have provided macros that can take up to four (4) arguments. If you need more, then you will need to create your own strings and pass them to an appropriate macro.

Some macros will always be enabled and other will only be available when compiling with -DDEBUG_RAVE. To be able to get the printouts you need to set a debug level by calling the function #Rave_setDebugLevel. The default level is always silent, meaning that nothing will be printed except CRITICAL errors. The levels that are possible to print out without enabling -DDEBUG_RAVE are:

  • RAVE_INFO - non error-related information
  • RAVE_WARNING - warnings that not will affect the result in any serious way
  • RAVE_ERROR - this is starting to get a bit more serious but it might occur, like trying to open a file that does not exist
  • RAVE_CRITICAL - these are really bad and should be used with care, this might be that memory allocation fails or other conditions that really not should occur during the life cycle of the application. So, do not use this if a file is missing or not is readable. One should also be aware that a critical error might indicate that it really is time to shut down the application and if someone has rerouted the error reporting to their own function, they might actually abort the application.

Speaking of which, you are able to send all printouts to your own logger. This is done by using #Rave_setDebugFunction.

static void MY_debugFunction(char* filename, int lineno, Rave_Debug lvl,  const char* fmt, ...)
{
  if (lvl == RAVE_CRITICAL) {
    fprintf(stderr, "%s:%d caused a CRITICAL ERROR\n", filename, lineno);
    abort();
  }
}

....

Rave_setDebugFunction(MY_debugFunction);

There is another thing that also can cause mischief related to the debug macros and that is the abuse of #RAVE_ASSERT. This macro is currently not possible to deactivate or disable in any way. By using this macro you have actually set up a contract on your code that says that unless the given criteria are fulfilled, I will crash the system.

Some people are against this type of behaviour since it forces a running system to dump instead of trying to recover or ignoring the recognized problem. The problem is actually that these macros can be used improperly and in that case they are really, really bad. If they are used with caution you might actually gain from them instead as they will give you an immediate information on where the problem occured instead of having a system that runs a bit further and then crashes for unknown reasons.

A good example on how to use the assert:

static int MyObject_setSize(MyObject_t* self, int sz) {
  RAVE_ASSERT((self != NULL), "self == NULL");
  self->sz = sz;
  return 1;
}

Here we say that if you pass in a self-pointer that is NULL, then there is something fundamentally wrong in your code.

A bad example on assert usage:

static int MyObject_setSize(MyObject_t* self, int sz) {
  RAVE_ASSERT((sz > 0 && sz < 10), "sz <= 0 or sz >= 10");
  self->sz = sz;
  return 1;
}

Here, on the other hand, we say that if you try to set a value that not is in the interval 1 - 9, you should crash the system. This might be a bit harsh, but, as usual, it's a matter of taste.

Personally, I would have implemented it like this:

static int MyObject_setSize(MyObject_t* self, int sz)
{
  int result = 0;
  RAVE_ASSERT((self != NULL), "self == NULL");
  if (sz > 0 && sz < 10) {
    self->sz = sz;
    result = 1;
  }
  return result;
}

This way I would have ensured that nobody atempts to call the object without the object itself and if someone is trying to set a value outside 1 - 9, I will return 0 instead of 1.

With this knowledge, it is time to take a look at the RAVE objects that are currently implemented and that are therefore at your disposal.

RAVE C Objects