17

I am developing C++ component dll that can be used by C or C++ applications. The exposed dll functions are as follows

#include <tchar.h>
#ifdef IMPORT
#define DLL __declspec(dllimport)
#else 
#define DLL __declspec(dllexport)
#endif

extern "C" {

    DLL  bool __cdecl Init();
    DLL  bool __cdecl Foo(const TCHAR*);
    DLL  bool __cdecl Release();

}

the internal implementation of these functions are C++ classes which are not exposed, I assume using this style the dll can be used either in C or C++ apps. The problem is I do not handle any type of c++ exception (i.e. bad_alloc) and I left this stuff to the caller (the higher layer). After much debate with my colleagues that I should catch all the exceptions and return error code or at least false because in case of C application it can not handle C++ exceptions? is that true? and what I should do in general? is there a rule of thumb for handling exeptions if you are developing component that will be used by other system.

Idan K
  • 20,443
  • 10
  • 63
  • 83
Ahmed
  • 7,148
  • 12
  • 57
  • 96
  • 2
    No, there is no such thing as an exception in C. – sje397 Dec 15 '10 at 10:05
  • http://www.on-time.com/ddj0011.htm – DumbCoder Dec 15 '10 at 10:11
  • 3
    C++ can register a C-linkage / `extern C` function as exception handler for uncaught exceptions via `std::set_unexpected()`. If the mangled name for that is known you could call it directly from plain C. Otherwise, you can expose an `extern C` wrapper for it. Not that this is a convenient interface, but if it's absolutely unavoidable that your library pollutes outside its own boundaries ... – FrankH. Dec 15 '10 at 10:24
  • @FrankH: this should be an answer, not a comment. Many libraries (eg. legacy fortran libraries) use the handler approach. – Alexandre C. Dec 15 '10 at 10:46
  • 1
    @DumbCoder: don't do this in real code. – Alexandre C. Dec 15 '10 at 10:47
  • In an actual experiment, I needed to use `std::set_terminate()` instead of `std::set_unexpected()` to get the C++ runtime redirect to a function provided by C. Example below. – FrankH. Dec 15 '10 at 10:47
  • @Alexandre C.: full ack on the "don't do this in real code.". – FrankH. Dec 15 '10 at 10:58

5 Answers5

20

C doesn't have exceptions, therefore in general you should catch all exception and return an error code and/or provide a function that returns the information about the last error.

vitaut
  • 49,672
  • 25
  • 199
  • 336
  • "C doesn't have exceptions". Why not; C++ runtime has exceptions, and C interfacing to C++ runtime has the C++ runtime, so why can't C intercept the C++ runtime for a specific implementation of exceptions(different systems may have different implementation, but the implementation is still there and must exist in memory, and even if it is not exposed, you can analyze the memory to figure it out and poke different bytes and call unexposed functions). – Dmytro Nov 29 '16 at 23:26
19

If this is Windows using MSVC then yes you can catch exceptions in C but you can't catch them that well. C++ exceptions are fed through the OS ABI's Structured Exception Handling mechanism, and Microsoft have a __try, __except, __finally C extension to handle OS structured exceptions. Note that these include access violations, divide-by-zero, etc. that you'd normally want to terminate your program and log a bug report. You can identify C++ exceptions by code 0xE04D5343 (4D 53 43 = "MSC") and throw the rest on.

That all said, you probably don't want to be throwing exceptions across a DLL boundary, and certainly not if you're only exposing a C API.

Rup
  • 33,765
  • 9
  • 83
  • 112
13

As a general rule, you should never allow C++ exceptions to propagate beyond a module's boundary. This is because the C++ standard does not specify how exception propagation has to be implemented, and as such this is compiler (and compiler flags) and operating system dependent. You cannot guarantee that the code calling your module will be compiled with the same compiler with the same compiler flags as your module. In fact, as you're demonstrating with this question is that you cannot guarantee that code calling your module will be written in the same language.

For more details, please refer to Item 62 in C++ Coding Standards by Sutter and Alexandrescu.

CadentOrange
  • 3,263
  • 1
  • 34
  • 52
9

Ok, since it was asked for:

C++ example code:

#include <typeinfo>
#include <exception>
extern "C" {
void sethandler(void (*func)(void)) { std::set_terminate(func); }
int throwingFunc(int arg) {
    if (arg == 0)
        throw std::bad_cast();
    return (arg - 1);
}
}

C example code:

#include <stdio.h>

extern int throwingFunc(int arg);
extern void sethandler(void (*func)(void));

void myhandler(void)
{
    printf("handler called - must've been some exception ?!\n");
}

int main(int argc, char **argv)
{
    sethandler(myhandler);

    printf("throwingFunc(1) == %d\n", throwingFunc(1));
    printf("throwingFunc(-1) == %d\n", throwingFunc(-1));
    printf("throwingFunc(0) == %d\n", throwingFunc(0));
    return 0;
}

When I compile these two, link them together and run this (Ubuntu 10.04, gcc 4.4.5, x64), I get:

$ ./xx
throwingFunc(1) == 0
throwingFunc(-1) == -2
handler called - must've been some exception ?!
Aborted

So while you can catch exceptions from C, that's hardly sufficient - because C++ runtime behaviour after std::terminate() is undefined, and because the handler gets no status information whatsoever (to distiguish exception types and/or sources). The handler cannot clean up anything.

Btw, I've deliberately chosen std::bad_cast() as exception type in this example. This is because throwing that illustrates a difference in behaviour between std::set_unexpected() and std::set_terminate() - the unexpected handler will get called for all non std::bad_* exceptions, while in order to catch standard exceptions, a terminate handler is required ... see the dilemma ? The harness is too wide to be of practical use :(

FrankH.
  • 17,675
  • 3
  • 44
  • 63
  • Actually the point in doing this is only to provide the user with an error message before aborting. The point is that what should be caught has to be caught, and for the remaining exceptions, be as descriptive as possible. – Alexandre C. Dec 15 '10 at 10:59
  • That's why I say "dilemma" - you can catch the exception and log an error, but how descriptive is _as descriptive as possible_ ? From the fact that the handler is passed no context whatsoever, I'd say "not very" ... – FrankH. Dec 15 '10 at 11:09
7

Only propagate exceptions to the caller if the caller is designed to handle them. I guess in your case terminate() will be called immediately once any exception escapes C++ code because from the point of C++ runtime that exception has not been handled.

The same situation arises in COM servers design - the clients can be in whatever language/technology. The rul is that no exceptions should escape COM server methods - all exceptions must be caught and translated in HRESULT and (optionally) IErrorInfo. You should do similarly in your situations.

In case C code is sandwiched between two layers of C++ code propagating exceptions to C code is still a very bad idea.

Community
  • 1
  • 1
sharptooth
  • 167,383
  • 100
  • 513
  • 979