通过C API边界传递异常



我正在C++中编写一个库,该库使用较旧的C API。我的库的客户端可以指定回调函数,这些函数是通过我的库间接调用的,我的库是通过C API调用的。这意味着必须处理客户端回调中的所有异常。

我的问题是:如何在边界的一侧捕获异常,并在重新跨越C API边界并且执行返回到C++区域后重新处理它,以便客户端代码可以处理该异常?

使用C++11,我们可以使用:

std::exception_ptr active_exception;
try
{
    // call code which may throw exceptions
}
catch (...)
{
    // an exception is thrown. save it for future re-throwing.
    active_exception = std::current_exception();
}
// call C code
...
// back to C++, re-throw the exception if needed.
if (active_exception)
    std::rethrow_exception(active_exception);

在C++11之前,这些仍然可以通过Boost Exception使用。

有些环境或多或少直接支持这一点。

例如,如果通过/EH编译器开关启用结构化异常处理和C++异常,则可以在Microsoft的结构化异常处理之上实现C++异常(C的"异常")。如果在编译所有代码(两端的C++和中间的C)时设置了这些选项,则堆栈展开将"工作"。

然而,这几乎总是一个坏主意(TM)你问为什么?考虑一下中间的C代码是:

WaitForSingleObject(mutex, ...);
invoke_cxx_callback(...);
ReleaseMutex(mutex);

并且invoke_cxx_callback()(…drum-roll…)调用抛出异常的C++代码。您将泄漏互斥锁。哎哟

你看,问题是大多数C代码并不是为了在函数执行的任何时候处理C++风格的堆栈展开而编写的。此外,它缺少析构函数,所以它没有RAII来保护自己免受异常的影响。

Kenny TM为基于C++11和Boost的项目提供了解决方案。对于一般情况,xxbbcc有一个更通用的解决方案,尽管更乏味。

您可能可以在C接口上传递一个结构,该结构在出现异常时会填充错误信息,然后当客户端收到错误信息时,根据结构中的数据对其进行检查并在客户端内部引发异常。如果您只需要最少的信息来重新创建异常,那么您可能只需要使用一个32位/64位整数作为错误代码。例如:

typedef int ErrorCode;
...
void CMyCaller::CallsClient ()
{
    CheckResult ( CFunction ( ... ) );
}
void CheckResult ( ErrorCode nResult )
{
    // If you have more information (for example in a structure) then you can
    // use that to decide what kind of exception to throw.)
    if ( nResult < 0 )
        throw ( nResult );
}
...
// Client component's C interface
ErrorCode CFunction ( ... )
{
    ErrorCode nResult = 0;
    try
    {
        ...
    }
    catch ( CSomeException oX )
    {
        nResult = -100;
    }
    catch ( ... )
    {
        nResult = -1;
    }
    return ( nResult );
}

如果您需要比单个int32/int64更多的信息,那么您可以在调用之前分配一个结构,并将其地址传递给C函数,C函数将在内部捕获异常,如果发生异常,则在其自身引发异常。

相关内容

最新更新