一种确保混合 C 和 C++ 时异常传播的机制;代码

发布于 2024-12-10 06:59:42 字数 3329 浏览 1 评论 0 原文

我并不是问 C++ 异常通过 C 代码传播是否安全,也不是问发生这种情况时会发生什么。我已阅读 SO(1, < a href="https://stackoverflow.com/questions/6426835/what-happens-if-exception-gets- thrown-through-c-code">2,3) 和 此常见问题解答。我问如何继续:

  • 避免向 C 代码泄漏任何 C++ 异常(这意味着在调用 C 代码之前捕获 C++ 领域中的所有异常)
  • 还能够捕获 C 代码之外的异常(在更高的 C++ 代码中) 。

让我说明一下我的想法:

假设 libfoo 是一个 C 库,我想在我的 bar C++ 程序中使用它。 libfoo 需要一个我必须提供的回调函数foo_callback。我的回调中使用的函数和方法可能会抛出异常,所以我写道:

void my_callback(void)
{
    try
    {
        // Do processing here.
    }
    catch(...)
    {
        // Catch anything to prevent an exception reaching C code.
        // Fortunately, libfoo provides a foo_error function to
        // signal errors and stop processing.
        foo_error() ;
    }
}

然后我使用我的回调,如下所示:

// The bar program.
int main()
{
    // Use libfoo function to set the desired callback
    foo_set_callback(&my_callback) ;

    // Start processing. This libfoo function uses internally my_callback.
    foo_process() ;

    // Check for errors
    if( foo_ok() )
    {
        // Hurray ! 
    }
    else
    {
        // Something gone wrong.
        // Unfortunately, we lost the exception that caused the error :(
    }
}

我想要的是能够捕获从 my_callback 中抛出的异常main 函数,没有异常通过 libfoo 传播(是的,这是一种量子异常实验 量子隧道通过 C 代码)。

所以我想使用的代码:

void my_callback(void)
{
    try
    {
        // Do processing here.
    }
    catch(...)
    {
        // Catch anything to prevent an exception reaching C code.
        // We save the exception using (the magic) ExceptionHolder.
        ExceptionHolder::Hold() ;

        // Call foo_error function to signal errors and stop processing.
        foo_error() ;
    }
}

// The bar program.
int main()
{
    // Use libfoo function to set the desired callback
    foo_set_callback(&my_callback) ;

    try
    {
        // Start processing. This libfoo function uses internally my_callback.
        foo_process() ;

        // Once gone out of the C land, release any hold exception.
        ExceptionHolder::Release() ;
    }
    catch(exception & e)
    {
        // Something gone wrong.
        // Fortunately, we can handle it in some manner.
    }
    catch( /*something else */ )
    {
    }
    // ...
}

考虑到以下限制:

  • libfoo 是源代码封闭的,用 C 编写,并由供应商以编译格式提供。对库进行的测试表明异常无法通过它传播。我无法访问源文件,也无法获得支持异常的编译版本。
  • 回调函数大量使用了使用异常的 C++ 代码。所有的错误处理都是围绕异常机制构建的。我决不能简单地使用吞掉所有异常的代码。
  • 不涉及多线程。
  • 不支持 c++0x。

我的问题:

  • 它是否已经被某些库或某些 C++ 魔法(例如 boost),甚至 c++0x 解决了?
  • 如果没有,我如何编写一个适用于任何异常类型的 ExceptionHolder?我对 C++ 很满意,但我还没有找到一种方法来编写一个可靠且易于使用的 ExceptionHolder,它适用于任何异常类型。

非常感谢您的任何建议!


编辑:我添加了一个响应,其中包含异常保持/释放机制的一些实现。欢迎所有批评或建议。

I'm not asking if it is safe for a C++ exception to propagate through C code, nor what happens when such thing occurs. I have read the following questions in SO(1, 2, 3) and this FAQ. I'm asking how to proceed to :

  • Avoid leaking any C++ exception toward C code (this implies catching all exceptions in the C++ land before calling C code)
  • Also be able to catch the exceptions outside the C code (in a higher C++ code).

Let me illustrate my idea :

Say libfoo is a C library, that I want to use in my bar C++ program. libfoo needs a callback function foo_callback that I must provide. The functions and methods used in my callback may throw an exception, so I wrote :

