Может ли программа C обрабатывать исключения С++?

Я разрабатываю dll dll С++, который может использоваться приложениями C или С++. Открытые функции dll следующие.

#include <tchar.h>
#ifdef IMPORT
#define DLL __declspec(dllimport)
#else 
#define DLL __declspec(dllexport)
#endif

extern "C" {

    DLL  bool __cdecl Init();
    DLL  bool __cdecl Foo(const TCHAR*);
    DLL  bool __cdecl Release();

}

внутренняя реализация этих функций - это классы С++, которые не отображаются, я предполагаю, что используя этот стиль, dll можно использовать либо в приложениях C, либо на С++. Проблема в том, что я не обрабатываю исключение С++ (т.е. Bad_alloc), и я оставил этот материал вызывающему (более высокий уровень). После долгих споров с моими коллегами я должен уловить все исключения и вернуть код ошибки или хотя бы false, потому что в случае приложения C он не может обрабатывать исключения С++? это правда? и что я должен делать в целом? есть ли правило для обработки исключений, если вы разрабатываете компонент, который будет использоваться другой системой.

Ответы

Ответ 1

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

Ответ 2

Если это Windows с использованием MSVC, то да, вы можете перехватывать исключения в C, но вы не можете их хорошо поймать. Исключения в С++ передаются через механизм обработки транзакций OS ABI Structured Exception, а Microsoft имеет __ try, __except, __finally расширение C для обработки структурных исключений ОС. Обратите внимание, что они включают нарушения прав доступа, разделение на ноль и т.д., Которые вы обычно хотели бы завершить свою программу и зарегистрировать отчет об ошибке. Вы можете идентифицировать исключения С++ по коду 0xE04D5343 (4D 53 43 = "MSC" ) и отбросить остальные.

Что все сказано, вы, вероятно, не хотите бросать исключения через границу DLL, и, конечно же, нет, если вы только открываете API C.

Ответ 3

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

Более подробную информацию см. в статье 62 в Стандартах кодирования С++ Sutter и Alexandrescu.

Ответ 4

Хорошо, так как его просили:

Код примера С++:

#include <typeinfo>
#include <exception>
extern "C" {
void sethandler(void (*func)(void)) { std::set_terminate(func); }
int throwingFunc(int arg) {
    if (arg == 0)
        throw std::bad_cast();
    return (arg - 1);
}
}

Пример кода:

#include <stdio.h>

extern int throwingFunc(int arg);
extern void sethandler(void (*func)(void));

void myhandler(void)
{
    printf("handler called - must've been some exception ?!\n");
}

int main(int argc, char **argv)
{
    sethandler(myhandler);

    printf("throwingFunc(1) == %d\n", throwingFunc(1));
    printf("throwingFunc(-1) == %d\n", throwingFunc(-1));
    printf("throwingFunc(0) == %d\n", throwingFunc(0));
    return 0;
}

Когда я скомпилирую эти два, соедините их и запустите (Ubuntu 10.04, gcc 4.4.5, x64), я получаю:

$ ./xx
throwingFunc(1) == 0
throwingFunc(-1) == -2
handler called - must've been some exception ?!
Aborted

Таким образом, хотя вы можете перехватывать исключения из C, это вряд ли достаточно, потому что поведение С++ во время выполнения после std::terminate() равно undefined, а также потому, что обработчик не получает никакой информации о статусе (чтобы отличить типы исключений и/или источники). Обработчик ничего не может очистить.

Кстати, я специально выбрал std::bad_cast() как тип исключения в этом примере. Это связано с тем, что бросание, которое иллюстрирует разницу в поведении между std::set_unexpected() и std::set_terminate(), будет вызвано для всех исключений non std::bad_*, но для исключения стандартных исключений требуется обработчик завершения... см. дилемма? Жгут проводов слишком широк для практического использования: (

Ответ 5

Только распространение исключений для вызывающего, если вызывающий объект предназначен для их обработки. Думаю, в вашем случае terminate() будет вызываться немедленно, как только любое исключение выйдет из С++-кода, потому что из точки выполнения С++ это исключение не было обработано.

Такая же ситуация возникает в дизайне серверов COM - клиенты могут быть на любом языке/технологии. Правило заключается в том, что никакие исключения не должны выходить из методов COM-сервера - все исключения должны быть пойманы и переведены в HRESULT и (необязательно) IErrorInfo. Вы также должны делать это в своих ситуациях.

В случае, если C-код зажат между двумя слоями кода С++ распространяя исключения на C-код, все еще очень плохая идея.