≡ Menu

Exceptional Error Handling in Teamcenter ITK and NX Open C

Share the knowledge

The normal way of testing for and handling errors when calling the Teamcenter ITK or UG/NX Open C APIs is to #define a macro to wrap your API calls in. Macros are a problematic and flawed solution however. If you’re compiling your code as C++ you can use a much safer inline function and use exception handling to provide a much more flexible and robust error handling mechanism.

Note: We’re covering some basic programming today. I’m interested to hear if this is too basic or not.

Simple Error Handling

Both the ITK and NX Open C APIs — like many C language APIs — follow the convention that every function returns 0 if it completes without errors or some other value if it doesn’t.

This means that a naive but diligent coder might write something like this:

    int result = 0; 
    result = API_do_something(input, &output); 
    if( result != 0 )
    { 
        // get error message 
        // handle error or quit? 
    } 
    result = API_do_something_else(input, &output); 
    if( result != 0 )
    {
         // get error message 
         // handle error or quit? 
    } 
    // etc. etc.

Error Handling with Macros

Naturally, all those assignments to result and conditional checks afterwards grow tiresome. So the next thing that’s often done is #define a macro or two to simplify matters. If you search for the phrases, “sample itk program” or “sample open c program” on the GTAC website you’ll find lots of ITK and Open C code examples(1). Typically in those samples you’ll see something like this,

//**** ITK Example ****//
#define IFERR_ABORT(X)  (report_error( __FILE__,__LINE__,#X,X,TRUE))
#define IFERR_REPORT(X) (report_error( __FILE__,__LINE__,#X,X,FALSE))
 
static int report_error(char *file, int line,
                        char *call, int status,
                        logical exit_on_error)
{
    if (status != ITK_ok)
    {
        // getting error message and writing it
        // to syslog omitted for clarity */
 
        // AIIIEE!! Kill the Program, NOW!!!
        if (exit_on_error) exit(status);
    }
    return status;
}
 
// Use the macro. Program dies here if something goes wrong.
// Have fun looking through the syslog for the error message.
IFERR_ABORT(AOM_refresh(itemRev, FALSE));

The Problem with Macros

That works, to an extent. But there are some drawbacks. First, you only have two options: write a message to the syslog or exit the program — hard. But what if you can do something to handle the error and proceed? Sorry, not supported unless you #define another macro specifically for that or manually insert code specifically for that one condition. And what if the proper place to handle a particular condition is a few levels up in the call stack? You’ll just have to make sure all the calls in the stack know to pass that particular error back to their caller.

And if that’s not enough for you, I’ll appeal to the authority of Scott Meyers, author of the absolutely essential for C++ programmers, Effective C++, who entitled item 1 of his book, “Prefer const and inline to #define“, and Herb Sutter and Andrei Alexandrescu who also covered the same topic in the also-essential C++ Coding Standards. They summarized their objections as such:

TO_PUT_IT_BLUNTLY: Macros are the bluntest Instrument of C and C++’s abstraction facilities, ravenous wolves in functions’ clothing, hard to tame, marching to their own beat all over your scopes. Avoid them.

Suffice to say, they convinced me. If you need more convincing I suggest you look up their full argument in their books.

Exceptional Error Handling

If you haven’t guessed yet, I like to write my programs in C++ instead of C. The advantages of C++ are just too great; there’s too many useful tools in C++ to limit yourself to C (in my opinion). One tool provided by C++ is exception handling with try/catch blocks. I presume that most programmers are already familiar with the concept, either from C++ itself or from one of the many other languages with similar functionality, so I won’t discuss it in depth.

With the twin goals of avoiding macros like the black death and utilizing exception handling, a superior alternative to the canonical macros can be developed. First, we’ll define a custom exception type:

class ITKError : public std::exception
{
private:
    int _error;
 
public:
    ITKError( const int error )
    : exception( ITKError::get_message(error).c_str() ),
      _error(error)
    {
        EMH_clear_last_error(error);
    }
 
    int error() const { return this->_error; }
 
    static std::string get_message(const int error)
    {
        // Use EMH_ask_errors or
        // EMH_ask_error_text to
        // retrieve an error message
    }
};

Next we’ll create our alternative to the IFERR_ABORT(X) macro:

inline void CHECKITK(const int error)
{
    if(error) throw ITKError(error);
 
    return; // no error, so do nothing.
}

Now we have some more robust options available to us for handling errors:

int inner(int argument)
{
   // No error handling at all if something goes wrong
   CHECKITK( API_some_function(argument) );
}
 
int middle(int argument)
{
    try
    {
        inner(argument);
    }
    catch(const ITKError &e)
    {
        // do some cleanup...
        cleanup();
 
        // pass the error along
        throw;
    }
}
 
int outer(int argument)
{
    try
    {
        middle(argument);
    }
    catch(const ITKError &e)
    {
        // two errors we can handle, the rest we throw
        if( e.error() == API_first_error_condition )
        {
             handle_first_error_condition();
        }
        else if( e.error() == API_second_error_condition )
        {
             handle_second_error_condition();
        }
        else
        {
            // no special handling possible, 
            // just pass the error on up.
           throw;
        } 
    }
}

So instead of simply reporting the error and bombing out, this in this example if inner() hits an error it throws an error and exits, passing control back to its caller, middle(), which does some cleanup and then passes the error on to its caller, outer(), which is able to handle two specific error conditions but passes any other error up the chain.

And that, I think, is a much better way to check for, and handle, errors.

Addendum: A warning about Exceptions and C

If you’re writing either Teamcenter ITK or NX Open C programs, your “main” function, e.g. ufusr() in Open C or a …register_callbacks() function in ITK will need to be declared as extern "C". Unless you enjoy it when programs blow up with nasty error messages you need to be sure that you do not let any exceptions escape from an extern "C" function. Exceptions are C++, extern "C" functions are straight C. They don’t know how to handle exceptions and if one does come through the system will not know what to do with it. Then it’s boom-boom time.

To be absolutely safe, wrap all of your calls inside of a try{} block and provide a fail-safe catch(...) block which will capture any errors that are thrown.

Example

extern "C" extern DLLAPI
int MY_CUSTOM_LIB_register_callbacks()
{
    try
    {
        do_everything();
    }
    catch(const ITKError &e)
    {
        report_itk_error(e);
        return e.error();
    }
    catch(...) // catch anything else that might be thrown
    {
        report_unknown_error();
        return UNKNOWN_ERROR;
    }
    return 0; // no errors caught, everything is okay.
}

Now you’re covered and no exceptions will escape to where they shouldn’t be.

  • ATUL khiste(patil)

    Nice one…Thanks for sharing…

  • Balaji Kumar

    Very interesting. Thanks for sharing.

  • SRIHARI

    Informative ..Thanks for sharing..

Optimization WordPress Plugins & Solutions by W3 EDGE