2012-02-12 11 views
9

Sto scrivendo una libreria in C++ che utilizza un'API C precedente. Il client della mia libreria può specificare funzioni di callback, che vengono chiamate indirettamente attraverso la mia libreria che viene chiamata attraverso l'API C. Ciò significa che tutte le eccezioni nei callback del client devono essere gestite.Passaggio di eccezioni attraverso un limite API C

La mia domanda è questa: come posso catturare l'eccezione su un lato del confine e rilanciarlo una volta che il limite dell'API C è stato riattraversato e l'esecuzione è tornata in C++ land in modo che l'eccezione possa essere gestita da codice cliente?

risposta

5

Con C++ 11 Potremmo usare:

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

Prima di C++ 11 questi possono ancora essere utilizzati tramite Boost Exception.

+0

Se l'eccezione è generata dal valore, il parametro 'exception_ptr' lo manterrà attivo? –

+0

@ SethCarnegie: Secondo lo standard, sì. (§18.8.5/8 "L'oggetto di riferimento deve rimanere valido almeno finché esiste un oggetto' exception_ptr' che si riferisce ad esso. ") L'implementazione Boost dovrebbe fare lo stesso. – kennytm

+0

Questo è fantastico, sembra che il Comitato abbia progettato C++ 11 pensando a me. È anche per la ragione che VS2010 implementa 'exception_ptr',' current_exception' e 'rethrow_exception' che posso accettare questa risposta. Grazie. –

0

Probabilmente è possibile passare una struttura nell'interfaccia C che viene riempita con informazioni di errore in caso di un'eccezione e quindi quando viene ricevuta sul lato client, controllarla e generare un'eccezione all'interno del client, in base ai dati dalla struttura. Se hai solo bisogno di informazioni minime per ricreare la tua eccezione, probabilmente puoi semplicemente usare un numero intero a 32-bit/64-bit come codice di errore. Per esempio:

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

Se avete bisogno di più informazioni di un singolo Int32/Int64 allora si può allocare una struttura di prima della chiamata e passare il suo indirizzo alla funzione C che, a sua volta, intercettare le eccezioni internamente e se succede, genera un'eccezione dalla sua parte.

+0

Mi piacerebbe non perdere l'eccezione se possibile, ad esempio se il callback dell'utente lancia il proprio tipo di eccezione, allora mi piacerebbe lanciarlo anch'io –

+0

@SethCarnegie se stai attraversando i limiti del modulo (presumo che tu faccia , altrimenti tutta questa faccenda non sarebbe un problema), quindi non vedo un modo per conservare le informazioni sulle eccezioni effettive. Un'ulteriore complicazione è che C++ consente a qualsiasi tipo di essere un'eccezione, quindi se si desidera conservarlo, è necessario conoscere i tipi da gestire. Se tutte le eccezioni sono garantite come derivate 'std :: exception', potrebbe non essere molto difficile ma se qualsiasi tipo è permesso, questo cambia le cose. – xxbbcc

3

Alcuni ambienti supportano questo più o meno direttamente.

Ad esempio, se si abilitano le eccezioni structured exception handling e C++ tramite lo switch del compilatore /EH, è possibile implementare eccezioni C++ rispetto alla gestione strutturata delle eccezioni di Microsoft ("eccezioni" per C). A condizione che queste opzioni siano impostate quando si compila tutto il codice (il C++ a ciascuna estremità e la C al centro) lo stack che si svolge si "lavora".

Tuttavia, questa è quasi sempre una cattiva idea (TM). Perché, chiedi? Si consideri che il pezzo di codice C in mezzo è:

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

E che la invoke_cxx_callback() (.... rullo di tamburi ...) invoca il tuo codice C++ che genera un'eccezione. Perderai un blocco mutex. Ahia.

Vedete, il fatto è che la maggior parte del codice C non è scritta per gestire C++ - stack di stile che si svolge in qualsiasi momento nell'esecuzione di una funzione. Inoltre, manca di distruttori, quindi non ha RAII per proteggersi dalle eccezioni.

Kenny TM ha una soluzione per C++ 11 e progetti basati su Boost. xxbbcc ha una soluzione più generale, anche se più noiosa per il caso generale.

+0

Questo non è un problema con Kenny TM perché 'invoke_cxx_callback' restituirebbe normalmente (avendo memorizzato l'eccezione in un' exception_ptr') e quando il codice C ritorna al codice C++, il codice C++ controllerebbe se veniva lanciata un'eccezione e rilanciare se è così. –

+0

@SethCarnegie: lo so, è per questo che ho fatto riferimento a entrambe le altre risposte per "soluzioni". Questa risposta spiega solo perché non esiste un "passaggio" di eccezioni attraverso il codice C. –

+0

* "è possibile implementare eccezioni C++ implementate rispetto alla gestione strutturata delle eccezioni di Microsoft" * - Non è corretto. I compilatori di Microsoft hanno sempre implementato eccezioni C++ in termini di eccezioni SEH. Nessuna scelta lì. L'interruttore del compilatore controlla semplicemente la semantica delle parole chiave di gestione delle eccezioni implementate da MSC. – IInspectable