seh_exceptions.h
Code:
#pragma once
#include <system_error>
#include <utility>
#include <Windows.h>
#include <excpt.h>
#include <eh.h>
class seh_exception : public std::system_error
{
EXCEPTION_POINTERS *_ep;
static __declspec(thread) _se_translator_function _old_translator;
static void translator(unsigned int code, EXCEPTION_POINTERS *ep);
seh_exception(unsigned int code, EXCEPTION_POINTERS *ep);
public:
static void enable_for_this_thread();
static void disable_for_this_thread();
static bool is_enabled_for_this_thread();
static void ignore(const seh_exception &e);
template <typename TryBlock, typename CatchBlock = decltype(&ignore)>
static bool handle(TryBlock &&try_block, CatchBlock &&catch_block = &ignore);
virtual ~seh_exception();
virtual const char *what() const override;
virtual EXCEPTION_POINTERS *exception_pointers() const;
};
template <typename TryBlock, typename CatchBlock>
bool seh_exception::handle(TryBlock &&try_block, CatchBlock &&catch_block)
{
bool was_enabled = seh_exception::is_enabled_for_this_thread();
try
{
seh_exception::enable_for_this_thread();
std::forward<TryBlock>(try_block)();
return true;
}
catch(const seh_exception &e)
{
std::forward<CatchBlock>(catch_block)(e);
if (!was_enabled)
seh_exception::disable_for_this_thread();
return false;
}
catch(...)
{
if (!was_enabled)
seh_exception::disable_for_this_thread();
throw;
}
}
seh_exception.cpp
Code:
#include "seh_exception"
__declspec(thread) _se_translator_function seh_exception::_old_translator = &seh_exception::translator;
void seh_exception::translator(unsigned int code, EXCEPTION_POINTERS *ep)
{
throw seh_exception(code, ep);
}
seh_exception::seh_exception(unsigned int code, EXCEPTION_POINTERS *ep)
: _ep(ep), std::system_error(code, std::generic_category())
{
}
void seh_exception::enable_for_this_thread()
{
if (!is_enabled_for_this_thread())
_old_translator = _set_se_translator(&translator);
}
void seh_exception::disable_for_this_thread()
{
if (is_enabled_for_this_thread())
_old_translator = _set_se_translator(_old_translator);
}
bool seh_exception::is_enabled_for_this_thread()
{
return _old_translator != &translator;
}
void seh_exception::ignore(const seh_exception &)
{
}
seh_exception::~seh_exception()
{
}
const char *seh_exception::what() const
{
return "seh_exception";
}
EXCEPTION_POINTERS *seh_exception::exception_pointers() const
{
return _ep;
}
main.cpp (Beispielhafte Anwendung)
Code:
#include <iostream>
#include "seh_exception.h"
int main(int argc, char *argv[])
{
seh_exception::handle([]
{
int *p = nullptr;
*p = 1; // raise EXCEPTION_ACCESS_VIOLATION
}, [](const seh_exception &e)
{
std::cerr << e.code().value() << std::endl;
});
// keep cmd.exe alive
std::cin.get();
return 0;
}
Im Beispiel wird eine SEH-Exceptions aufgefangen, die geworfen wurde, als
nullptr dereferenziert wurde. Der ExceptionCode dieser Exception ist
EXCEPTION_ACCESS_VIOLATION und die für uns im folgenden interessanteste.
Was muss ich bei der Benutzung von seh_exception beachten?
Um das benutzen zu können, muss man in Visual Studio die Einstellung
/EHa setzen:
Projekt > Eigenschaften > C/C++ > Codegenerierung > C++ Ausnahmen aktivieren > Ja bei SEH-Ausnahmen (/EHa)
SEH-Exceptions werden für jeden Thread, der
seh_exception::enable_for_this_thread() aufgerufen hat, aktiviert.
Das deaktiveren ist für den jeweiligen Thread mit
seh_exception::disable_for_this_thread() möglich, aber optional.
seh_exception::handle übernimmt das jeweils für euch.
Die Funktion
_set_se_translator sollte nicht innerhalb von
seh_exception::handle verwendet werden.
Ist das zweite Parameter von
seh_exception::handle nicht spezifiziert, ist es
&seh_exception::ignore; diese Funktion macht nichts.
Wichtig ist außerdem, dass
DisableThreadLibraryCalls nicht für ein Modul benutzt werden darf, das
seh_exception benutzt. Grund dafür ist, dass
seh_exception TLS (Thread-Local Storage) benutzt.
Warum
seh_exception die Stabilität eurer Cheats erhöhen kann? Ein weiteres Beispiel (Pseudo-Code):
pseudo-code.cpp
Code:
// solange der cheat aktiv ist
while(cheat_is_enabled())
{
// warte, bis der spieler ingame ist
wait_until_player_is_ingame();
// führe die player cheats aus
player_cheats();
}
Aber was ist, wenn der Spieler das Spiel verlässt, während player_cheats() ausgeführt wird?
Es crasht! Der Playerpointer ist dann nämlich
nullptr, bei dessen Zugriff eine
EXCEPTION_ACCESS_VIOLATION auftritt.
Um das zu verhindern wollen wir also bei Auftritt einer solchen Exception wieder zu
wait_until_player_is_ingame() springen.
Wir wollen also
seh_exception::handle so benutzen:
Code:
// solange der cheat aktiv ist
while(cheat_is_enabled())
{
// warte, bis der spieler ingame ist
wait_until_player_is_ingame();
// jetzt kommt der spannende trick
seh_exception::handle([]
{
// rufe player_cheats auf und breche bei einer seh_exception ab
player_cheats();
}, [](const seh_exception &err)
{
// das wird aufgerufen, wenn eine seh_exception stattgefunden hat
write_to_log(err.what());
});
}