feat: add windows function helper

- add windows function helper namespace for some commonly used windows functions.
- add corresponding testbench for added code.
This commit is contained in:
yyc12345 2024-06-13 11:18:25 +08:00
parent ab12268395
commit 015ff874f8
7 changed files with 178 additions and 21 deletions

View File

@ -12,6 +12,7 @@ PRIVATE
${CMAKE_CURRENT_LIST_DIR}/IOHelper.hpp ${CMAKE_CURRENT_LIST_DIR}/IOHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/ParserHelper.hpp ${CMAKE_CURRENT_LIST_DIR}/ParserHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/StringHelper.hpp ${CMAKE_CURRENT_LIST_DIR}/StringHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/WinFctHelper.hpp
# Windows including guard pair # Windows including guard pair
${CMAKE_CURRENT_LIST_DIR}/WinImportPrefix.hpp ${CMAKE_CURRENT_LIST_DIR}/WinImportPrefix.hpp
${CMAKE_CURRENT_LIST_DIR}/WinImportSuffix.hpp ${CMAKE_CURRENT_LIST_DIR}/WinImportSuffix.hpp
@ -27,6 +28,7 @@ PRIVATE
${CMAKE_CURRENT_LIST_DIR}/IOHelper.cpp ${CMAKE_CURRENT_LIST_DIR}/IOHelper.cpp
${CMAKE_CURRENT_LIST_DIR}/ParserHelper.cpp ${CMAKE_CURRENT_LIST_DIR}/ParserHelper.cpp
${CMAKE_CURRENT_LIST_DIR}/StringHelper.cpp ${CMAKE_CURRENT_LIST_DIR}/StringHelper.cpp
${CMAKE_CURRENT_LIST_DIR}/WinFctHelper.cpp
) )
# Setup header infomations # Setup header infomations
target_include_directories(YYCCommonplace target_include_directories(YYCCommonplace

View File

@ -5,6 +5,7 @@
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
#include <cinttypes> #include <cinttypes>
#include "WinFctHelper.hpp"
#include "WinImportPrefix.hpp" #include "WinImportPrefix.hpp"
#include <Windows.h> #include <Windows.h>
@ -12,35 +13,42 @@
#include "WinImportSuffix.hpp" #include "WinImportSuffix.hpp"
namespace YYCC::ExceptionHelper { namespace YYCC::ExceptionHelper {
/** /**
* @brief true if the exception handler already registered. * @brief True if the exception handler already registered, otherwise false.
* This variable is served for singleton. * @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; 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. * 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; static bool g_IsProcessing = false;
/** /**
* @brief The backup of original exception handler. * @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 #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) { static const char* UExceptionGetCodeName(DWORD code) {
switch (code) { switch (code) {
case EXCEPTION_ACCESS_VIOLATION: case EXCEPTION_ACCESS_VIOLATION:
@ -241,7 +249,7 @@ namespace YYCC::ExceptionHelper {
std::filesystem::path ironpad_path; std::filesystem::path ironpad_path;
WCHAR module_path[MAX_PATH]; WCHAR module_path[MAX_PATH];
std::memset(module_path, 0, sizeof(module_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; goto failed;
} }
ironpad_path = module_path; ironpad_path = module_path;

View File

@ -6,12 +6,28 @@ namespace YYCC::ExceptionHelper {
/** /**
* @brief Register unhandled exception handler * @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(); void Register();
/** /**
* @brief Unregiister unhandled exception handler * @brief Unregister unhandled exception handler
* @detail This function frequently called at the end of program. * @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(); void Unregister();

78
src/WinFctHelper.cpp Normal file
View File

@ -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<DWORD>(wpath.size()), wpath.data())) == 0) {
// failed, set to empty
expected_size = 0;
// and break while
break;
}
if (expected_size > static_cast<DWORD>(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<DWORD>(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

43
src/WinFctHelper.hpp Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#include <string>
#include "WinImportPrefix.hpp"
#include <Windows.h>
#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

View File

@ -7,3 +7,6 @@
#include "ConsoleHelper.hpp" #include "ConsoleHelper.hpp"
#include "DialogHelper.hpp" #include "DialogHelper.hpp"
#include "ParserHelper.hpp" #include "ParserHelper.hpp"
#include "ExceptionHelper.hpp"
#include "IOHelper.hpp"
#include "WinFctHelper.hpp"

View File

@ -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) { int main(int argc, char** args) {
YYCCTestbench::ConsoleTestbench(); //YYCCTestbench::ConsoleTestbench();
//YYCCTestbench::StringTestbench(); //YYCCTestbench::StringTestbench();
//YYCCTestbench::ParserTestbench(); //YYCCTestbench::ParserTestbench();
//YYCCTestbench::DialogTestbench(); //YYCCTestbench::DialogTestbench();
YYCCTestbench::WinFctTestbench();
} }