Про двойной перехват исключений в DLL (оформление кода)
От: acDev Россия  
Дата: 16.11.20 13:49
Оценка:
Имеется чисто виндовая софтина, для которой можно писать плагины в виде 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 экспортных функций.

А вот так примерно было бы с учётом ловли всех исключений:
class my::exception : public std::exception { ... };

class my::DLL { ... };

my::DLL g_dll;

int _DllFunc1(int arg1, int arg2)
{
    try {
        return g_dll.func1(arg1, arg2);
    }
    catch (my::exception & ex) {
        g_dll.init_exception(ex);
        throw;
    }
    catch (std::exception & ex) {
        g_dll.init_exception(ex);
        throw;
    }
    return -1;
}

int WINAPI DllFunc1(int arg1, int arg2)
{
    #pragma comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
    __try {
        return _DllFunc1(arg1, arg2);
    }
    __except( g_dll.set_exception_info(GetExceptionInformation(), GetExceptionCode()) ) {
        g_dll.show_exception();
    }
    return -1;
}

В этом варианте на каждую экспортную функцию нужно создавать ещё одну дополнительную функцию, т.к. нельзя в одной функции обрабатывать два разных типа исключений.
И уж как то совсем напряжно становится от мысли о дублировании 20 раз этого кода.

Примечание: Внутри set_exception_info планируется заюзать StackWalker (поэтому то и юзается __except).

Но вот как то не очень прикольно сколько дублирующего кода писать.
Неужели только макросы помогут?

