Механизм, обеспечивающий распространение исключений при смешивании кода C и C ++ - PullRequest
8 голосов
/ 16 октября 2011

Я не спрашиваю, безопасно ли для исключения C ++ распространяться через код C, и что происходит, когда такое происходит. Я прочитал следующие вопросы в SO ( 1 , 2 , 3 ) и в этом FAQ . Я спрашиваю, как перейти к:

  • Избегайте утечки любых исключений 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, который работает с любым типом исключений.

Большое спасибо за любые советы!


РЕДАКТИРОВАТЬ: я добавил ответ с небольшой реализацией механизма удержания / освобождения исключений. Все критики или предложения приветствуются.

Ответы [ 4 ]

4 голосов
/ 18 октября 2011

В 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();
    }
}
4 голосов
/ 16 октября 2011

Я считаю, что в boost.exception есть механизм, который можно адаптировать, чтобы он был полезен для ваших целей. Смотрите здесь для вдохновения:

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

Похоже, он предназначен для передачи их особого типа исключения между потоками, но я думаю, что это почти одно и то же - остановить распространение исключения через границу потока или границу кода C, спрятать его копию за указатель, затем извлеките его позже с помощью указателя на другой стороне границы и при необходимости перебросьте его.

Я не уверен, насколько возможно сделать ваши собственные исключения производными от типа магического исключения Boost, но если я правильно помню, что они работали около года назад, это вполне разумно.

1 голос
/ 19 октября 2011

Редактировать: Вы можете использовать fungo , лучшую реализацию идеи, которую я описал ниже.От ее автора:

fungo - это библиотека C ++, предназначенная для тех из нас, кто застрял в старых реализациях C ++, которые еще не поддерживают std :: exception_ptr.

Другими словами, 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, является throw, у вас есть две возможности:

  • В хранилище добавляется держатель базового типа, поэтому исключение будет поймано и разрезано,сохраняя только копию базового типа.
  • Ни один держатель не подходит для исключения, и он просто теряется.Ничто не просачивается в землю C.

Пока я предпочитаю использовать это решение более проверенному / используемому / проверенному boost :: enable_current_exception , потому что я не могу позволить себе рефакторингвесь код C ++, чтобы окружить все сайты бросков boost::enable_current_exception(...).

В любом случае, std::exception_ptr кажется идеальным решением, и я заменю приведенный выше код, как только смогу перейти на новый стандарт C ++.

1 голос
/ 17 октября 2011

Если вы не участвуете в крупном рефакторинге, можете ли вы определить конкретное подмножество типов исключений, которые могут быть выброшены?

Если это так, вы, вероятно, можете написать контейнер для хранения копий каждого из них, а затем извлечь их по возвращении из C-вызова. Это требует возможности скопировать тип исключения.

В качестве альтернативы, рассмотрите, каково окончательное разрешение различных исключений (если вы можете), и упакуйте это в некоторый объект для обработки по возвращении. Это работает, только если у вас достаточно базы кода, чтобы знать, какой потенциал может генерировать обработка исключений.

Гидридная модель будет перехватывать исключения, создавать новое исключение на основе того, что было найдено, и затем выбрасывать его по возвращении из библиотеки C.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...