views:

156

answers:

1

I am writing a custom C++ exception class (so I can pass exceptions occuring in C++ to another language via a C API).

My initial plan of attack was to proceed as follows:

//C++ 
myClass
{
public:
   myClass();
   ~myClass();

   void foo() // throws myException
   int foo(const int i, const bool b) // throws myException
} * myClassPtr;


// C API
#ifdef __cplusplus
extern "C" {
#endif

myClassPtr MyClass_New();
void MyClass_Destroy(myClassPtr p);
void MyClass_Foo(myClassPtr p);
int  MyClass_FooBar(myClassPtr p, int i, bool b);

#ifdef __cplusplus
};
#endif

I need a way to be able to pass exceptions thrown in the C++ code to the C side. The information I want to pass to the C side is the following:

(a). What (b). Where (c). Simple Stack Trace (just the sequence of error messages in order they occured, no debugging info etc)

I want to modify my C API, so that the API functions take a pointer to a struct ExceptionInfo, which will contain any exception info (if an exception occured) before consuming the results of the invocation.

So an C++ method exposed in the C API would be implemented something like this:

// I must admit there is something freakishly 'hackish' about this sort of implementation (maintenance nightmare aside) - ther must be a better way?

// ExceptionInfo default ctor initialized to no error

void MyClass::foobarTrappedExceptionWrapper(ExceptionInfo& ei)
{    
    try {
      // call foobar ...
      foobar();
    }
    catch(fast_exception& fe)
    {
       switch (fe.errcode())
       {
          //package exception info into ei for C consumption 
       }
    }
    catch(std::exception &e)
    {    
      //package exception info into ei for C consumption 
    }
}

Implementation of each of the C++ methods exposed in the C API needs to be enclosed in a try/catch statement (see above snippet). The performance implications for this seem quite serious (according to this article):

"It is a mistake (with high runtime cost) to use C++ exception handling for events that occur frequently, or for events that are handled near the point of detection."

At the same time, I remember reading somewhere in my C++ days, that all though exception handling is expensive, it only becmes expensive when an exception actually occurs. So, which is correct?. what to do?. Is there an alternative way that I can trap errors safely and pass the resulting error info to the C API?. Or is this a minor consideration (the article after all, is quite old, and hardware have improved a bit since then).

[Edit]

Removed question2, since I found a way to create a simple stack trace here.

+2  A: 

Answers

  1. Both are correct; you are misinterpreting the first source. The try{} block incurs basically no cost; it is the throw....catch (i.e. propagation) that is expensive. So, it's cheap if the exception doesn't occur, which is why they should not be used if they are occuring frequenty (because then you do throw...catch often).
  2. Using a very long but fixed-size buffer isn't lighter weight than using an std::string. Using an std::string is a bad idea only if you are worried that you might throw an exception due to being out of memory (in which case the std::string constructor would throw because it cannot allocate space). However, why reinvent the wheel? I suggest you take a look at boost::exception, which provides a boost::exception class that allows arbitrary metadata to be attached to the exception object, and it can be as light weight (no metadata attached) or as heavy weight as you want, depending on what metadata you use.

Comment on Passing C++ Exceptions Around in C
The choice to inherit or not to inherit from std::exception has no implication on performance; the article was mentioning the fact that propagating an exception and performing stack unwinding is expensive (regardless of the exception's type). In terms of passing an exception around in the C world.... error handing in C is typically done using integer return types and error codes, although if you want to pass a C++ object around, as well, then if you can polymorphically copy the exception type, then you can simply construct a copy on the heap, and then pass around the exception object as type void*, and if you need to extract information from that object, then you can create C interfaces that are implemented in C++ and which cast the void* object back to a pointer to your exception type and extract the appropriate fields. An alternative, if all you want is the what() message, is to pass around the strdup() of the message. Bear in mind, though, that anything that requires allocation / copying is not going to work if std::bad_alloc is one of the exceptions your handling (in other words, if you can fail due to being out of memory, allocating more memory is not a good idea).

As the saying goes, "When in Rome, do as the Romans do".... my own advice would be to use return status codes and error codes when in C and to use exception handling in C++. I really would not bother trying to bring a C++ exception object into the C world. Also bear in mind, that it is more common for C++ code to call into C code than for C code to call into C++ code. If you are creating C interaces for your C++ code, then they should probably behave like C interfaces, namely returning error status codes. If your C++ code calls a C function that returns an error status code, then it is reasonable to throw an exception rather than returning an error status code where the underlying C code failed. In that case, though, you are in C++ space and don't really have to worry about the exception crossing back into C code. But, in the case where you do cross back into the C world, I would advise logging whatever information you have and using error status codes.

Also, given that std::exception::what() returns a char* object describing what happened, whereas your API returns an integer code indicating the type of exception... well, this will be really confusing to other people using your code. When I saw you use waht() in a switch statement, I was about to comment that you cannot switch on a string, and then had to make a double-take and realize it means something different than usual.

Michael Aaron Safyan
@mike: thanks about boost::exception, I'll take a look at it (most likely, for another project). A little more info on what I'm doing is that I am exposing a C++ library to mono. The C API is required for P/Invoke. I didnt want to dump all that info in my question, as it would cloud the main issue - that of propagating C++ exceptions to a C API.
skyeagle
@mike: point noted about not sticking to convention. I will clarify the code
skyeagle