feat: add callback for unhandled exception handler.

- add callback for unhandled exception handler to give programmer a chance to fetch log and coredump path, especially for GUI application because its stderr is invisible.
- fix fatal anto-recursive calling bug in unhandled exception handler.
This commit is contained in:
yyc12345 2024-07-29 21:42:27 +08:00
parent e8a0299fbc
commit 650fcd12ec
3 changed files with 56 additions and 8 deletions

View File

@ -26,6 +26,7 @@ namespace YYCC::ExceptionHelper {
ExceptionRegister() :
m_CoreMutex(),
m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr),
m_UserCallback(nullptr),
m_SingletonMutex(NULL) {}
~ExceptionRegister() {
Unregister();
@ -35,7 +36,7 @@ namespace YYCC::ExceptionHelper {
/**
* @brief Try to register unhandled exception handler.
*/
void Register() {
void Register(ExceptionCallback callback) {
std::lock_guard<std::mutex> locker(m_CoreMutex);
// if we have registered, return
if (m_IsRegistered) return;
@ -63,6 +64,8 @@ namespace YYCC::ExceptionHelper {
// okey, we can register it.
// backup old handler
m_PrevProcHandler = SetUnhandledExceptionFilter(UExceptionImpl);
// set user callback
m_UserCallback = callback;
// mark registered
m_IsRegistered = true;
}
@ -115,6 +118,14 @@ namespace YYCC::ExceptionHelper {
std::lock_guard<std::mutex> locker(m_CoreMutex);
return m_PrevProcHandler;
}
/**
* @brief Get user specified callback.
* @return The function pointer to user callback. nullptr if no associated callback.
*/
ExceptionCallback GetUserCallback() const {
std::lock_guard<std::mutex> locker(m_CoreMutex);
return m_UserCallback;
}
/**
* @brief Try to start process unhandled exception.
@ -154,6 +165,12 @@ namespace YYCC::ExceptionHelper {
* True if it is, otherwise false.
*/
bool m_IsProcessing;
/**
* @brief User defined callback.
* @details It will be called at the tail of unhandled exception handler, because it may raise exception.
* We must make sure all log and coredump have been done before calling it.
*/
ExceptionCallback m_UserCallback;
/**
* @brief The backup of old unhandled exception handler.
*/
@ -166,6 +183,7 @@ namespace YYCC::ExceptionHelper {
HANDLE m_SingletonMutex;
};
/// @brief Core register singleton.
static ExceptionRegister g_ExceptionRegister;
#pragma region Exception Handler Implementation
@ -505,10 +523,14 @@ namespace YYCC::ExceptionHelper {
// write crash coredump
UExceptionCoreDump(coredump_path, info);
// call user callback
ExceptionCallback user_callback = g_ExceptionRegister.GetUserCallback();
if (user_callback != nullptr)
user_callback(log_path, coredump_path);
}
// stop process
g_ExceptionRegister.StartProcessing();
g_ExceptionRegister.StopProcessing();
end_proc:
// if backup proc can be run, run it
@ -523,8 +545,8 @@ namespace YYCC::ExceptionHelper {
#pragma endregion
void Register() {
g_ExceptionRegister.Register();
void Register(ExceptionCallback callback) {
g_ExceptionRegister.Register(callback);
}
void Unregister() {

View File

@ -11,6 +11,24 @@
*/
namespace YYCC::ExceptionHelper {
/**
* @brief The callback function prototype which will be called when unhandled exception happened after registering.
* @details
* During registering unhandled exception handler,
* caller can optionally provide a function pointer matching this prorotype to register.
* Then it will be called if unhandled exception hanppened.
*
* This callback will provide 2 readonly arguments.
* First is the path to error log file.
* Second is the path to core dump file.
* These pathes may be empty if internal handler fail to create them.
*
* This callback is convenient for programmer using an explicit way to tell user an exception happened.
* Because in default, handler will only write error log to \c stderr and file.
* It will be totally invisible on a GUI application.
*/
using ExceptionCallback = void(*)(const yycc_u8string& log_path, const yycc_u8string& coredump_path);
/**
* @brief Register unhandled exception handler
* @details
@ -23,7 +41,7 @@ namespace YYCC::ExceptionHelper {
*
* This function usually is called at the start of program.
*/
void Register();
void Register(ExceptionCallback callback = nullptr);
/**
* @brief Unregister unhandled exception handler
* @details

View File

@ -338,7 +338,15 @@ namespace YYCCTestbench {
static void ExceptionTestbench() {
#if YYCC_OS == YYCC_OS_WINDOWS
YYCC::ExceptionHelper::Register();
YYCC::ExceptionHelper::Register([](const YYCC::yycc_u8string& log_path, const YYCC::yycc_u8string& coredump_path) -> void {
MessageBoxW(
NULL,
YYCC::EncodingHelper::UTF8ToWchar(
YYCC::StringHelper::Printf(YYCC_U8("Log generated:\nLog path: %s\nCore dump path: %s"), log_path.c_str(), coredump_path.c_str())
).c_str(),
L"Fatal Error", MB_OK + MB_ICONERROR
);
});
// Perform a div zero exception.
int i = 1, j = 0;
@ -376,7 +384,7 @@ namespace YYCCTestbench {
std::filesystem::path test_path;
for (const auto& strl : c_UTF8TestStrTable) {
test_path /= YYCC::FsPathPatch::FromUTF8Path(strl.c_str());
}
}
YYCC::yycc_u8string test_slashed_path(YYCC::FsPathPatch::ToUTF8Path(test_path));
#if YYCC_OS == YYCC_OS_WINDOWS
@ -389,7 +397,7 @@ namespace YYCCTestbench {
Assert(test_slashed_path == test_joined_path, YYCC_U8("YYCC::FsPathPatch"));
}
}
enum class TestEnum : int8_t {
Test1, Test2, Test3