diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6604759..d5e4e04 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,7 @@ PRIVATE ${CMAKE_CURRENT_LIST_DIR}/IOHelper.hpp ${CMAKE_CURRENT_LIST_DIR}/ParserHelper.hpp ${CMAKE_CURRENT_LIST_DIR}/StringHelper.hpp + ${CMAKE_CURRENT_LIST_DIR}/WinFctHelper.hpp # Windows including guard pair ${CMAKE_CURRENT_LIST_DIR}/WinImportPrefix.hpp ${CMAKE_CURRENT_LIST_DIR}/WinImportSuffix.hpp @@ -27,6 +28,7 @@ PRIVATE ${CMAKE_CURRENT_LIST_DIR}/IOHelper.cpp ${CMAKE_CURRENT_LIST_DIR}/ParserHelper.cpp ${CMAKE_CURRENT_LIST_DIR}/StringHelper.cpp + ${CMAKE_CURRENT_LIST_DIR}/WinFctHelper.cpp ) # Setup header infomations target_include_directories(YYCCommonplace diff --git a/src/ExceptionHelper.cpp b/src/ExceptionHelper.cpp index e92ff7f..b137486 100644 --- a/src/ExceptionHelper.cpp +++ b/src/ExceptionHelper.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "WinFctHelper.hpp" #include "WinImportPrefix.hpp" #include @@ -12,35 +13,42 @@ #include "WinImportSuffix.hpp" namespace YYCC::ExceptionHelper { - + /** - * @brief true if the exception handler already registered. - * This variable is served for singleton. + * @brief True if the exception handler already registered, otherwise false. + * @details + * This variable is designed to prevent multiple register operation + * because unhandled exception handler should only be registered once. + * \n + * Register function should check whether this variable is false before registering, + * and set this variable to true after registing. + * Unregister as well as should do the same check. */ static bool g_IsRegistered = false; /** - * @brief true if a exception handler is running. + * @brief True if a exception handler is running, otherwise false. + * @details * This variable is served for blocking possible infinity recursive exception handling. + * \n + * When entering unhandled exception handler, we must check whether this variable is true. + * If it is true, it mean that there is another unhandled exception handler running. + * Then we should exit immediately. + * Otherwise, this variable should be set to true indicating we are processing unhandled exception. + * After processing exception, at the end of unhandled exception handler, + * we should restore this value to false. + * */ static bool g_IsProcessing = false; /** * @brief The backup of original exception handler. + * @details + * This variable was set when registering unhandled exception handler. + * And will be used when unregistering for restoring. */ - LPTOP_LEVEL_EXCEPTION_FILTER g_ProcBackup; + static LPTOP_LEVEL_EXCEPTION_FILTER g_ProcBackup; #pragma region Exception Handler Implementation - static HMODULE GetCurrentModule() { - // Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code - HMODULE hModule = NULL; - GetModuleHandleExW( - GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address and do not inc ref counter. - (LPCWSTR)GetCurrentModule, - &hModule); - - return hModule; - } - static const char* UExceptionGetCodeName(DWORD code) { switch (code) { case EXCEPTION_ACCESS_VIOLATION: @@ -241,7 +249,7 @@ namespace YYCC::ExceptionHelper { std::filesystem::path ironpad_path; WCHAR module_path[MAX_PATH]; std::memset(module_path, 0, sizeof(module_path)); - if (GetModuleFileNameW(GetCurrentModule(), module_path, MAX_PATH) == 0) { + if (GetModuleFileNameW(WinFctHelper::GetCurrentModule(), module_path, MAX_PATH) == 0) { goto failed; } ironpad_path = module_path; diff --git a/src/ExceptionHelper.hpp b/src/ExceptionHelper.hpp index 25b731a..29e8d9b 100644 --- a/src/ExceptionHelper.hpp +++ b/src/ExceptionHelper.hpp @@ -6,12 +6,28 @@ namespace YYCC::ExceptionHelper { /** * @brief Register unhandled exception handler - * @detail This function frequently called at the start of program. + * @details + * This function will set an internal function as unhandled exception handler on Windows. + * \n + * When unhandled exception raised, + * That internal function will output error stacktrace in standard output + * and log file (located in temp folder), and also generate a dump file + * in temp folder (for convenient debugging of developer when reporting bugs) if it can. + * \n + * This function usually is called at the start of program. + * @remarks This function is Windows only. */ void Register(); /** - * @brief Unregiister unhandled exception handler - * @detail This function frequently called at the end of program. + * @brief Unregister unhandled exception handler + * @details + * The reverse operation of Register(). + * \n + * This function and Register() should always be used as a pair. + * You must call this function if you have called Register() before. + * \n + * This function usually is called at the end of program. + * @remarks This function is Windows only. */ void Unregister(); diff --git a/src/WinFctHelper.cpp b/src/WinFctHelper.cpp new file mode 100644 index 0000000..08edfcc --- /dev/null +++ b/src/WinFctHelper.cpp @@ -0,0 +1,78 @@ +#include "WinFctHelper.hpp" +#if YYCC_OS == YYCC_OS_WINDOWS + +#include "EncodingHelper.hpp" + +namespace YYCC::WinFctHelper { + + HMODULE GetCurrentModule() { + // Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code + HMODULE hModule = NULL; + GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address and do not inc ref counter. + (LPCWSTR)GetCurrentModule, + &hModule); + + return hModule; + } + + std::string GetTempDirectory() { + // create wchar buffer for receiving the temp path. + std::wstring wpath(MAX_PATH + 1u, L'\0'); + DWORD expected_size; + + // fetch temp folder + while (true) { + if ((expected_size = GetTempPathW(static_cast(wpath.size()), wpath.data())) == 0) { + // failed, set to empty + expected_size = 0; + // and break while + break; + } + + if (expected_size > static_cast(wpath.size())) { + // buffer is too short, need enlarge and do fetching again + wpath.resize(expected_size); + } else { + // ok. shrink to real length, break while + break; + } + } + + // resize result + wpath.resize(expected_size); + // convert to utf8 and return + return YYCC::EncodingHelper::WcharToUTF8(wpath.c_str()); + } + + std::string GetModuleName(HINSTANCE hModule) { + // create wchar buffer for receiving the temp path. + std::wstring wpath(MAX_PATH + 1u, L'\0'); + DWORD copied_size; + + while (true) { + if ((copied_size = GetModuleFileNameW(hModule, wpath.data(), static_cast(wpath.size()))) == 0) { + // failed, return empty string + copied_size = 0; + break; + } + + // check insufficient buffer + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + // buffer is not enough, enlarge it and try again. + wpath.resize(wpath.size() + MAX_PATH); + } else { + // ok, break while + break; + } + } + + // resize result + wpath.resize(copied_size); + // convert to utf8 and return + return YYCC::EncodingHelper::WcharToUTF8(wpath.c_str()); + } + +} + +#endif diff --git a/src/WinFctHelper.hpp b/src/WinFctHelper.hpp new file mode 100644 index 0000000..7846534 --- /dev/null +++ b/src/WinFctHelper.hpp @@ -0,0 +1,43 @@ +#pragma once +#include "YYCCInternal.hpp" +#if YYCC_OS == YYCC_OS_WINDOWS + +#include + +#include "WinImportPrefix.hpp" +#include +#include "WinImportSuffix.hpp" + +namespace YYCC::WinFctHelper { + + /** + * @brief Get Windows used HANDLE for current module. + * @details + * If your target is EXE, the current module simply is your program self. + * However, if your target is DLL, the current module is your DLL, not the EXE loading your DLL. + * \n + * This function is frequently used by DLL. + * Because some design need the HANDLE of current module, not the host EXE loading your DLL. + * For example, you may want to get the name of your built DLL at runtime, then you should pass current module HANDLE, not the HANDLE of EXE. + * Or, if you want to get the path to your DLL, you also should pass current module HANDLE. + * @return A Windows HANDLE pointing to current module, NULL if failed. + */ + HMODULE GetCurrentModule(); + + /** + * @brief Get path to Windows temp folder. + * @return UTF8 encoded path to Windows temp folder. Empty string if failed. + */ + std::string GetTempDirectory(); + + /** + * @brief Get the file name of given module HANDLE + * @param hModule[in] + * The HANDLE to the module where we want get file name. + * It is same as the HANDLE parameter of GetModuleFileName. + * @return UTF8 encoded file name of given module. Empty string if failed. + */ + std::string GetModuleName(HINSTANCE hModule); +} + +#endif diff --git a/src/YYCCommonplace.hpp b/src/YYCCommonplace.hpp index 4b375f4..f66fcf7 100644 --- a/src/YYCCommonplace.hpp +++ b/src/YYCCommonplace.hpp @@ -7,3 +7,6 @@ #include "ConsoleHelper.hpp" #include "DialogHelper.hpp" #include "ParserHelper.hpp" +#include "ExceptionHelper.hpp" +#include "IOHelper.hpp" +#include "WinFctHelper.hpp" diff --git a/testbench/main.cpp b/testbench/main.cpp index c3add19..a5fd429 100644 --- a/testbench/main.cpp +++ b/testbench/main.cpp @@ -215,11 +215,18 @@ namespace YYCCTestbench { } } + static void WinFctTestbench() { + Console::WriteLine("Current Module HANDLE: 0x%016" PRIXPTR, YYCC::WinFctHelper::GetCurrentModule()); + Console::WriteLine("Temp Directory: %s", YYCC::WinFctHelper::GetTempDirectory().c_str()); + Console::WriteLine("Current Module Name: %s", YYCC::WinFctHelper::GetModuleName(YYCC::WinFctHelper::GetCurrentModule()).c_str()); + } + } int main(int argc, char** args) { - YYCCTestbench::ConsoleTestbench(); + //YYCCTestbench::ConsoleTestbench(); //YYCCTestbench::StringTestbench(); //YYCCTestbench::ParserTestbench(); //YYCCTestbench::DialogTestbench(); + YYCCTestbench::WinFctTestbench(); }