You can implement your wrapper for the C interface which uses a context variable. That way that you give the control of the threading problem to the caller.
Here's an untested example:
wrapper.cpp
#include "wrapper.h"
struct context {
std::string last_error_message;
};
char const * ctx_get_last_error(context_handle ctx) {
if(ctx->last_error_message.empty()) return 0;
return ctx.last_error_message.c_str();
}
extern "C" context_handle create_context() {
return new context();
}
extern "C" void free_context(context_handle ctx) {
delete ctx;
}
extern "C" int my_lib_call(context_handle ctx, int some, char const * params) {
try {
lib_call(some, params);
}
catch(std::exception const & e) {
ctx->last_error_message = e.what();
return -1;
}
catch(...) {
ctx->last_error_message = "Unexpected error";
return -1;
}
return 0;
}
wrapper.h
#ifdef _cplusplus
extern "C" {
#endif
typedef struct context * context_handle;
context_handle create_context();
char const * ctx_get_last_error(context_handle ctx);
int my_lib_call(context_handle ctx, int some, char const * params);
#ifdef _cplusplus
}
#endif