From 650fcd12ec2dca9885f9aa5b79120f48048956bc Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Mon, 29 Jul 2024 21:42:27 +0800 Subject: [PATCH] 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. --- src/ExceptionHelper.cpp | 30 ++++++++++++++++++++++++++---- src/ExceptionHelper.hpp | 20 +++++++++++++++++++- testbench/main.cpp | 14 +++++++++++--- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/ExceptionHelper.cpp b/src/ExceptionHelper.cpp index 7424352..e3e303f 100644 --- a/src/ExceptionHelper.cpp +++ b/src/ExceptionHelper.cpp @@ -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 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 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 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() { diff --git a/src/ExceptionHelper.hpp b/src/ExceptionHelper.hpp index f175523..67d0790 100644 --- a/src/ExceptionHelper.hpp +++ b/src/ExceptionHelper.hpp @@ -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 diff --git a/testbench/main.cpp b/testbench/main.cpp index 9f65520..c98a703 100644 --- a/testbench/main.cpp +++ b/testbench/main.cpp @@ -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