2011-10-15 11 views
8

No estoy preguntando si es seguro que una excepción en C++ se propague a través del código C, ni qué sucede cuando ocurre eso. He leído las siguientes preguntas en SO (1, 2, 3) y . Estoy preguntando cómo proceder para:Un mecanismo para asegurar la propagación de excepciones al mezclar el código C y C++

  • evitar fugas ninguna excepción C++ hacia el código C (esto implica la captura de todas las excepciones en la tierra C++ antes de llamar a código C)
  • también ser capaz de detectar las excepciones fuera de la Código C (en un código C++ más alto).

Vamos a ilustrar mi idea:

Say libfoo es una biblioteca de C, que yo quiero usar en mi programa bar C++. libfoo necesita una función de devolución de llamada foo_callback que debo proporcionar. Las funciones y los métodos utilizados en mi devolución de llamada puede lanzar una excepción, por lo que escribieron:

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

Y entonces yo uso mi devolución de llamada como se muestra a continuación:

// 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 :(
    } 
} 

Lo que yo quiero es ser capaz de atrapar las excepciones lanzadas desde my_callback en la función main, sin tener la excepción de propagarse a través de libfoo (Sí, es una especie de excepción cuántica que experimenta quantum tunnelling a través del código C).

Así que el código me gustaría usar:

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 */) 
    { 
    } 
    // ... 
} 

Dadas las siguientes limitaciones:

  • libfoo es de código cerrado, escrito en C y proporcionado en formato compilado a partir de un proveedor. Las pruebas realizadas en la biblioteca mostraron que las excepciones no pueden propagarse a través de ella. No tengo acceso a los archivos fuente ni puedo obtener una versión compilada que admita excepciones.
  • La función de devolución de llamada hace un uso extensivo del código C++ que usa excepciones. Todo el manejo de errores se basa en el mecanismo de excepción. De ninguna manera, simplemente puedo usar el código que se traga todas las excepciones.
  • Sin multihilo involucrado.
  • Sin soporte de C++ 0x.

Mis preguntas:

  • ¿Es ya resuelto por alguna biblioteca o un poco de magia C++ (como boost), o incluso C++ 0x?
  • Si no es así, ¿cómo puedo escribir un ExceptionHolder que funcione con cualquier tipo de excepción? Estoy cómodo con C++ pero no he encontrado la manera de escribir un ExceptionHolder confiable y fácil de usar que funcione con cualquier tipo de excepción.

Muchas gracias por cualquier consejo!


EDIT: He añadido una respuesta con un poco de implementación de la celebración de excepción/mecanismo de liberación. Todos los críticos o proposiciones son bienvenidos.

Respuesta

1

Editar: Puede utilizar fungo, una mejor aplicación de la idea que he descrito a continuación. A partir de su autor:

fungo es una biblioteca de C++ que está diseñado para aquellos de nosotros pegado usando más antiguas implementaciones de C++ que aún no admiten std :: exception_ptr.

En otras palabras, fungo le permite hacer un buen intento de almacenar y luego volver a lanzar excepciones capturadas por un bloque catch (...). Esto es útil para propagar excepciones entre las uniones de subprocesos o sobre las interfaces de límites de C/C++.

Voy a marcar esto como una respuesta.


Como ya he mencionado en mi pregunta, no puedo usar C++ 0x/11 características de momento (utilizando funciones nuevas que no está prevista por ahora), y voy a presentar aquí lo que he hecho hasta ahora:

Las excepciones tienen un tiempo de vida que se extiende por el bloque de prueba-receptor. Con el fin de guardar una excepción, se debe crear una copia en el montón. Nos deshacemos de la copia cuando volvemos a lanzar la excepción. Escribí un excepción titular interfaz:

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 : 
} ; 

Ésta es una clase independiente tipo. El tipo de excepción se introduce el uso de plantillas:

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 retira un montón de pruebas/optimizaciones/verificaciones, me quedé con la idea principal. Hasta ahora tenemos un titular de excepción para un tipo, por lo que podemos construir un almacén de excepción que puede contener muchos tipos a la vez.

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 
} ; 

puedo utilizar el almacén de excepción, como se muestra a continuación:

