Question

I'm currently wrapping an existing C++ library for use in Go. To do that, I have to wrap it with a C shim to then access it from Go. While the C++ API raises exceptions, and I am currently communicating error conditions using custom values in errno, I am trying to figure out a way to communicate the error strings from the exceptions.

My bindings are taking allocated pointers from the C++ API, and wrapping them opaquely => C => Go. Had I authored the 3rd party library, I would have been able to store the last error right on the class, but I need to associate it somewhere else obviously.

Because Go / cgo is multi-threaded, my initial idea was to use thread local storage to store the error string and allow Go to grab it through a GetLastError() function. The problem I found is that on OSX, there isn't thread local storage (from what I understand).

Disclaimer: My C / C++ skills are novice

How could I go about taking the error string from the exception that I have in the C++ side, and store it in a thread-specific way to make it available to my C wrapper (i.e. available to my Go bindings), short of actually resorting to returning a special struct in every one of my error-potential functions? Or is my only option really to make all of my C functions use output parameters, and a special error struct return type?

Edit: (thoughts raised from the suggestion of context objects)

If my C++ -> C shim wraps classes like this:

// foo.h
#ifdef __cplusplus
extern "C" {
#endif

typedef void Thing;
const char* Thing_foo(Thing *ptr);

#ifdef __cplusplus
}
#endif
#endif

// foo.cpp
extern "C" {

const char* Thing_foo(Thing *ptr) {
    return static_cast<CPP::Thing*>(ptr)->foo();
}
}

Would it be basically the same effect as a context to use a struct Thing that carries both the pointer and the last error message?

typedef struct Thing { 
    void *ptr; 
    char *last_err;
} Thing;
Was it helpful?

Solution

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

OTHER TIPS

If your platforms are all POSIX platforms, you can use pthread_[g|s]etspecific to handle thread specific data. The opengroup page on pthread_key_create contains an example how to use this feature.

You could create a small wrapper library that uses thread local variables if available (_Thread_local is part of the new C and C++ standards, C11 and C++11) and uses the pthread functions as a fallback when these are not available.

AFAIR, windows has similar functions, but I am not an expert on this.

If you really don't want to pass an extra parameter you might need to implement an IPC (http://en.wikipedia.org/wiki/Inter-process_communication) system like a queue or named pipe to pass your data around.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top