2012-02-12 11 views
9

Piszę bibliotekę w C++, która używa starszego C API. Klient mojej biblioteki może określać funkcje wywołania zwrotnego, które są pośrednio wywoływane przez moją bibliotekę, która jest wywoływana przez C API. Oznacza to, że wszystkie wyjątki w oddzwanianiu klienta muszą być obsługiwane.Przekazywanie wyjątków przez granicę C API

Moje pytanie brzmi: jak mogę uchwycić wyjątek po jednej stronie granicy i ponownie go wyrzucić, gdy granica C API zostanie przekreślona, ​​a wykonanie powróci do C++, aby wyjątek mógł być obsługiwany przez kod klienta?

Odpowiedz

5

z C++ 11 możemy użyć:

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

Przed C++ 11 te mogą być nadal używane przez Boost Exception.

+0

Jeśli wyjątek jest zgłaszany przez wartość, czy 'wyjątek_ptr' zachowa go przy życiu? –

+0

@SethCarnegie: Standardowo, tak. (§18.8.5/8 "Obiekt przywoływany zachowuje ważność co najmniej tak długo, jak istnieje obiekt' exception_ptr', który się do niego odnosi. ") Implementacja Boost powinna być taka sama. – kennytm

+0

To jest niesamowite, wygląda na to, że Komitet zaprojektował dla mnie C++ 11. To również dlatego, że VS2010 implementuje 'wyjątek_ptr',' current_exception' i 'rethrow_exception', które mogę zaakceptować. Dzięki. –

0

Prawdopodobnie możesz przekazać strukturę przez interfejs C, który zostanie wypełniony informacjami o błędach w przypadku wyjątku, a następnie po otrzymaniu po stronie klienta, sprawdź i wyrzuć wyjątek do klienta, na podstawie danych ze struktury. Jeśli potrzebujesz tylko minimalnych informacji do odtworzenia wyjątku, prawdopodobnie możesz po prostu użyć 32-bitowej/64-bitowej liczby całkowitej jako kodu błędu. Na przykład:

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

Jeżeli potrzebują Państwo więcej informacji niż jednym int32/Int64 następnie można przeznaczyć strukturę przed wywołaniem i przekazać swój adres do funkcji C, która, z kolei, złapać wyjątki wewnętrznie i jeśli się zdarza, rzuca wyjątek po swojej stronie.

+0

Chciałbym nie zgubić wyjątku, jeśli to możliwe, na przykład, jeśli wywołanie zwrotne użytkownika rzuca własny typ wyjątku, to chciałbym go również rzucić –

+0

@SethCarnegie, jeśli przekraczasz granice modułów (zakładam, że robisz w przeciwnym razie to wszystko nie byłoby problemem), wtedy nie widzę sposobu, aby zachować faktyczne informacje o wyjątku. Kolejną komplikacją jest to, że C++ pozwala na wyjątek dowolnego typu, więc jeśli chcesz go zachować, musisz wiedzieć, jakie typy obsługiwać. Jeśli wszystkie wyjątki są gwarantowane jako pochodne 'std :: exception', może nie być bardzo trudne, ale jeśli dowolny typ jest dozwolony, to zmienia rzeczy. – xxbbcc

3

Niektóre środowiska obsługują to mniej więcej bezpośrednio.

Na przykład, jeśli włączysz structured exception handling i wyjątków C++ przez przełącznik /EH kompilatora, można mieć wyjątki C++ realizowane w ciągu uporządkowanego wyjątkiem Microsoft magazynowe („wyjątki” do C). Pod warunkiem, że te opcje są ustawione podczas kompilowania całego kodu (C++ na każdym końcu i C w środku) rozwijanie stosu będzie "działało".

Jednak prawie zawsze jest to zły pomysł (TM). Dlaczego, pytasz? Uważają, że kawałek kodu C w środku jest:

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

I że invoke_cxx_callback() (.... werble ...) wywołuje Twój kod C++, który zgłasza wyjątek. Wycieksz blokadę mutex. Oooo.

Widzisz, chodzi o to, że większość kodu C nie jest napisana do obsługi rozwijania stosów w stylu C++ w dowolnym momencie podczas wykonywania funkcji. Co więcej, nie ma destruktorów, więc nie ma potrzeby ochrony przed wyjątkami.

Kenny TM ma rozwiązanie dla projektów opartych na C++ 11 i Boost. xxbbcc ma bardziej ogólne, aczkolwiek bardziej nużące rozwiązanie dla ogólnego przypadku.

+0

To nie jest problem ze sposobem Kenny TM, ponieważ 'invoke_cxx_callback' zwróciłby normalnie (po zapisaniu wyjątku w' exception_ptr') i gdy kod C powróci do kodu C++, kod C++ sprawdzi, czy wyjątek został zgłoszony i jeśli to nastąpi, wróć do niego. –

+0

@SethCarnegie: Wiem, dlatego odniosłem się do obu odpowiedzi na "rozwiązania". Ta odpowiedź wyjaśnia tylko, dlaczego nie ma "przepuszczania" wyjątków przez kod C. –

+0

* "można wprowadzić wyjątki C++ za pośrednictwem strukturalnej obsługi wyjątków Microsoft" * - To nie jest poprawne. Kompilatory Microsoftu zawsze implementują wyjątki C++ pod względem wyjątków SEH. Nie ma wyboru. Przełącznik kompilatora kontroluje tylko semantykę słów kluczowych obsługi wyjątków zaimplementowanych przez MSC. – IInspectable

Powiązane problemy