// 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 ... 
    } 
} 

Esto es muy básico, y que podría haber algunos scenarii inesperada que no están a cargo de este ejemplo. Si una excepción con un tipo que no declarada usando AddExceptionHolder es un tiro, tiene dos posibilidades:

  • Se añade un soporte para un tipo base a la tienda, por lo que la excepción será capturado y en rodajas, manteniendo sólo una copia de el tipo base
  • Ningún soporte se ajusta a la excepción, y simplemente se pierde. Nada se filtra a la tierra C

Por ahora, yo prefiero usar esta solución para el más probado/usado/verificado boost::enable_current_exception porque no puede permitirse el lujo de refactorización todo el código C++ para rodear todos los sitios de tiro con boost::enable_current_exception(...).

De todos modos, el std::exception_ptr parece ser la solución perfecta, y reemplazaré el código anterior una vez que pueda pasar al nuevo estándar de C++.

4

Creo que boost.exception tiene un mecanismo que podría adaptarse para ser útil para su propósito. Vea aquí en busca de inspiración:

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

Parece estar destinado a la comunicación de su tipo de excepción especial entre hilos, pero creo que es más o menos la misma cosa - detener una excepción se propague a través del límite de rosca o C límite de código, oculte una copia de él detrás de un puntero, luego recupérelo más tarde a través del puntero del otro lado del límite y opcionalmente vuelva a tirarlo.

No estoy seguro de cuán factible es hacer que sus propias excepciones se deriven del tipo de excepción mágica de boost, pero si recuerdo correctamente haber usado herramientas hace un año o más, es bastante razonable.

+0

Probablemente valga la pena mencionar que el enfoque de impulso refleja bastante directamente los mecanismos que se han introducido en la biblioteca estándar en C++ 0x para abordar el mismo problema. Son un poco más mágicos, por lo que puedo decir. – ben

+0

Gracias! Investigaré tu proposición, ya que parece prometedor :) – overcoder

+0

Después de jugar con 'boost :: enable_current_exception', encontré que tiene poca flexibilidad, ya que obliga al usuario a rodear todas las excepciones con' boost :: enable_current_exception' en todos los posibles sitios de lanzamiento antes de poder usarlo. No me puedo permitir esta refactorización * major * en la base de código actual. – overcoder

1

Si no está preparado para un refactor importante, ¿puede identificar un subconjunto específico de tipos de excepción que podrían arrojarse?

Si es así, probablemente pueda escribir un contenedor para almacenar copias de cada uno de estos específicamente, y luego recuperarlos a cambio de la C-call. Esto requiere poder copiar el tipo de excepción.

Como alternativa, tenga en cuenta cuál es la resolución final de las diversas excepciones (si puede), y empaquételo en algún objeto para su procesamiento en el momento de la devolución. Esto solo funciona si posee suficiente de la base de código para saber qué manejo potencial pueden generar las excepciones.

Un modelo de Hydrid atraparía las excepciones, crearía una nueva excepción basada en lo que se encontró y luego la arrojaría a la vuelta de la biblioteca de C lib.

+0

¡Gracias! Eso es básicamente lo que estoy haciendo actualmente, pero manejar un subconjunto específico de tipos de excepciones es el punto débil de mi solución actual. La falta de algunos casos rompe la fiabilidad. – overcoder

+0

No estoy seguro de seguir. Si el contenedor de la biblioteca no sabe qué hacer con la excepción, ¿es tan frecuente que el código de llamada lo haga? De lo contrario, una captura (...) que se correlaciona con un solo tipo para el nuevo lanzamiento le daría el comportamiento definido que desea. – Keith

+0

Buena idea. Insistir en las excepciones me hace sentir como hacer control de flujo con la herramienta incorrecta. Mi código necesita un replanteamiento ... – overcoder

2

En C++ 11 puede usar current_exception() en su devolución de llamada para establecer una variable de tipo exception_ptr, y luego cuando su código de llamada sea informado del error por la biblioteca, use rethow_exception().

Este es el mismo mecanismo utilizado para propagar excepciones entre subprocesos en C++ 0x.

Ejemplo:


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