DLL-ку переписываю на MSVC 2015.
Отредактировано 17.11.2020 4:07 acDev . Предыдущая версия . Еще …
Отредактировано 16.11.2020 13:59 acDev . Предыдущая версия .
Отредактировано 16.11.2020 13:50 acDev . Предыдущая версия .
exception dll cpp seh
Re: Про двойной перехват исключений в DLL (как бы эстетичнее офо
От: ArtDenis Россия  
Дата: 16.11.20 17:44
Оценка:
Здравствуйте, acDev, Вы писали:

D>...

D>Но вот как то не очень прикольно сколько дублирующего кода писать.
D>Неужели только макросы помогут?

Я ничего не понял. К чем относится пример (к экспортируемой или импортируемой части)? Причём тут my::DLL?
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re: Про двойной перехват исключений в DLL (как бы эстетичнее офо
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 16.11.20 17:49
Оценка: 1 (1)
Здравствуйте, acDev, Вы писали:

D>Но вот как то не очень прикольно сколько дублирующего кода писать.

D>Неужели только макросы помогут?

Боюсь, что да. Вся автоматизация в C++ развивается исключительно вокруг класса/функции, да и то убого. Дополнительной автоматизации в плане оформления фрагментов кода не придумали с 70-х годов. Очевидно ж, что люди, голоса которых имеют вес в принятии стандартов, подобным либо никогда не занимались, либо занимались очень мало.

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

Можно #pragma заменить на __pragma (), тогда ее тоже можно завернуть в макрос.
Re: Про двойной перехват исключений в DLL (как бы эстетичнее офо
От: acDev Россия  
Дата: 16.11.20 19:47
Оценка:
Могут ли выручить std::tuple + std::function ?
Пример использования

Потери при вызовах будут значительные?
Аллокация в куче не юзается?
Re: Про двойной перехват исключений в DLL (как бы эстетичнее офо
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 16.11.20 20:03
Оценка: 1 (1)
Здравствуйте, acDev, Вы писали:

D>Имеется чисто виндовая софтина, для которой можно писать плагины в виде DLL (_WIN32 и _WIN64).

D>Решил я переписать один из плагинов на плюсах ... с блэкджеком и исключениями.

Посмотрите здесь
Автор: 0xDEADBEEF
Дата: 05.10.05
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re: Про двойной перехват исключений в DLL (как бы эстетичнее
От: ononim  
Дата: 16.11.20 23:00
Оценка:
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
Как много веселых ребят, и все делают велосипед...
Отредактировано 16.11.2020 23:28 ononim . Предыдущая версия .
Re[2]: Про двойной перехват исключений в DLL (как бы эстетичнее офо
От: acDev Россия  
Дата: 17.11.20 04:11
Оценка:
ЕМ>Можно #pragma заменить на __pragma (), тогда ее тоже можно завернуть в макрос.

Наверняка не получится, т.к. макрос __FUNCDNAME__ доступен только внутри функции, а при "раскрутке" макросов функция как таковая ещё и не оформлена.
Re[2]: Про двойной перехват исключений в DLL (как бы эстетичнее офо
От: acDev Россия  
Дата: 17.11.20 04:14
Оценка:
Здравствуйте, Коваленко Дмитрий, Вы писали:

КД>Посмотрите здесь
Автор: 0xDEADBEEF
Дата: 05.10.05


Странно, что вас "напугало" наличие всего двух блоков catch. Проблема то глубже. В одной функции нельзя юзать try и __try, а вывод StackWalker заюзать всё же хочется.
Re[2]: Про двойной перехват исключений в DLL (как бы эстетичнее
От: acDev Россия  
Дата: 17.11.20 04:24
Оценка:
Здравствуйте, 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 (как бы эстетичнее офо
От: acDev Россия  
Дата: 17.11.20 04:39
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Очевидно ж, что люди, голоса которых имеют вес в принятии стандартов, подобным либо никогда не занимались, либо занимались очень мало.


Есть такое. К примеру, очень нужный функционал std::scope_guard до сих пор валяется в черновиках. А функционал оного крайне полезен при портировании C-кода.
Поэтому в этом проекте планирую заюзать фэйсбучный folly::ScopeGuard.
Re: Про двойной перехват исключений в DLL (оформление кода)
От: PM  
Дата: 17.11.20 07:34
Оценка: 6 (1) +2
Здравствуйте, acDev, Вы писали:


...

D>В этом варианте на каждую экспортную функцию нужно создавать ещё одну дополнительную функцию, т.к. нельзя в одной функции обрабатывать два разных типа исключений.

D>И уж как то совсем напряжно становится от мысли о дублировании 20 раз этого кода.

D>Примечание: Внутри set_exception_info планируется заюзать StackWalker (поэтому то и юзается __except).


Это какая-то древня библиотека с codeproject для печати stacktrace? Если я ничего не путаю, в ней вроде были утечки памяти. Может стоит глянуть на что-то поновее, типа boost::backtrace, которую даже двигают в стандарт.

D>Но вот как то не очень прикольно сколько дублирующего кода писать.

D>Неужели только макросы помогут?

Если я правильно понял, нельзя выпускать исключения за пределы экспортируемых из DLL функций, и хочется как-то обрабатывать их?

Начиная C++11 есть лямбды, для передачи блока кода в другую функцию, так что можно написать функцию которая будет обрабатывать исключения:
template<typename F>
int catch_cpp_exceptions(F&& f)
{
    try
    {
        return f();
    }
    catch (...)
    {
        g_dll.init_exception(std::exception_ptr());
        throw;
    }
}

template<typename F>
int catch_seh_exceptions(F&& f)
{
    __try
    {
        catch_cpp_exceptions(std::forward<F>(f));
    }
    __catch(...)
    {
        g.show_exception();
    }
    return -1;
}

int WINAPI DllFunc1(int arg1, int arg2)
{
    #pragma comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
    return catch_seh_exceptions([&]{ g_dll.func1(arg1, arg2); });
}


D>DLL-ку переписываю на MSVC 2015.


Староват компилятор, но C+11 и частично 14 поддерживает. Можно попробовать версию поновее, Visual C++ 2019. Она совместима на уровне ABI с 2015-й, так что проблем с C++ runtime быть не должно.
Re[2]: Про двойной перехват исключений в DLL (как бы эстетичнее офо
От: acDev Россия  
Дата: 17.11.20 07:42
Оценка:
Здравствуйте, Коваленко Дмитрий, Вы писали:

КД>Посмотрите здесь
Автор: 0xDEADBEEF
Дата: 05.10.05


Вот кроме этого написали бы вы сразу о /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 (как бы эстетичнее офо
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 17.11.20 09:27
Оценка:
Здравствуйте, acDev, Вы писали:

ЕМ>>Можно #pragma заменить на __pragma (), тогда ее тоже можно завернуть в макрос.


D>Наверняка не получится


Я проверил перед тем, как предложить.

D>макрос __FUNCDNAME__ доступен только внутри функции, а при "раскрутке" макросов функция как таковая ещё и не оформлена.


Так это ж не настоящий сварщик макрос. На стадии препроцессирования эти имена не обрабатываются, поэтому макровызов, содержащий __pragma, тупо разворачивается в ту же самую строку. Если она оказалась внутри функции, то компилятор заменяет __FUNCDNAME__, __FUNCTION__ и прочие на соответствующие строки.
Re[2]: Про двойной перехват исключений в DLL (оформление код
От: acDev Россия  
Дата: 17.11.20 11:36
Оценка:
Здравствуйте, 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 user
        if (psize)
            *psize = -1LL;
        return -1;
    }
    return rc;
}

И теперь даже нет необходимости в /EHa. И с указанием /EHsc работает нормально.
Отредактировано 17.11.2020 11:43 acDev . Предыдущая версия . Еще …
Отредактировано 17.11.2020 11:40 acDev . Предыдущая версия .
Отредактировано 17.11.2020 11:37 acDev . Предыдущая версия .
Re[3]: Про двойной перехват исключений в DLL (оформление код
От: Gaia  
Дата: 19.11.20 13:53
Оценка: +1
Здравствуйте, acDev, Вы писали:

D>И теперь даже нет необходимости в /EHa. И с указанием /EHsc работает нормально.


Плохая идея.
Пример здесь.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.