【问题标题】:Transferring error strings from C++ => C wrapped API (multi-threaded)从 C++ 传输错误字符串 => C 包装 API(多线程)
【发布时间】:2014-01-04 09:15:25
【问题描述】:

我目前正在包装一个现有的 C++ 库以在 Go 中使用。为此,我必须用 C 垫片包装它,然后从 Go 访问它。虽然 C++ API 引发异常,并且我目前正在使用 errno 中的自定义值来传达错误条件,但我正在尝试找出一种从异常中传达错误字符串的方法。

我的绑定从 C++ API 获取分配的指针,并将它们不透明地包装 => C => Go。如果我编写了第 3 方库,我本来可以将最后一个错误存储在类中,但显然我需要将它关联到其他地方。

因为 Go/cgo 是多线程的,所以我最初的想法是使用线程本地存储来存储错误字符串,并允许 Go 通过GetLastError() 函数抓取它。我发现的问题是在 OSX 上,没有线程本地存储(据我了解)。

免责声明:我的 C/C++ 技能是新手

如何从 C++ 端的异常中获取错误字符串,并以特定于线程的方式存储它以使其可用于我的 C 包装器(即可用于我的 Go 绑定),简而言之实际上在我的每一个可能出错的函数中都返回一个特殊的结构?还是我唯一的选择真的是让我的所有 C 函数都使用输出参数和特殊的错误结构返回类型?

编辑:(从上下文对象的建议中提出的想法)

如果我的 C++ -> C shim 像这样包装类:

// 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();
}
}

使用struct Thing同时携带指针和最后一个错误消息是否与上下文基本相同?

typedef struct Thing { 
    void *ptr; 
    char *last_err;
} Thing;

【问题讨论】:

  • 如果这是您的问题,那么您应该考虑为您的库提供一个上下文对象,您可以在其中存储这样的数据。并且 Thread Local Storage 不会为您解决问题,即使它可以在 OSX 上运行。因为 Go 并不为每个 goroutine 使用一个线程。
  • @Vinzenz - 但是如何在没有线程本地存储的情况下管理线程特定的上下文?如果两个不同的线程访问对象并在不同的调用中生成单独的错误怎么办?或者更常见的......两个不同的调用通常会在不同的线程中产生错误,
  • 你实现这个POSIX线程、pthread、平台的都是线程平台吗?
  • @jdi 使用上下文对象,您不需要线程本地存储。线程本地存储只不过是线程特定的全局变量。我不知道你的代码是什么样子的,但是为每个想要使用你的库的调用者创建一个上下文对象会有什么问题,你在内部存储诸如最后的错误消息等之类的东西。在你的 C API 中您总是将此上下文对象作为第一个参数传递。如果您从 Go 调用,您可以创建它并根据需要管理这些上下文对象,但恕我直言,这是最安全的方法。
  • @Vinzenz - 我想这比输出参数和自定义错误返回类型更容易切换。

标签: c++ c multithreading error-handling thread-local


【解决方案1】:

您可以为使用上下文变量的 C 接口实现包装器。这样您就可以将线程问题的控制权交给调用者。

这是一个未经测试的例子:

包装器.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;
}

包装器.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

【讨论】:

  • 我会选择这个,因为我最终要么做上下文对象,要么将我的指针包装到一个携带上下文数据的结构中。谢谢!
【解决方案2】:

如果您的平台都是 POSIX 平台,您可以使用pthread_[g|s]etspecific 来处理特定于线程的数据。 pthread_key_create 上的 opengroup 页面包含如何使用此功能的示例。

您可以创建一个使用线程局部变量(如果可用)的小型包装库(_Thread_local 是新 C 和 C++ 标准、C11 和 C++11 的一部分),并在不使用 pthread 函数时使用它们作为后备可用。

AFAIR,windows也有类似的功能,但我不是这方面的专家。

【讨论】:

  • 我将不得不研究什么会/不会限制我使用 windows,但乍一看 pthread set/get api 似乎非常简单。如果它不是 [Linux、Windows、OSX、FreeBSD] 中的限制因素,那么我会尝试这种方法。否则,我想答案是将上下文(或至少 char* err)作为参数传递给我的所有 C 包装函数。
  • 这是很棒的信息,我尝试应用它。发现线程本地存储实际上不是 Go 的好主意(显然与您的答案完全无关,这很好)。从 Go 到 cgo 的调用可以产生不同的 OS 线程(出于调度程序的原因),因此从 Go 端获取从线程本地存储中检索最后一个错误的访问通常不是来自 C++ 代码运行和存储值的同一线程。我将不得不接受关于在通话中转移上下文的其他建议答案。
【解决方案3】:

如果您真的不想传递额外的参数,您可能需要实现 IPC (http://en.wikipedia.org/wiki/Inter-process_communication) 系统,例如队列或命名管道来传递您的数据。

【讨论】:

  • 嗯...对我来说,我应该修改我的绑定以使用输出参数并返回一个特殊的结构。管理与每个新对象的创建相关的队列/管道似乎要复杂得多。谢谢(你的)信息。我会再等一会儿,看看是否有其他人提供答案。
猜你喜欢
  • 2014-10-19
  • 2018-02-02
  • 1970-01-01
  • 1970-01-01
  • 2020-05-08
  • 1970-01-01
  • 2017-06-05
  • 2019-06-01
  • 1970-01-01
相关资源
最近更新 更多