Выбрасывание исключения в С++ в обратном вызове C, возможно пересечение границы динамической библиотеки... безопасно?
Я использую libjpeg, чтобы сохранить изображения JPEG. Если есть ошибка, поведение по умолчанию libjpeg - вызвать exit()
, чего я хочу избежать, поскольку это не фатальная ошибка для моей программы. libjpeg позволяет вам использовать собственный диспетчер ошибок и требует, чтобы при использовании вашей собственной функции error_exit()
(которая вызывает exit()
по умолчанию ) вы должны не возвращать управление вызывающему. libjpeg предлагает использовать setjmp.h для удовлетворения этого требования, а не exit()
программы.
Однако я пишу программу на С++, и у меня есть доступ к исключениям. Этот ответ на вопрос утверждает, что он безопасен (как в четко определенном поведении), чтобы вызывать исключение из обратного вызова. Но он не упоминает динамические библиотеки, и существует общее эмпирическое правило, что вы не бросаете исключения через границы динамической библиотеки.
Вот пример:
#include <iostream>
#include <jpeglib.h>
#include <cstdio>
#include <stdexcept>
static void handleLibJpegFatalError(j_common_ptr cinfo)
{
(*cinfo->err->output_message)(cinfo);
throw std::runtime_error("error in libjpeg, check stderr");
}
int main()
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE* file = std::fopen("out.jpeg", "wb"); // assume this doesn't fail for this example
try
{
cinfo.err = jpeg_std_error(&jerr);
jerr.error_exit = handleLibJpegFatalError;
// let say this triggers a fatal error in libjpeg and handleLibJpegFatalError() is called
// by libjpeg
jpeg_create_compress(&cinfo);
}
catch (...)
{
std::cerr << "Error saving the JPEG!\n";
}
jpeg_destroy_compress(&cinfo);
std::fclose(file);
}
Я хотел бы знать: могу ли я сделать исключение из этого обратного вызова и уловить его в своем приложении, даже если libjpeg скомпилирован как динамическая библиотека? libjpeg может быть статическим или динамической библиотеки, и если это динамическая библиотека, она может быть построена с другим компилятором. Однако код, который выбрасывает и ловит исключение, безусловно, будет в одном модуле компиляции. Безопасен ли этот код?
FYI, я разрабатываю для OS X и Windows (и сохраняю в виду будущее возможности Linux), поэтому меня больше интересует, известно ли, что это хорошо определенное поведение в целом, а не для конкретной платформы/компилятора.
Ответы
Ответ 1
Это не безопасно. В зависимости от того, как был скомпилирован соответствующий код библиотеки, отличной от С++, необходимые таблицы разматывания могут отсутствовать. Это просто практическая причина, почему она может потерпеть неудачу; концептуальная причина в том, что это просто поведение undefined.
Вы должны следовать документации и использовать setjmp
/longjmp
, чтобы получить доступ к вызову только с кодом libjpeg, а затем исключите исключение непосредственно в тело if (setjmp(...)) { ... }
, если вы хотите использовать исключения.
Ответ 2
Другой ответ применим здесь. При разматывании стека ничего не получится. Даже не имеет значения, использует ли библиотека какое-то сумасшедшее правило вызова, если это не связано с вашими структурами обработки исключений для реализации С++ (что обычно не является программой C). Никакая реализация С++, о которой я знаю, обнаруживает блок catch, выталкивая фреймы стека (что сделало бы оптимизацию кошмаром), все они поддерживают внутренние структуры для обработки исключений. Пока вызов вниз в цепочке вызовов не испортится с этими структурами, разворачивание стека будет отлично работать для всего вашего личного кода. Теперь, в общем, вполне возможно, что это оставит библиотеку с перепутанным внутренним состоянием, поскольку вы никогда не возвращаете выполнение в библиотеку для очистки, но в случае обратного вызова ошибки libjpeg ожидает, что поток управления не вернется и имеет по-видимому, уже очищены после себя.
В этом случае я бы пошел на это. В общем, я бы только выбросил фатальные исключения из обратного вызова C.
Надеюсь, что это помогло.