Ответ 1
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 он не может обрабатывать исключения С++? это правда? и что я должен делать в целом? есть ли правило для обработки исключений, если вы разрабатываете компонент, который будет использоваться другой системой.
C не имеет исключений, поэтому в общем случае вы должны поймать все исключения и вернуть код ошибки и/или предоставить функцию, которая возвращает информацию о последней ошибке.
Если это Windows с использованием MSVC, то да, вы можете перехватывать исключения в C, но вы не можете их хорошо поймать. Исключения в С++ передаются через механизм обработки транзакций OS ABI Structured Exception, а Microsoft имеет __ try, __except, __finally расширение C для обработки структурных исключений ОС. Обратите внимание, что они включают нарушения прав доступа, разделение на ноль и т.д., Которые вы обычно хотели бы завершить свою программу и зарегистрировать отчет об ошибке. Вы можете идентифицировать исключения С++ по коду 0xE04D5343 (4D 53 43 = "MSC" ) и отбросить остальные.
Что все сказано, вы, вероятно, не хотите бросать исключения через границу DLL, и, конечно же, нет, если вы только открываете API C.
Как правило, вы должны никогда разрешать исключения С++ распространяться за пределы модуля. Это связано с тем, что стандарт С++ не указывает, как должно быть реализовано распространение исключений, и, таким образом, это компилятор (и флаги компилятора) и зависит от операционной системы. Вы не можете гарантировать, что код, вызывающий ваш модуль, будет скомпилирован с тем же компилятором с теми же флагами компилятора, что и ваш модуль. Фактически, поскольку вы демонстрируете этим вопросом, вы не можете гарантировать, что код, вызывающий ваш модуль, будет написан на одном языке.
Более подробную информацию см. в статье 62 в Стандартах кодирования С++ Sutter и Alexandrescu.
Хорошо, так как его просили:
Код примера С++:
#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_*
, но для исключения стандартных исключений требуется обработчик завершения... см. дилемма? Жгут проводов слишком широк для практического использования: (
Только распространение исключений для вызывающего, если вызывающий объект предназначен для их обработки. Думаю, в вашем случае terminate()
будет вызываться немедленно, как только любое исключение выйдет из С++-кода, потому что из точки выполнения С++ это исключение не было обработано.
Такая же ситуация возникает в дизайне серверов COM - клиенты могут быть на любом языке/технологии. Правило заключается в том, что никакие исключения не должны выходить из методов COM-сервера - все исключения должны быть пойманы и переведены в HRESULT и (необязательно) IErrorInfo. Вы также должны делать это в своих ситуациях.
В случае, если C-код зажат между двумя слоями кода С++ распространяя исключения на C-код, все еще очень плохая идея.