Имеется чисто виндовая софтина, для которой можно писать плагины в виде DLL (_WIN32 и _WIN64).
Решил я переписать один из плагинов на плюсах ... с блэкджеком и исключениями. При этом эта софтина C++ исключений вообще не ожидает. Поэтому все исключения нужно перехватывать в каждой API функции и как то сообщать пользователю об ошибке (EventLog, MessageBox и т.п.).
Вот так код выглядел бы без учёта исключений:
class my::DLL { ... };
my::DLL g_dll;
int WINAPI DllFunc1(int arg1, int arg2)
{
#pragma comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
return g_dll.func1(arg1, arg2);
}
В этом варианте код простой и наглядный. Можно смело дублировать для 20 экспортных функций.
А вот так примерно было бы с учётом ловли всех исключений:
В этом варианте на каждую экспортную функцию нужно создавать ещё одну дополнительную функцию, т.к. нельзя в одной функции обрабатывать два разных типа исключений.
И уж как то совсем напряжно становится от мысли о дублировании 20 раз этого кода.
Примечание: Внутри set_exception_info планируется заюзать StackWalker (поэтому то и юзается __except).
Но вот как то не очень прикольно сколько дублирующего кода писать.
Неужели только макросы помогут?
Здравствуйте, acDev, Вы писали:
D>Но вот как то не очень прикольно сколько дублирующего кода писать. D>Неужели только макросы помогут?
Боюсь, что да. Вся автоматизация в C++ развивается исключительно вокруг класса/функции, да и то убого. Дополнительной автоматизации в плане оформления фрагментов кода не придумали с 70-х годов. Очевидно ж, что люди, голоса которых имеют вес в принятии стандартов, подобным либо никогда не занимались, либо занимались очень мало.
Даже если завернуть функции в шаблоны с числовыми параметрами, все равно придется явно специализировать с именами вызываемых функций.
Можно #pragma заменить на __pragma (), тогда ее тоже можно завернуть в макрос.
Re: Про двойной перехват исключений в DLL (как бы эстетичнее офо
Здравствуйте, acDev, Вы писали:
D>Имеется чисто виндовая софтина, для которой можно писать плагины в виде DLL (_WIN32 и _WIN64). D>Решил я переписать один из плагинов на плюсах ... с блэкджеком и исключениями.
D> } D> catch (my::exception & ex) { D> g_dll.init_exception(ex); D> throw; D> } D> catch (std::exception & ex) { D> g_dll.init_exception(ex); D> throw; D> }
1) зачем нужно g_dll.init_exception?
2) если оно прям таки нужно (нет), почему бы my::exception не отнаследовать от std::exception или runtime_error какого чтоб проще было? И ловить соответственно только std::exception, вместо SEH в том числе тут:
D> __except( g_dll.set_exception_info(GetExceptionInformation(), GetExceptionCode()) ) { D> g_dll.show_exception(); D> }
..а stackwalker заюзать в к-ре my::exception
Как много веселых ребят, и все делают велосипед...
Странно, что вас "напугало" наличие всего двух блоков catch. Проблема то глубже. В одной функции нельзя юзать try и __try, а вывод StackWalker заюзать всё же хочется.
Re[2]: Про двойной перехват исключений в DLL (как бы эстетичнее
Здравствуйте, ononim, Вы писали:
O>1) зачем нужно g_dll.init_exception?
Потому что до вызова show_exception нужно собрать инфу о C++ исключении и о SEH исключении.
O>2) если оно прям таки нужно (нет), почему бы my::exception не отнаследовать от std::exception
Так вроде в примерах выше я это и указал наглядно.
O>... И ловить соответственно только std::exception, вместо SEH ...
Ловля SEH нужна для функционала StackWalker. Хотя можно его заюзать и в блоке catch (помниться я давно это делал).
O>..а stackwalker заюзать в к-ре my::exception
Согласен. Вроде так можно, но точно StackWalker больше инфы соберёт именно внутри __except.
А catch(...) точно все возможные исключения отлавливает? Ничего не пропускает?
Re[2]: Про двойной перехват исключений в DLL (как бы эстетичнее офо
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Очевидно ж, что люди, голоса которых имеют вес в принятии стандартов, подобным либо никогда не занимались, либо занимались очень мало.
Есть такое. К примеру, очень нужный функционал std::scope_guard до сих пор валяется в черновиках. А функционал оного крайне полезен при портировании C-кода.
Поэтому в этом проекте планирую заюзать фэйсбучный folly::ScopeGuard.
Re: Про двойной перехват исключений в DLL (оформление кода)
...
D>В этом варианте на каждую экспортную функцию нужно создавать ещё одну дополнительную функцию, т.к. нельзя в одной функции обрабатывать два разных типа исключений. D>И уж как то совсем напряжно становится от мысли о дублировании 20 раз этого кода.
D>Примечание: Внутри set_exception_info планируется заюзать StackWalker (поэтому то и юзается __except).
Это какая-то древня библиотека с codeproject для печати stacktrace? Если я ничего не путаю, в ней вроде были утечки памяти. Может стоит глянуть на что-то поновее, типа boost::backtrace, которую даже двигают в стандарт.
D>Но вот как то не очень прикольно сколько дублирующего кода писать. D>Неужели только макросы помогут?
Если я правильно понял, нельзя выпускать исключения за пределы экспортируемых из DLL функций, и хочется как-то обрабатывать их?
Начиная C++11 есть лямбды, для передачи блока кода в другую функцию, так что можно написать функцию которая будет обрабатывать исключения:
Староват компилятор, но C+11 и частично 14 поддерживает. Можно попробовать версию поновее, Visual C++ 2019. Она совместима на уровне ABI с 2015-й, так что проблем с C++ runtime быть не должно.
Re[2]: Про двойной перехват исключений в DLL (как бы эстетичнее офо
Вот кроме этого написали бы вы сразу о /EHa и _set_se_translator, то сразу стало бы понятнее.
А так самому пришлось почитать про "завёртывание" SE-исключений с плюсовые.
Но всё равно спасибо.
Пришёл пока к такому варианту реализации:
Кодес
class my::exception : public std::exception { ... };
class my::DLL { ... };
void DLL::exception_handler()
{
try {
throw;
}
catch (my::exception & ex) {
// делаем одно
}
catch (std::exception & ex) {
// делаем второе
}
catch (...) {
// делаем еще что-то
}
// get info from StackWalker
OutputDebugStringA("show exception info");
}
my::DLL g_dll;
class SE_Translator
{
private:
const _se_translator_function old_SE_translator;
public:
SE_Translator( _se_translator_function new_SE_translator ) noexcept
: old_SE_translator{ _set_se_translator( new_SE_translator ) } { }
~SE_Translator() noexcept { _set_se_translator( old_SE_translator ); }
};
void se_trans_func(UINT code, PEXCEPTION_POINTERS info)
{
throw my::exception(code, info);
}
void WINAPI DllFunc1(int arg1, int arg2)
{
#pragma comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
SE_Translator se_translator { se_trans_func };
try {
g_dll.func1(int arg1, int arg2);
}
catch(...) {
g_dll.exception_handler();
}
}
Re[3]: Про двойной перехват исключений в DLL (как бы эстетичнее офо
Здравствуйте, acDev, Вы писали:
ЕМ>>Можно #pragma заменить на __pragma (), тогда ее тоже можно завернуть в макрос.
D>Наверняка не получится
Я проверил перед тем, как предложить.
D>макрос __FUNCDNAME__ доступен только внутри функции, а при "раскрутке" макросов функция как таковая ещё и не оформлена.
Так это ж не настоящий сварщик макрос. На стадии препроцессирования эти имена не обрабатываются, поэтому макровызов, содержащий __pragma, тупо разворачивается в ту же самую строку. Если она оказалась внутри функции, то компилятор заменяет __FUNCDNAME__, __FUNCTION__ и прочие на соответствующие строки.
Re[2]: Про двойной перехват исключений в DLL (оформление код
Здравствуйте, PM, Вы писали: PM>Это какая-то древня библиотека с codeproject для печати stacktrace?
Она самая. PM>Если я ничего не путаю, в ней вроде были утечки памяти.
Погуглил. Вроде было такое. Но очень давно. PM>Может стоит глянуть на что-то поновее, типа boost::backtrace, которую даже двигают в стандарт.
Из пушки по маленькому воробушку не хочется совсем. Да и мне кажется, что в "мире винды" больше всего юзают именно StackWalker. PM>Если я правильно понял, нельзя выпускать исключения за пределы экспортируемых из DLL функций, и хочется как-то обрабатывать их?
Верно PM>Начиная C++11 есть лямбды, для передачи блока кода в другую функцию, так что можно написать функцию которая будет обрабатывать исключения:
Отличная идея!
Как раз лямбды начинаю осваивать. С ними код лаконичным стал.
Вот что у меня получилось
class my::exception : public std::exception { ... };
class my::DLL { ... };
class my::ExceptInfo { ... };
my::DLL g_dll;
template<typename R, typename F>
auto catch_cpp_exceptions(my::ExceptInfo & einf, R eval, F && func)
{
try {
return func();
}
catch (my::exception & ex) {
einf.init(ex);
throw;
}
catch (std::exception & ex) {
einf.init(ex);
throw;
}
return eval;
}
template<typename R, typename F>
auto catch_exceptions(my::ExceptInfo & einf, R eval, F && func)
{
__try {
return catch_cpp_exceptions(einf, eval, std::forward<F>(func));
}
__except ( einf.init(GetExceptionInformation(), GetExceptionCode()) ) {
// nothing
}
return eval;
}
int WINAPI DllFunc1(int arg1, int arg2)
{
#pragma comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
my::ExceptInfo einf(g_dll);
return catch_exceptions(einf, -1, [&]{ return g_dll.func1(arg1, arg2); });
// ~einf() -> einf.show()
}
int WINAPI DllFunc2(int arg1, const char * filename, INT64 * psize)
{
#pragma comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
my::ExceptInfo einf(g_dll);
int rc = catch_exceptions(einf, -1, [&]{ return g_dll.func2(arg1, filename, psize); });
if (einf.is_active()) {
einf.show(L"filename = '%S'", filename); // Aux info for userif (psize)
*psize = -1LL;
return -1;
}
return rc;
}
И теперь даже нет необходимости в /EHa. И с указанием /EHsc работает нормально.