I am working on a C++ library that performs computations on user-passed data in a closed-source dynamic link library (DLL). I want to perform input checking on the data passed through the API so any errors in the input data can be caught immediately.
I have read that passing exceptions across DLL boundaries is problematic unless the same compiler is used to compile the calling code and the DLL, which cannot be guaranteed. In this case, does it make more sense to just use std::exit()
to ensure the error is correctly propagated?
I am resisting not using the exception, because std::exit
will not allow the user to catch the error. However, if the exception functionality is going to be broken in this case anyway, I guess this is the best option.
[EDIT]
Thanks everyone, in the end I went for return codes for the default error-handling. The situation was slightly more complex than I described, in that there is also a python interface exposed through pybind. Here, exceptions are automatically raised on the python side. Also, I guess the user may not want to handle error-codes for all function calls when prototyping, and just quickly get an error, but not be able to use exceptions across DLL boundaries so I also provide an option to just exit if desired. I ended up wrapping all public function calls internally in he below macro. Internally, all errors thrown are exceptions.
#define HANDLE_ERRORS(block) \
if (g_exceptionMode.errorType == ErrorType::Exception) \
{ \
block \
} \
else \
{ \
try { \
block \
} catch (const std::runtime_error& e) { \
return handleError(e, ErrorCode::runtime_error); \
} catch (const std::invalid_argument& e) { \
return handleError(e, ErrorCode::invalid_argument); \
} catch (const std::exception& e) { \
return handleError(e, ErrorCode::exception); \
} \
} \
return PlotterStatus{ErrorCode::noError, ""};
inline PlotterStatus handleError(const std::exception& e, ErrorCode errorCode)
{
if (g_exceptionMode.errorType == ErrorType::Exit)
{
std::string errorCodeString;
if (errorCode == ErrorCode::runtime_error)
{
errorCodeString = "runtime_error";
}
else if (errorCode == ErrorCode::invalid_argument)
{
errorCodeString = "invalid_argument";
}
else if (errorCode == ErrorCode::exception)
{
errorCodeString = "exception";
}
else
{
std::cerr << "CRITICAL ERROR: errorCode not recognised." << std::endl;
std::exit(EXIT_FAILURE);
}
std::cerr << "Program exited after " << errorCodeString << " thrown: " << e.what() << std::endl;
std::exit(EXIT_FAILURE);
}
else
{
return PlotterStatus{errorCode, e.what()};
}
}
std::exit
will:
Causes normal program termination to occur.
It can be argued that it is just an opinion, but this sounds way too extreme for a library to do in response to invalid input (at least as a general rule).
(Some cleanup steps are taken, but it's far from being a proper error report mechanism).
A possible straight-forward solution is to report errors via a status return code (which can also be augmented to a status struct). This will allow the user code to [potentially] recover from the error if possible.