void my_callback(void)
{
    try
    {
        // Do processing here.
    }
    catch(...)
    {
        // Catch anything to prevent an exception reaching C code.
        // Fortunately, libfoo provides a foo_error function to
        // signal errors and stop processing.
        foo_error() ;
    }
}

And then I use my callback as shown below :

// The bar program.
int main()
{
    // Use libfoo function to set the desired callback
    foo_set_callback(&my_callback) ;

    // Start processing. This libfoo function uses internally my_callback.
    foo_process() ;

    // Check for errors
    if( foo_ok() )
    {
        // Hurray ! 
    }
    else
    {
        // Something gone wrong.
        // Unfortunately, we lost the exception that caused the error :(
    }
}

What I want is to be able to catch the exceptions thrown from my_callback in the main function, without having the exception propagating through libfoo (Yes, it's a sort of quantum exception experimenting quantum tunnelling through C code).

So the code I would love to use :

void my_callback(void)
{
    try
    {
        // Do processing here.
    }
    catch(...)
    {
        // Catch anything to prevent an exception reaching C code.
        // We save the exception using (the magic) ExceptionHolder.
        ExceptionHolder::Hold() ;

        // Call foo_error function to signal errors and stop processing.
        foo_error() ;
    }
}

// The bar program.
int main()
{
    // Use libfoo function to set the desired callback
    foo_set_callback(&my_callback) ;

    try
    {
        // Start processing. This libfoo function uses internally my_callback.
        foo_process() ;

        // Once gone out of the C land, release any hold exception.
        ExceptionHolder::Release() ;
    }
    catch(exception & e)
    {
        // Something gone wrong.
        // Fortunately, we can handle it in some manner.
    }
    catch( /*something else */ )
    {
    }
    // ...
}

Given the following constraints :

  • libfoo is source-closed, written in C and provided in compiled format from a vendor. Tests conducted on the library showed that exceptions cannot propagate through it. I have no access to the source files nor I can obtain a compiled version that supports exceptions.
  • The callback function makes extensive use of C++ code that uses exceptions. All the error handling is built around the exception mechanism. In no way I can simply use the code that swallows all exceptions.
  • No multithreading involved.
  • No c++0x support.

My questions :

  • Is it already solved by some library or some C++ magic (like boost), or even c++0x ?
  • If not, how can I write an ExceptionHolder that works with any exception type ? I'm confortable with C++ but I haven't found a way to write a reliable and easy to use ExceptionHolder that works with any exception type.

Many thanks for any advice !


EDIT : I added a response with a little implementation of the exception holding/releasing mechanism. All critics or propositions are welcome.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

[浮城] 2024-12-17 06:59:42

我相信 boost.exception 有一种机制可以适应您的目的。请参阅此处获取灵感:

http://www.boost。 org/doc/libs/1_47_0/libs/exception/doc/tutorial_exception_ptr.html

它似乎是为了在线程之间传达它们的特殊异常类型,但我认为它几乎是同一件事 - 阻止异常跨线程边界或 C 代码边界传播,将其副本存储在指针后面,然后稍后通过边界另一侧的指针检索它,并可以选择重新抛出它。

我不确定让你自己的异常从 boost 的神奇异常类型派生有多大可行性,但如果我在大约一年前使用它时没记错的话,这是相当合理的。

I believe that boost.exception has a mechanism that could be adapted to be useful for your purpose. See here for inspiration:

http://www.boost.org/doc/libs/1_47_0/libs/exception/doc/tutorial_exception_ptr.html

It seems to be intended for communicating their special exception type between threads, but I think it is pretty much the same thing - stop an exception from propagating across the thread boundary or C code boundary, stash a copy of it away behind a pointer, then retrieve it later via the pointer on the other side of the boundary and optionally rethrow it.

I'm not sure how feasible it is to make your own exceptions derive from boost's magical exception type, but if I remember correctly from tooling around with it a year or so ago, it's fairly reasonable.

梦毁影碎の 2024-12-17 06:59:42

在 c++11 中,您可以在回调中使用 current_exception() 来设置 exception_ptr 类型的变量,然后当库通知您的调用代码有错误时,使用 rethow_exception()。

这与 c++0x 中用于跨线程传播异常的机制相同。

例子:


void foo();                      // C++
extern "C" void bar(void *exp);  // C
void baz(void *exp);             // C++

void foo() {
    std::exception_ptr exp;
    bar(&exp);
    if (exp) {
      std::rethrow_exception(exp);
    }
}

extern "C" void bar(void *exp) {
    baz(exp);
}

void baz(void *exp) {
    try {
        // some code that might throw an exception
        // ...
    } catch (...) { // catch all exceptions, so as to avoid leaking any into the calling C code.
        // capture the exception and make it available to the C++ code above the C code.
        static_cast<std::exception_ptr*>(exp) = std::current_exception();
    }
}

In c++11 you can use current_exception() in your callback to set a variable of type exception_ptr, and then when your calling code is informed of the error by the library use rethow_exception().

This is the same mechanism used to propagate exceptions across threads in c++0x.

Example:


void foo();                      // C++
extern "C" void bar(void *exp);  // C
void baz(void *exp);             // C++

void foo() {
    std::exception_ptr exp;
    bar(&exp);
    if (exp) {
      std::rethrow_exception(exp);
    }
}

extern "C" void bar(void *exp) {
    baz(exp);
}

void baz(void *exp) {
    try {
        // some code that might throw an exception
        // ...
    } catch (...) { // catch all exceptions, so as to avoid leaking any into the calling C code.
        // capture the exception and make it available to the C++ code above the C code.
        static_cast<std::exception_ptr*>(exp) = std::current_exception();
    }
}
暮色兮凉城 2024-12-17 06:59:42

编辑:您可以使用 fungo,这是我在下面描述的想法的更好实现。来自其作者:

fungo 是一个 C++ 库,专为我们这些陷入困境的人而设计使用尚不支持 std::exception_ptr 的较旧 C++ 实现。

换句话说,fungo 允许您公平地尝试存储并稍后重新抛出由 catch(...) 块捕获的异常。这对于跨线程连接或跨 C/C++ 边界接口传播异常很有用。

我会将其标记为答案。


正如我在问题中提到的,我现在无法使用 C++0x/11 功能(目前没有计划使用新功能),我将在这里介绍我到目前为止所做的事情:

异常的生命周期跨越try-catcher 块。为了保存异常,必须在堆上创建一个副本。当重新抛出异常时,我们会删除副本。我编写了一个异常持有者接口:

class ExceptionHolderInterface
{
    public :
      ExceptionHolderInterface(void) ;
      virtual ~ExceptionHolderInterface(void) ;
      
      /* For holding an exception. To be called inside a catch block.*/
      virtual void  Hold(void)    = 0 ;

      /* For releasing an exception. To be called inside a try block.*/
      virtual void  Release(void) = 0 ;
    private :
} ;

这是一个类型独立的类。异常类型是使用模板引入的:

template<typename ExceptionType>
class ExceptionHolder : public ExceptionHolderInterface
{
    public :
      ExceptionHolder(void) ;
      virtual ~ExceptionHolder(void) ;

      virtual void Hold(void)
      {
           try
           {
               throw ;
           }
           catch(const ExceptionType & e)
           {
               exception.reset(new ExceptionType(e)) ;
           }
      }

      virtual void Release(void)
      {
          if(exception.get())
          {
              throw ExceptionType(*exception.get()) ;
          }
      }
    private :
      std::auto_ptr<ExceptionType> exception ;
      
      // declare the copy-constructor and the assignment operator here to make the class non-copyable
} ;

我删除了一堆测试/优化/验证,我保留了主要思想。到目前为止,我们已经有了一种类型的异常持有者,因此我们可以构建一个可以同时持有多种类型的异常存储

class ExceptionStore
{
    public :
      ExceptionStore(void) ;
      ~ExceptionStore(void)
      {
          for(Iterator holder = exception_holders.begin() ; holder != exception_holders.end() ; ++holder)
          {
              delete (*holder) ;
          }
      }

      // Add an exception type to handle
      template<typename ExceptionType>
      void AddExceptionHolder(void)
      {
          exception_holders.push_back(new ExceptionHolder<ExceptionType>()) ; 
      }

      // Try to hold an exception using available holders. Use this inside a catch block.
      void Hold(void)
      {
          Iterator holder = exception_holders.begin() :
          while(holder != exception_holders.end())
          {
              try
              {
                  (*holder)->Hold() ;
                  break ;
              }
              catch(...)
              {
                  ++holder ;
              }
          }
      }

      // Try to release any hold exception. Call this inside a try-block.
      void Release(void)
      {
          Iterator holder = exception_holders.begin() :
          while(holder != exception_holders.end())
          {
              (*holder++)->Release() ;
          }
      }

    private :
      std::list<ExceptionHolderInterface *>  exception_holders ;
      typedef std::list<ExceptionHolderInterface *>::iterator Iterator ;
      
      // Declare the copy-constructor and the assignment operator here to make the class non-copyable
} ;

我可以使用异常存储,如下所示:

// I made a global ExceptionStore just to keep the example simple.
ExceptionStore exception_store ;

void callable_from_c_code(void)
{
    // Normally, we should retrieve the exception store in some manner.
    
    try
    {
        // Do processing here. Exceptions may be thrown.
    }
    catch(...)
    {
        // Something wrong happened. Let's store the error for later.
        exception_store.Hold() ;
    }

    // Exceptions do not propagate to C code. 
} 

int main(int, char * [])
{
    // First, set the exception types we want to handle. The handling is done in
    // the same order as below.
    exception_store.AddExceptionHolder<std::runtime_error>() ;
    exception_store.AddExceptionHolder<std::logic_error>() ;
    exception_store.AddExceptionHolder<MyFancyException>() ;

    // Somehow invoke some C code that uses `callable_from_c_code`
    use_some_c_library_with_callback(&callable_from_c_code) ;

    // Handle any caught exception
    try
    {
        exception_holder.Release() ;
    }
    catch(std::exception &)
    {
        // Something gone wrong ...
    }
    catch(MyFancyException &)
    {
        // Nothing fancy despite the name. We have problems here ...
    }
}

这是非常基本的,并且可能存在一些本示例未处理的意外场景。如果抛出未使用 AddExceptionHolder 声明的类型的异常,则有两种可能性:

  • 将基本类型的持有者添加到存储中,因此异常将被捕获并切片,仅保留副本的基本类型。
  • 没有一个持有人适合例外,并且它只是丢失了。没有任何事情泄露到C地。

目前,我更喜欢使用此解决方案,而不是经过更多测试/使用/验证的 boost::enable_current_exception 因为我无法重构整个 C++ 代码来包围所有抛出站点boost::enable_current_exception(...)

不管怎样,std::exception_ptr 似乎是完美的解决方案,一旦我可以转向新的 C++ 标准,我将替换上面的代码。

Edit : You can use fungo, a better implementation of the idea I described below. From its author :

fungo is a C++ library that is designed for those of us stuck using older C++ implementations that do not yet support std::exception_ptr.

In other words, fungo allows you to make a fair attempt at storing and later re-throwing exceptions caught by a catch(...) block. This is useful for propagating exceptions across thread-joins or over C/C++ boundary interfaces.

I'll mark this as an answer.


As I mentioned in my question, I cannot use C++0x/11 features for now (using new features isn't planned for now), and I will present here what I have done so far :

Exceptions have a lifetime that spans across the try-catcher block. In order to save an exception, one must create a copy on the heap. We get rid of the copy when re-throwing the exception. I wrote an exception holder interface :

class ExceptionHolderInterface
{
    public :
      ExceptionHolderInterface(void) ;
      virtual ~ExceptionHolderInterface(void) ;
      
      /* For holding an exception. To be called inside a catch block.*/
      virtual void  Hold(void)    = 0 ;

      /* For releasing an exception. To be called inside a try block.*/
      virtual void  Release(void) = 0 ;
    private :
} ;

This is a type independent class. The exception type is introduced using templates :

template<typename ExceptionType>
class ExceptionHolder : public ExceptionHolderInterface
{
    public :
      ExceptionHolder(void) ;
      virtual ~ExceptionHolder(void) ;

      virtual void Hold(void)
      {
           try
           {
               throw ;
           }
           catch(const ExceptionType & e)
           {
               exception.reset(new ExceptionType(e)) ;
           }
      }

      virtual void Release(void)
      {
          if(exception.get())
          {
              throw ExceptionType(*exception.get()) ;
          }
      }
    private :
      std::auto_ptr<ExceptionType> exception ;
      
      // declare the copy-constructor and the assignment operator here to make the class non-copyable
} ;

I removed a bunch of tests/optimizations/verifications, I kept the main idea. So far we have an exception holder for one type, so we can build an exception store that can hold many types at a time.

class ExceptionStore
{
    public :
      ExceptionStore(void) ;
      ~ExceptionStore(void)
      {
          for(Iterator holder = exception_holders.begin() ; holder != exception_holders.end() ; ++holder)
          {
              delete (*holder) ;
          }
      }

      // Add an exception type to handle
      template<typename ExceptionType>
      void AddExceptionHolder(void)
      {
          exception_holders.push_back(new ExceptionHolder<ExceptionType>()) ; 
      }

      // Try to hold an exception using available holders. Use this inside a catch block.
      void Hold(void)
      {
          Iterator holder = exception_holders.begin() :
          while(holder != exception_holders.end())
          {
              try
              {
                  (*holder)->Hold() ;
                  break ;
              }
              catch(...)
              {
                  ++holder ;
              }
          }
      }

      // Try to release any hold exception. Call this inside a try-block.
      void Release(void)
      {
          Iterator holder = exception_holders.begin() :
          while(holder != exception_holders.end())
          {
              (*holder++)->Release() ;
          }
      }

    private :
      std::list<ExceptionHolderInterface *>  exception_holders ;
      typedef std::list<ExceptionHolderInterface *>::iterator Iterator ;
      
      // Declare the copy-constructor and the assignment operator here to make the class non-copyable
} ;

I can use the exception store as shown below :

// I made a global ExceptionStore just to keep the example simple.
ExceptionStore exception_store ;

void callable_from_c_code(void)
{
    // Normally, we should retrieve the exception store in some manner.
    
    try
    {
        // Do processing here. Exceptions may be thrown.
    }
    catch(...)
    {
        // Something wrong happened. Let's store the error for later.
        exception_store.Hold() ;
    }

    // Exceptions do not propagate to C code. 
} 

int main(int, char * [])
{
    // First, set the exception types we want to handle. The handling is done in
    // the same order as below.
    exception_store.AddExceptionHolder<std::runtime_error>() ;
    exception_store.AddExceptionHolder<std::logic_error>() ;
    exception_store.AddExceptionHolder<MyFancyException>() ;

    // Somehow invoke some C code that uses `callable_from_c_code`
    use_some_c_library_with_callback(&callable_from_c_code) ;

    // Handle any caught exception
    try
    {
        exception_holder.Release() ;
    }
    catch(std::exception &)
    {
        // Something gone wrong ...
    }
    catch(MyFancyException &)
    {
        // Nothing fancy despite the name. We have problems here ...
    }
}

This is very basic, and there might be some unexpected scenarii that are not handled by this example. If an exception with a type not declared using AddExceptionHolder is throw, you have two possibilities :

  • A holder for a base type is added to the store, so the exception will be caught and sliced, keeping only a copy of the base type.
  • No holder fits the exception, and it is simply lost. Nothing leaks to the C land.

For now, I prefer using this solution to the more tested/used/verified boost::enable_current_exception because I can't afford refactoring the whole C++ code to surround all throw sites with boost::enable_current_exception(...).

Anyway, the std::exception_ptr seems to be the perfect solution, and I will replace the above code once I can move to the new C++ standard.

清音悠歌 2024-12-17 06:59:42

如果您不准备进行重大重构,您是否能够识别可能引发的异常类型的特定子集?

如果是这样,您可能可以编写一个容器来专门存储每个副本,然后在从 C 调用返回时检索它们。这需要能够复制异常类型。

或者,考虑各种异常的最终解决方案是什么(如果可以的话),并将其打包到某个对象中以便在返回时进行处理。仅当您拥有足够的代码库以了解可能会产生哪些潜在的异常处理时,这才有效。

Hydrid 模型会捕获异常,根据发现的内容创建一个新的异常,然后在从 C 库返回时抛出该异常。

If you are not up for a major refactor, are you able to identify a specific subset of exception types that might be thrown?

If so, you can probably write a container to store copies of each of these specifically, and then retrieve them on return from the C-call. This requires being able to copy the exception type.

Alternatively, consider what the ultimate resolution of the various exceptions is (if you can), and pack that up into some object for processing on return. This only works if you own enough of the codebase to know what potential handling the exceptions may generate.

A hydrid model would trap the exceptions, create a new exception based on what was found, and then throw that on return from the C lib.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文