Compare commits

...

10 Commits

Author SHA1 Message Date
f153f9bc22 doc: update comments of code.
- add lost testbench for wchar encoding convertion.
- update code documentation.
2024-06-18 16:03:41 +08:00
77b6f439f7 feat: add encoding convertion functions between utf8 and utf16, utf32.
- add bidirectional convertion between utf8 and utf16, utf32 in every platforms.
- add testbench for new added functions.
2024-06-18 11:03:48 +08:00
0319be7e19 feat: finish exception helper.
- finish exception helper.
	- switch to LOCALAPPDATA/CrashDumps as output directory.
- add testbench for exception helper.
- add lost testbench for GetLocalAppData in WinFctHelper.
- link with DbgHelp.lib on Windows in CMake.
2024-06-17 15:10:45 +08:00
e20c03a5f1 refactor: move COM type and guard into independent file
- move COM types and guard into independent file and namespace COMHelper because not only dialog, but also other parts also need to use COM related fucntion.
- remove ParserHelper.cpp because it is empty (ParserHelper is header only namespace).
- Add a function fetching LOCALAPPDATA in WinFctHelper.
2024-06-17 12:46:32 +08:00
8465d80a54 chore: update CMakeLists
- update the use of target_sources() to remove duplicated header file declarations.
	- thx Gary Wang
2024-06-15 22:03:17 +08:00
5481898ad9 feat: move std::filesystem::path related function to independent namespace.
- create FsPathPatch namespace to hold std::filesystem::path related functions.
- add corresponding testbench code for it.
2024-06-15 17:57:33 +08:00
e7b13768ec fix: rename function. update console function
- Update implementation of EnsureColorfulConsole.
	- previous implementation will exit if one of the process to stdout and stderr failed. so if it exit at the process of stdout, the process of stderr will not be run.
	- current implementation will always do process for both of stdout and stderr. the return value will be false if any process of them are failed.
- rename GetModuleName to GetModuleFileName and disable annoy Windows macro GetModuleFileName in WinImportSuffix.hpp.
2024-06-15 16:59:54 +08:00
d0a8733379 refactor: change 2 functions declarations in WinFctHelper
- update GetTempPath and GetModuleName function to let them more reliable.
2024-06-15 16:44:11 +08:00
c32806ea03 feat: add more output functions in console helper.
- add more output functions in console helper.
	* split output functions by format string and plain string as Format and Write.
	* add functions which output to stderr.
- improve document.
2024-06-14 11:59:08 +08:00
0ef1939e6e feat: update IO helper.
- add utf8 path to std::filesystem::path convertion function.
- add left-padding zero format string for printing pointer.
- update exception handler, but not finished because console helper need update.
2024-06-13 15:24:12 +08:00
23 changed files with 1077 additions and 363 deletions

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.12) cmake_minimum_required(VERSION 3.23)
project(YYCC project(YYCC
VERSION 1.0.0 VERSION 1.0.0
LANGUAGES CXX LANGUAGES CXX

View File

@ -3,32 +3,39 @@ add_library(YYCCommonplace STATIC "")
# Setup static library sources # Setup static library sources
target_sources(YYCCommonplace target_sources(YYCCommonplace
PRIVATE PRIVATE
# Sources
COMHelper.cpp
ConsoleHelper.cpp
DialogHelper.cpp
EncodingHelper.cpp
ExceptionHelper.cpp
FsPathPatch.cpp
IOHelper.cpp
StringHelper.cpp
WinFctHelper.cpp
)
target_sources(YYCCommonplace
PUBLIC
FILE_SET HEADERS
FILES
# Headers # Headers
# Common headers # Common headers
${CMAKE_CURRENT_LIST_DIR}/ConsoleHelper.hpp COMHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/DialogHelper.hpp ConsoleHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/EncodingHelper.hpp DialogHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/ExceptionHelper.hpp EncodingHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/IOHelper.hpp ExceptionHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/ParserHelper.hpp FsPathPatch.hpp
${CMAKE_CURRENT_LIST_DIR}/StringHelper.hpp IOHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/WinFctHelper.hpp ParserHelper.hpp
StringHelper.hpp
WinFctHelper.hpp
# Windows including guard pair # Windows including guard pair
${CMAKE_CURRENT_LIST_DIR}/WinImportPrefix.hpp WinImportPrefix.hpp
${CMAKE_CURRENT_LIST_DIR}/WinImportSuffix.hpp WinImportSuffix.hpp
# Misc # Misc
${CMAKE_CURRENT_LIST_DIR}/YYCCInternal.hpp YYCCInternal.hpp
${CMAKE_CURRENT_LIST_DIR}/YYCCommonplace.hpp YYCCommonplace.hpp
# Sources
${CMAKE_CURRENT_LIST_DIR}/ConsoleHelper.cpp
${CMAKE_CURRENT_LIST_DIR}/DialogHelper.cpp
${CMAKE_CURRENT_LIST_DIR}/EncodingHelper.cpp
${CMAKE_CURRENT_LIST_DIR}/ExceptionHelper.cpp
${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 # Setup header infomations
target_include_directories(YYCCommonplace target_include_directories(YYCCommonplace
@ -36,6 +43,11 @@ PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
) )
# Link with DbgHelp.lib on Windows
target_link_libraries(YYCCommonplace
PRIVATE
$<$<BOOL:${WIN32}>:DbgHelp.lib>
)
# Setup C++ standard # Setup C++ standard
set_target_properties(YYCCommonplace set_target_properties(YYCCommonplace
PROPERTIES PROPERTIES
@ -63,23 +75,5 @@ install(TARGETS YYCCommonplace
ARCHIVE DESTINATION lib ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
INCLUDES DESTINATION include INCLUDES DESTINATION include
) FILE_SET HEADERS DESTINATION include
# Install headers
install(
FILES
# Common headers
${CMAKE_CURRENT_LIST_DIR}/ConsoleHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/DialogHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/EncodingHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/ExceptionHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/IOHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/ParserHelper.hpp
${CMAKE_CURRENT_LIST_DIR}/StringHelper.hpp
# Windows including guard pair
${CMAKE_CURRENT_LIST_DIR}/WinImportPrefix.hpp
${CMAKE_CURRENT_LIST_DIR}/WinImportSuffix.hpp
# Misc
${CMAKE_CURRENT_LIST_DIR}/YYCCInternal.hpp
${CMAKE_CURRENT_LIST_DIR}/YYCCommonplace.hpp
DESTINATION include
) )

40
src/COMHelper.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "COMHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
namespace YYCC::COMHelper {
/**
* @brief The guard for initialize COM environment.
* @details This class will try initializing COM environment by calling CoInitialize when constructing,
* and it also will try uninitializing COM environment when destructing.
* If initialization failed, uninitialization will not be executed.
*/
class ComGuard {
public:
ComGuard() : m_HasInit(false) {
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr)) m_HasInit = true;
}
~ComGuard() {
if (m_HasInit) {
CoUninitialize();
}
}
protected:
bool m_HasInit;
};
/**
* @brief The instance of COM environment guard.
* @details Dialog related function need COM environment,
* so we need initializing COM environment when loading this module,
* and uninitializing COM environment when we no longer use this module.
* So we use a static instance in here.
* And make it be const so no one can change it.
*/
static const ComGuard c_ComGuard;
}
#endif

69
src/COMHelper.hpp Normal file
View File

@ -0,0 +1,69 @@
#pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#include <memory>
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include <shlobj_core.h>
#include "WinImportSuffix.hpp"
/**
* @brief COM fucntions related namespace.
* @details
* This namespace is Windows specific and is unavailable on other platforms.
*
* This namespace contain a COM Guard which make sure COM was initialized in current module when loading current module.
* It is essential because all calling to COM functions should be under the premise that COM has been initialized.
* This guard also will uninitialize COM when unloading this module.
*
* This namespace also provided various memory-safe types for interacting with COM functions.
* Although Microsoft also has similar smart pointer called \c CComPtr.
* But this library is eager to hide all Microsoft-related functions calling.
* Using \c CComPtr is not corresponding with the philosophy of this library.
* So these std-based smart pointer type were created.
*
* This namespace is used by internal functions as intended.
* They should not be used outside of this library.
* But if you compel to use them, it is also okey.
*/
namespace YYCC::COMHelper {
/**
* @brief C++ standard deleter for every COM interfaces inheriting IUnknown.
*/
class ComPtrDeleter {
public:
ComPtrDeleter() {}
void operator() (IUnknown* com_ptr) {
if (com_ptr != nullptr) {
com_ptr->Release();
}
}
};
using SmartIFileDialog = std::unique_ptr<IFileDialog, ComPtrDeleter>;
using SmartIFileOpenDialog = std::unique_ptr<IFileOpenDialog, ComPtrDeleter>;
using SmartIShellItem = std::unique_ptr<IShellItem, ComPtrDeleter>;
using SmartIShellItemArray = std::unique_ptr<IShellItemArray, ComPtrDeleter>;
using SmartIShellFolder = std::unique_ptr<IShellFolder, ComPtrDeleter>;
/**
* @brief C++ standard deleter for almost raw pointer used in COM which need to be free by CoTaskMemFree()
*/
class CoTaskMemDeleter {
public:
CoTaskMemDeleter() {}
void operator() (void* com_ptr) {
if (com_ptr != nullptr) {
CoTaskMemFree(com_ptr);
}
}
};
using SmartLPWSTR = std::unique_ptr<std::remove_pointer_t<LPWSTR>, CoTaskMemDeleter>;
}
#endif

View File

@ -127,10 +127,10 @@ namespace YYCC::ConsoleHelper {
return real_return_buffer; return real_return_buffer;
} }
static void WinConsoleWrite(const std::string& strl) { static void WinConsoleWrite(const std::string& strl, bool to_stderr) {
// Prepare some Win32 variables // Prepare some Win32 variables
// fetch stdout handle first // fetch stdout handle first
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
DWORD dwConsoleMode; DWORD dwConsoleMode;
DWORD dwWrittenNumberOfChars; DWORD dwWrittenNumberOfChars;
@ -163,9 +163,10 @@ namespace YYCC::ConsoleHelper {
bool EnableColorfulConsole() { bool EnableColorfulConsole() {
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
if (!RawEnableColorfulConsole(stdout)) return false; bool ret = true;
if (!RawEnableColorfulConsole(stderr)) return false; ret &= RawEnableColorfulConsole(stdout);
return true; ret &= RawEnableColorfulConsole(stderr);
return ret;
#else #else
@ -198,35 +199,80 @@ namespace YYCC::ConsoleHelper {
#endif #endif
} }
static void RawWrite(const std::string& strl) { template<bool bNeedFmt, bool bIsErr, bool bHasEOL>
static void RawWrite(const char* u8_fmt, va_list argptr) {
// Buiild string need to be written first
// If no format string or plain string for writing, return.
if (u8_fmt == nullptr) return;
// Build or simply copy string
std::string strl;
if constexpr (bNeedFmt) {
// treat as format string
va_list argcpy;
va_copy(argcpy, argptr);
strl = YYCC::StringHelper::VPrintf(u8_fmt, argcpy);
va_end(argcpy);
} else {
// treat as plain string
strl = u8_fmt;
}
// Checkout whether add EOL
if constexpr (bHasEOL) {
strl += "\n";
}
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
// call Windows specific writer // call Windows specific writer
WinConsoleWrite(strl); WinConsoleWrite(strl, bIsErr);
#else #else
// in linux, directly use C function to write. // in linux, directly use C function to write.
std::fputs(strl.c_str(), stdout); std::fputs(strl.c_str(), to_stderr ? stderr : stdout);
#endif #endif
} }
void Write(const char* u8_fmt, ...) { void Format(const char* u8_fmt, ...) {
va_list argptr; va_list argptr;
va_start(argptr, u8_fmt); va_start(argptr, u8_fmt);
RawWrite(YYCC::StringHelper::VPrintf(u8_fmt, argptr)); RawWrite<true, false, false>(u8_fmt, argptr);
va_end(argptr); va_end(argptr);
} }
void WriteLine(const char* u8_fmt, ...) { void FormatLine(const char* u8_fmt, ...) {
va_list argptr; va_list argptr;
va_start(argptr, u8_fmt); va_start(argptr, u8_fmt);
std::string cache(YYCC::StringHelper::VPrintf(u8_fmt, argptr)); RawWrite<true, false, true>(u8_fmt, argptr);
cache += "\n";
RawWrite(cache);
va_end(argptr); va_end(argptr);
} }
void Write(const char* u8_strl) {
RawWrite<false, false, false>(u8_strl, va_list());
}
void WriteLine(const char* u8_strl) {
RawWrite<false, false, true>(u8_strl, va_list());
}
void ErrFormat(const char* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
RawWrite<true, true, false>(u8_fmt, argptr);
va_end(argptr);
}
void ErrFormatLine(const char* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
RawWrite<true, true, true>(u8_fmt, argptr);
va_end(argptr);
}
void ErrWrite(const char* u8_strl) {
RawWrite<false, true, false>(u8_strl, va_list());
}
void ErrWriteLine(const char* u8_strl) {
RawWrite<false, true, true>(u8_strl, va_list());
}
} }

View File

@ -4,6 +4,64 @@
#include <cstdio> #include <cstdio>
#include <string> #include <string>
/**
* @brief The namespace providing universal Console visiting functions like C-Sharp Console class.
* @details
* \par Why this Namespace
* Windows console doesn't support UTF8 very well.
* The standard input output functions can not work properly on Windows with UTF8.
* So we create this namespace and provide various console-related functions
* to patch Windows console and let it more like the console in other platforms.
* \par
* The function provided in this function can be called in any platforms.
* In Windows, the implementation will use Windows native function,
* and in other platform, the implementation will redirect request to standard C function
* like std::fputs and etc.
* So the programmer do not need to be worried about which function should they use,
* and don't need to use macro to use different IO function in different platforms.
* It is just enough that fully use the functions provided in this namespace.
* \par
* All IO functions this namespace provided are UTF8-based.
* It also means that input output string should always be UTF8 encoded.
*
* \par Input Functions
* Please note that EOL will automatically converted into LF on Windows platform, not CRLF.
* This action actually is removing all CR chars in result string.
* This behavior affect nothing in most cases but it still is possible break something in some special case.
* \par
* Due to implementation, if you decide to use this function,
* you should give up using any other function to read stdin stream,
* such as std::gets() and std::cin.
* Because this function may read chars which is more than needed.
* These extra chars will be stored in this function and can be used next calling.
* But these chars can not be visited by stdin again.
* This behavior may cause bug.
* So if you decide using this function, stick on it and do not change.
* \par
* Due to implementation, this function do not support hot switch of stdin.
* It means that stdin can be redirected before first calling of this function,
* but it should not be redirected during program running.
* The reason is the same one introduced above.
*
* \par Output Functions
* In current implementation, EOL will not be converted automatically to CRLF.
* This is different with other stream read functions provided in this namespace.
* \par
* Comparing with other stream read functions provided in this namespace,
* stream write function support hot switch of stdout and stderr.
* Because they do not have internal buffer storing something.
* \par
* In this namespace, there are various stream write function.
* There is a list telling you how to choose one from them for using:
* \li Functions with leading "Err" will write data into stderr,
* otherwise they will write data into stdout.
* \li Functions with embedded "Format" are output functions with format feature
* like std::fprintf(), otherwise the functions with embedded "Write" will
* only write plain string like std::fputs().
* \li Functions with trailing "Line" will write extra EOL to break current line.
* This is commonly used, otherwise functions will only write the text provided by arguments,
* without adding something.
*/
namespace YYCC::ConsoleHelper { namespace YYCC::ConsoleHelper {
#define YYCC_COLORHDR_BLACK "\033[30m" #define YYCC_COLORHDR_BLACK "\033[30m"
@ -56,54 +114,62 @@ namespace YYCC::ConsoleHelper {
/** /**
* @brief Universal console read function * @brief Universal console read function
* @details This function is more like C# Console.ReadLine(). * @return
* The UTF8 encoded string this function read. EOL is excluded.
* Empty string if user just press Enter key or function failed.
* @remarks
* This function is more like C# Console.ReadLine().
* It read user input with UTF8 encoding until reaching EOL. * It read user input with UTF8 encoding until reaching EOL.
* * \par
* This function provide an universal, platform-independent way to read UTF8 string from console,
* no matter whether it is redirected.
* @return The UTF8 encoded string this function read. EOL is excluded.
* @remarks In Windows, this function will try use native Win32 function for reading,
* because standard C/C++ function can not handle UTF8 input on Windows normally.
* In other platforms, this function will redirect request to std::readline with std::cin,
* which is all programmer commonly used method.
* It also mean that we assume stdin is encoded by UTF8 on these platforms.
*
* Please note that EOL will automatically converted into LF on Windows platform, not CRLF.
* This action is actually remove all CR chars in result string.
*
* This function also can be used as ordering user press Enter key by * This function also can be used as ordering user press Enter key by
* simply calling this function and ignoring its return value. * simply calling this function and ignoring its return value.
*/ */
std::string ReadLine(); std::string ReadLine();
/** /**
* @brief Universal console write function * @brief Universal console write function with format feature.
* @details This function is more like C# Console.Write(). * @param[in] u8_fmt The format string.
* It write user given UTF8 string into console. * @param[in] ... The arguments to be formatted.
*
* This function provide an universal, platform-independent way to write UTF8 string into console,
* no matter whether it is redirected.
* @param u8_fmt[in] The format string.
* If you just want to write a pure string, you should escape formatter chars (%) in this string,
* because this function always take this parameter as a format string.
* @param ...[in] The arguments to be formatted.
* @remarks In Windows, this funcion will use native Win32 function for writing,
* because standard C/C++ function can not handle UTF8 output on Windows normally.
* In other platforms, this function will redirect request to std::fprintf with stdout,
* which is all programmer commonly used method.
* It also mean that we assume stdout is encoded by UTF8 on these platforms.
*/ */
void Write(const char* u8_fmt, ...); void Format(const char* u8_fmt, ...);
/** /**
* @brief Universal console write function with automatic EOL * @brief Universal console write function with format and auto EOL feature.
* @details This function is same as Write(const char*, ...), * @param[in] u8_fmt The format string.
* but it will automatically add EOL in output to break line. * @param[in] ... The arguments to be formatted.
* This is commonly used.
* @param u8_fmt[in] The format string.
* If you just want to write a pure string, you should escape formatter chars (%) in this string,
* because this function always take this parameter as a format string.
* If you want to output plain string, you need escape format
* @param ...[in] The arguments to be formatted.
*/ */
void WriteLine(const char* u8_fmt, ...); void FormatLine(const char* u8_fmt, ...);
/**
* @brief Universal console write function.
* @param[in] u8_strl The string to be written.
*/
void Write(const char* u8_strl);
/**
* @brief Universal console write function with auto EOL feature.
* @param[in] u8_strl The string to be written.
*/
void WriteLine(const char* u8_strl);
/**
* @brief Universal console error write function with format and feature.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments to be formatted.
*/
void ErrFormat(const char* u8_fmt, ...);
/**
* @brief Universal console error write function with format and auto EOL feature.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments to be formatted.
*/
void ErrFormatLine(const char* u8_fmt, ...);
/**
* @brief Universal console error write function.
* @param[in] u8_strl The string to be written.
*/
void ErrWrite(const char* u8_strl);
/**
* @brief Universal console error write function with auto EOL feature.
* @param[in] u8_strl The string to be written.
*/
void ErrWriteLine(const char* u8_strl);
} }

View File

@ -6,43 +6,6 @@
namespace YYCC::DialogHelper { namespace YYCC::DialogHelper {
#pragma region COM Guard
/**
* @brief The guard for initialize COM environment.
* @details This class will try initializing COM environment by calling CoInitialize when constructing,
* and it also will try uninitializing COM environment when destructing.
* If initialization failed, uninitialization will not be executed.
*/
class ComGuard {
public:
ComGuard() : m_HasInit(false) {
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr)) m_HasInit = true;
}
~ComGuard() {
if (m_HasInit) {
CoUninitialize();
}
}
protected:
bool m_HasInit;
};
/**
* @brief The instance of COM environment guard.
* @details Dialog related function need COM environment,
* so we need initializing COM environment when loading this module,
* and uninitializing COM environment when we no longer use this module.
* So we use a static instance in here.
* And make it be const so no one can change it.
*/
static const ComGuard c_ComGuard;
#pragma endregion
#pragma region FileFilters #pragma region FileFilters
bool FileFilters::Add(const char* filter_name, std::initializer_list<const char*> il) { bool FileFilters::Add(const char* filter_name, std::initializer_list<const char*> il) {
@ -180,12 +143,12 @@ namespace YYCC::DialogHelper {
* @return True if success, otherwise false. * @return True if success, otherwise false.
* @remarks This is an assist function of CommonFileDialog. * @remarks This is an assist function of CommonFileDialog.
*/ */
bool ExtractDisplayName(IShellItem* item, std::string& ret) { static bool ExtractDisplayName(IShellItem* item, std::string& ret) {
// fetch display name from IShellItem* // fetch display name from IShellItem*
LPWSTR _name; LPWSTR _name;
HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name); HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name);
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
SmartLPWSTR display_name(_name); COMHelper::SmartLPWSTR display_name(_name);
// convert result // convert result
if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret)) if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret))
@ -205,7 +168,7 @@ namespace YYCC::DialogHelper {
* @remarks This function is the real underlying function of all dialog functions. * @remarks This function is the real underlying function of all dialog functions.
*/ */
template<CommonFileDialogType EDialogType> template<CommonFileDialogType EDialogType>
bool CommonFileDialog(const FileDialog& params, std::vector<std::string>& ret) { static bool CommonFileDialog(const FileDialog& params, std::vector<std::string>& ret) {
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
// prepare result variable // prepare result variable
HRESULT hr; HRESULT hr;
@ -235,7 +198,7 @@ namespace YYCC::DialogHelper {
); );
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
// create memory-safe dialog pointer // create memory-safe dialog pointer
SmartIFileDialog pfd(_pfd); COMHelper::SmartIFileDialog pfd(_pfd);
// set options for dialog // set options for dialog
// before setting, always get the options first in order. // before setting, always get the options first in order.
@ -320,7 +283,7 @@ namespace YYCC::DialogHelper {
IShellItem* _item; IShellItem* _item;
hr = pfd->GetResult(&_item); hr = pfd->GetResult(&_item);
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
SmartIShellItem result_item(_item); COMHelper::SmartIShellItem result_item(_item);
// extract display name // extract display name
std::string result_name; std::string result_name;
@ -338,13 +301,13 @@ namespace YYCC::DialogHelper {
IFileOpenDialog* _pfod = nullptr; IFileOpenDialog* _pfod = nullptr;
hr = pfd->QueryInterface(IID_PPV_ARGS(&_pfod)); hr = pfd->QueryInterface(IID_PPV_ARGS(&_pfod));
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
SmartIFileOpenDialog pfod(_pfod); COMHelper::SmartIFileOpenDialog pfod(_pfod);
// obtain multiple file entires // obtain multiple file entires
IShellItemArray* _items; IShellItemArray* _items;
hr = pfod->GetResults(&_items); hr = pfod->GetResults(&_items);
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
SmartIShellItemArray result_items(_items); COMHelper::SmartIShellItemArray result_items(_items);
// analyze file entries // analyze file entries
// get array count first // get array count first
@ -357,7 +320,7 @@ namespace YYCC::DialogHelper {
IShellItem* _item;; IShellItem* _item;;
hr = result_items->GetItemAt(i, &_item); hr = result_items->GetItemAt(i, &_item);
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
SmartIShellItem result_item(_item); COMHelper::SmartIShellItem result_item(_item);
// extract display name // extract display name
std::string result_name; std::string result_name;

View File

@ -2,10 +2,10 @@
#include "YYCCInternal.hpp" #include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
#include "COMHelper.hpp"
#include <string> #include <string>
#include <vector> #include <vector>
#include <initializer_list> #include <initializer_list>
#include <memory>
#include "WinImportPrefix.hpp" #include "WinImportPrefix.hpp"
#include <Windows.h> #include <Windows.h>
@ -14,44 +14,6 @@
namespace YYCC::DialogHelper { namespace YYCC::DialogHelper {
#pragma region COM Pointer Management
/**
* @brief C++ standard deleter for every COM interfaces inheriting IUnknown.
*/
class ComPtrDeleter {
public:
ComPtrDeleter() {}
void operator() (IUnknown* com_ptr) {
if (com_ptr != nullptr) {
com_ptr->Release();
}
}
};
using SmartIFileDialog = std::unique_ptr<IFileDialog, ComPtrDeleter>;
using SmartIFileOpenDialog = std::unique_ptr<IFileOpenDialog, ComPtrDeleter>;
using SmartIShellItem = std::unique_ptr<IShellItem, ComPtrDeleter>;
using SmartIShellItemArray = std::unique_ptr<IShellItemArray, ComPtrDeleter>;
using SmartIShellFolder = std::unique_ptr<IShellFolder, ComPtrDeleter>;
/**
* @brief C++ standard deleter for almost raw pointer used in COM which need to be free by CoTaskMemFree()
*/
class CoTaskMemDeleter {
public:
CoTaskMemDeleter() {}
void operator() (void* com_ptr) {
if (com_ptr != nullptr) {
CoTaskMemFree(com_ptr);
}
}
};
using SmartLPWSTR = std::unique_ptr<std::remove_pointer_t<LPWSTR>, CoTaskMemDeleter>;
#pragma endregion
/** /**
* @brief The class represent the file types region in file dialog * @brief The class represent the file types region in file dialog
* @details THis class is specific for Windows use, not user oriented. * @details THis class is specific for Windows use, not user oriented.
@ -169,7 +131,7 @@ namespace YYCC::DialogHelper {
UINT m_WinDefaultFileTypeIndex; UINT m_WinDefaultFileTypeIndex;
bool m_HasTitle, m_HasInitFileName; bool m_HasTitle, m_HasInitFileName;
std::wstring m_WinTitle, m_WinInitFileName; std::wstring m_WinTitle, m_WinInitFileName;
SmartIShellItem m_WinInitDirectory; COMHelper::SmartIShellItem m_WinInitDirectory;
void Clear() { void Clear() {
m_WinOwner = nullptr; m_WinOwner = nullptr;

View File

@ -1,8 +1,11 @@
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#include <cuchar>
namespace YYCC::EncodingHelper { namespace YYCC::EncodingHelper {
#if YYCC_OS == YYCC_OS_WINDOWS
bool WcharToChar(const wchar_t* src, std::string& dest, UINT codepage) { bool WcharToChar(const wchar_t* src, std::string& dest, UINT codepage) {
int count, write_result; int count, write_result;
@ -65,6 +68,137 @@ namespace YYCC::EncodingHelper {
return ret; return ret;
} }
#endif
template<typename _TChar, std::enable_if_t<std::is_same_v<_TChar, char16_t> || std::is_same_v<_TChar, char32_t>, int> = 0>
static bool UTF8ToUTFOther(const char* src, std::basic_string<_TChar>& dest) {
// Reference:
// https://zh.cppreference.com/w/cpp/string/multibyte/mbrtoc32
// https://zh.cppreference.com/w/cpp/string/multibyte/mbrtoc16
// https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/mbrtoc16-mbrtoc323?view=msvc-170
//
// Due to the same reason introduced in UTFOtherToUTF8,
// we use these function as convertion function.
// init src string
if (src == nullptr) return false;
std::string src_string(src);
// init result string
dest.clear();
// init essential cvt variables
std::mbstate_t state {};
_TChar c1632;
const char* ptr = src_string.c_str();
const char* end = src_string.c_str() + src_string.size() + 1;
// start convertion
while (true) {
// do convertion
size_t rc;
if constexpr (std::is_same_v<_TChar, char16_t>) {
rc = std::mbrtoc16(&c1632, ptr, end - ptr, &state);
} else {
rc = std::mbrtoc32(&c1632, ptr, end - ptr, &state);
}
if (!rc) break;
// check result
if (rc == static_cast<size_t>(-1)) {
// encoding error, return false
return false;
} else if (rc == static_cast<size_t>(-2)) {
// insufficient sequence, return false
return false;
} else if (rc == static_cast<size_t>(-3)) {
// UTF16 pair case (usually is emoji, one emoji is represented by 2 UTF16)
//
// only push result char but do not increase pointer
// because this char is output from state.
dest.push_back(c1632);
} else {
// normal case
// append to result
dest.push_back(c1632);
// inc ptr
ptr += rc;
}
}
return true;
}
bool UTF8ToUTF16(const char* src, std::u16string& dest) {
return UTF8ToUTFOther<char16_t>(src, dest);
}
std::u16string UTF8ToUTF16(const char* src) {
std::u16string ret;
if (!UTF8ToUTF16(src, ret)) ret.clear();
return ret;
}
bool UTF8ToUTF32(const char* src, std::u32string& dest) {
return UTF8ToUTFOther<char32_t>(src, dest);
}
std::u32string UTF8ToUTF32(const char* src) {
std::u32string ret;
if (!UTF8ToUTF32(src, ret)) ret.clear();
return ret;
}
template<typename _TChar, std::enable_if_t<std::is_same_v<_TChar, char16_t> || std::is_same_v<_TChar, char32_t>, int> = 0>
static bool UTFOtherToUTF8(const _TChar* src, std::string& dest) {
// Reference:
// https://zh.cppreference.com/w/cpp/string/multibyte/c32rtomb
// https://zh.cppreference.com/w/cpp/string/multibyte/c16rtomb
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/c16rtomb-c32rtomb1?view=msvc-170
//
// Due to Microsoft implementation, c16rtomb and c32rtomb
// always convert UTF32 and UTF16 string into UTF8 string no matter current c locale.
// At the same time, most Linux use UTF8 as their locale.
// So using c16rtomb and c32rtomb do the convertion from UTF32 or UTF16 to UTF8 is reasonable.
// initialize src string
if (src == nullptr) return false;
std::basic_string<_TChar> src_string(src);
// init result string
dest.clear();
// init essential cvt variables
std::mbstate_t state {};
char out[MB_LEN_MAX] {};
for (_TChar c : src_string) {
// do convertion
std::size_t rc;
if constexpr (std::is_same_v<_TChar, char16_t>) {
rc = std::c16rtomb(out, c, &state);
} else {
rc = std::c32rtomb(out, c, &state);
}
// convertion failed
if (rc == static_cast<size_t>(-1)) return false;
// otherwise append result
dest.append(out, rc);
}
return true;
}
bool UTF16ToUTF8(const char16_t* src, std::string& dest) {
return UTFOtherToUTF8<char16_t>(src, dest);
}
std::string UTF16ToUTF8(const char16_t* src) {
std::string ret;
if (!UTF16ToUTF8(src, ret)) ret.clear();
return ret;
}
bool UTF32ToUTF8(const char32_t* src, std::string& dest) {
return UTFOtherToUTF8<char32_t>(src, dest);
}
std::string UTF32ToUTF8(const char32_t* src) {
std::string ret;
if (!UTF32ToUTF8(src, ret)) ret.clear();
return ret;
}
} }
#endif

View File

@ -1,15 +1,57 @@
#pragma once #pragma once
#include "YYCCInternal.hpp" #include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#include <string> #include <string>
#if YYCC_OS == YYCC_OS_WINDOWS
#include "WinImportPrefix.hpp" #include "WinImportPrefix.hpp"
#include <Windows.h> #include <Windows.h>
#include "WinImportSuffix.hpp" #include "WinImportSuffix.hpp"
#endif
/**
* @brief The namespace handling encoding issues.
* @details
* \par Windows Encoding Convertion
* This namespace provides the convertion between wchar_t, UTF8 and code-page-based string:
* The function name has following format: \c AAAToBBB.
* AAA is the source string and BBB is target string.
* AAA and BBB has following possible value:
* \li \c Char: Code-page-based string. Usually it will add a code page parameter for function to get the code page of this string. For code page, please see Microsoft document.
* \li \c UTF8: UTF8 string.
* \li \c Wchar: wchar_t string.
* \par
* For example: \c WcharToUTF8 will perform the convertion from wchar_t to UTF8,
* and \c CharToChar will perform the convertion between 2 code-page-based string and caller can specify individual code page for these 2 string.
* \par
* These functions are Windows specific and are unavailable on other platforms.
* Becasue Windows use wchar_t string as its function arguments for globalization, and this library use UTF8 everywhere.
* So it should have a bidirectional way to do convertion between wchar_t string and UTF8 string.
*
* \par UTF32, UTF16 and UTF8 Convertion
* This namespace also provide the convertion among UTF32, UTF16 and UTF8.
* These convertion functions are suit for all platforms, not Windows oriented.
* \par
* Due to implementation, this library assume all non-Windows system use UTF8 as their C locale.
* Otherwise these functions will produce wrong result.
*
* \par Function Parameters
* We provide these encoding convertion functions with following 2 types:
* \li Function returns \c bool and its parameter order source string pointer and a corresponding \c std::basic_string container for receiving result.
* \li Function returns corresponding \c std::basic_string result, and its parameter only order source string pointer.
* \par
* For these 2 declarations, both of them will not throw any exception and do not accept nullptr as source string.
* The only difference is that the way to indicate convertion error.
* \par
* First declaration will return false to indicate there is an error when doing convertion. Please note that the content of string container passing in may still be changed!
* Last declaration will return empty string to indicate error. Please note if you pass empty string in, they still will output empty string but it doesn't mean an error.
* So last declaration is used in the scenario that we don't care whether the convertion success did. For example, output something to console.
*
*/
namespace YYCC::EncodingHelper { namespace YYCC::EncodingHelper {
#if YYCC_OS == YYCC_OS_WINDOWS
bool WcharToChar(const wchar_t* src, std::string& dest, UINT codepage); bool WcharToChar(const wchar_t* src, std::string& dest, UINT codepage);
bool WcharToUTF8(const wchar_t* src, std::string& dest); bool WcharToUTF8(const wchar_t* src, std::string& dest);
std::string WcharToChar(const wchar_t* src, UINT codepage); std::string WcharToChar(const wchar_t* src, UINT codepage);
@ -23,6 +65,16 @@ namespace YYCC::EncodingHelper {
bool CharToChar(const char* src, std::string& dest, UINT src_codepage, UINT dest_codepage); bool CharToChar(const char* src, std::string& dest, UINT src_codepage, UINT dest_codepage);
std::string CharToChar(const char* src, UINT src_codepage, UINT dest_codepage); std::string CharToChar(const char* src, UINT src_codepage, UINT dest_codepage);
}
#endif #endif
bool UTF8ToUTF16(const char* src, std::u16string& dest);
std::u16string UTF8ToUTF16(const char* src);
bool UTF8ToUTF32(const char* src, std::u32string& dest);
std::u32string UTF8ToUTF32(const char* src);
bool UTF16ToUTF8(const char16_t* src, std::string& dest);
std::string UTF16ToUTF8(const char16_t* src);
bool UTF32ToUTF8(const char32_t* src, std::string& dest);
std::string UTF32ToUTF8(const char32_t* src);
}

View File

@ -1,11 +1,16 @@
#include "ExceptionHelper.hpp" #include "ExceptionHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
#include "WinFctHelper.hpp"
#include "ConsoleHelper.hpp"
#include "StringHelper.hpp"
#include "IOHelper.hpp"
#include "EncodingHelper.hpp"
#include "FsPathPatch.hpp"
#include <filesystem> #include <filesystem>
#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>
@ -49,6 +54,11 @@ namespace YYCC::ExceptionHelper {
#pragma region Exception Handler Implementation #pragma region Exception Handler Implementation
/**
* @brief Get human-readable exception string from given exception code.
* @param[in] code Exception code
* @return The const string pointer to corresponding exception explanation string.
*/
static const char* UExceptionGetCodeName(DWORD code) { static const char* UExceptionGetCodeName(DWORD code) {
switch (code) { switch (code) {
case EXCEPTION_ACCESS_VIOLATION: case EXCEPTION_ACCESS_VIOLATION:
@ -96,24 +106,50 @@ namespace YYCC::ExceptionHelper {
} }
} }
static void UExceptionFormat(std::FILE* fs, const char* fmt, ...) { /**
* @brief Error log (including backtrace) used output function with format feature
* @details
* This function will format message first.
* And write them into given file stream and stderr.
* @param[in] fs
* The file stream where we write.
* If it is nullptr, function will skip writing for file stream.
* @param[in] fmt The format string.
* @param[in] ... The argument to be formatted.
*/
static void UExceptionErrLogFormatLine(std::FILE* fs, const char* fmt, ...) {
// write to file // write to file
if (fs != nullptr) {
va_list arg1; va_list arg1;
va_start(arg1, fmt); va_start(arg1, fmt);
std::vfprintf(fs, fmt, arg1); std::vfprintf(fs, fmt, arg1);
std::fputs("\n", fs);
va_end(arg1); va_end(arg1);
// write to stdout }
// write to stderr
va_list arg2; va_list arg2;
va_start(arg2, fmt); va_start(arg2, fmt);
std::vfprintf(stdout, fmt, arg2); ConsoleHelper::ErrWriteLine(YYCC::StringHelper::VPrintf(fmt, arg2).c_str());
va_end(arg2); va_end(arg2);
} }
static void UExceptionPrint(std::FILE* fs, const char* strl) { /**
* @brief Error log (including backtrace) used output function
* @details
* This function will write given string into given file stream and stderr.
* @param[in] fs
* The file stream where we write.
* If it is nullptr, function will skip writing for file stream.
* @param[in] strl The string to be written.
*/
static void UExceptionErrLogWriteLine(std::FILE* fs, const char* strl) {
// write to file // write to file
if (fs != nullptr) {
std::fputs(strl, fs); std::fputs(strl, fs);
// write to stdout std::fputs("\n", fs);
std::fputs(strl, stdout); }
// write to stderr
ConsoleHelper::ErrWriteLine(strl);
} }
static void UExceptionBacktrace(FILE* fs, LPCONTEXT context, int maxdepth) { static void UExceptionBacktrace(FILE* fs, LPCONTEXT context, int maxdepth) {
@ -126,8 +162,8 @@ namespace YYCC::ExceptionHelper {
// init symbol // init symbol
if (!SymInitialize(process, 0, TRUE)) { if (!SymInitialize(process, 0, TRUE)) {
// fail to load. return // fail to init. return
UExceptionPrint(fs, "Lost symbol file!\n"); UExceptionErrLogWriteLine(fs, "Fail to initialize symbol handle for process!");
return; return;
} }
@ -172,9 +208,6 @@ namespace YYCC::ExceptionHelper {
frame.AddrStack.Mode = AddrModeFlat; frame.AddrStack.Mode = AddrModeFlat;
frame.AddrFrame.Mode = AddrModeFlat; frame.AddrFrame.Mode = AddrModeFlat;
// other variables
char module_name_raw[MAX_PATH];
// stack walker // stack walker
while (StackWalk64(machine_type, process, thread, &frame, context, while (StackWalk64(machine_type, process, thread, &frame, context,
0, SymFunctionTableAccess64, SymGetModuleBase64, 0)) { 0, SymFunctionTableAccess64, SymGetModuleBase64, 0)) {
@ -182,19 +215,22 @@ namespace YYCC::ExceptionHelper {
// depth breaker // depth breaker
--maxdepth; --maxdepth;
if (maxdepth < 0) { if (maxdepth < 0) {
UExceptionPrint(fs, "...\n"); // indicate there are some frames not listed UExceptionErrLogWriteLine(fs, "..."); // indicate there are some frames not listed
break; break;
} }
// get module name // get module name
DWORD64 module_base = SymGetModuleBase64(process, frame.AddrPC.Offset); const char* module_name = "<unknown module>";
const char* module_name = "[unknown module]"; std::string module_name_raw;
if (module_base && GetModuleFileNameA((HINSTANCE)module_base, module_name_raw, MAX_PATH)) { DWORD64 module_base;
module_name = module_name_raw; if (module_base = SymGetModuleBase64(process, frame.AddrPC.Offset)) {
if (WinFctHelper::GetModuleFileName((HINSTANCE)module_base, module_name_raw)) {
module_name = module_name_raw.c_str();
}
} }
// get source file and line // get source file and line
const char* source_file = "[unknow_source_file]"; const char* source_file = "<unknown source>";
DWORD64 source_file_line = 0; DWORD64 source_file_line = 0;
DWORD dwDisplacement; DWORD dwDisplacement;
IMAGEHLP_LINE64 winline; IMAGEHLP_LINE64 winline;
@ -205,9 +241,10 @@ namespace YYCC::ExceptionHelper {
} }
// write to file // write to file
UExceptionFormat(fs, "0x%016llx(rel: 0x%016llx)[%s]\t%s#%llu\n", UExceptionErrLogFormatLine(fs, "0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "[%s+0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "]\t%s#L%" PRIu64,
frame.AddrPC.Offset, frame.AddrPC.Offset - module_base, module_name, frame.AddrPC.Offset, // memory adress
source_file, source_file_line module_name, frame.AddrPC.Offset - module_base, // module name + relative address
source_file, source_file_line // source file + source line
); );
} }
@ -218,9 +255,51 @@ namespace YYCC::ExceptionHelper {
SymCleanup(process); SymCleanup(process);
} }
static void UExceptionCoreDump(LPCWSTR filename, LPEXCEPTION_POINTERS info) { static void UExceptionErrorLog(const std::string& u8_filename, LPEXCEPTION_POINTERS info) {
// open file stream if we have file name
std::FILE* fs = nullptr;
if (!u8_filename.empty()) {
fs = IOHelper::UTF8FOpen(u8_filename.c_str(), "wb");
}
// record exception type first
PEXCEPTION_RECORD rec = info->ExceptionRecord;
UExceptionErrLogFormatLine(fs, "Unhandled exception occured at 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR ": %s (%" PRIu32 ").",
rec->ExceptionAddress,
UExceptionGetCodeName(rec->ExceptionCode),
rec->ExceptionCode
);
// special proc for 2 exceptions
if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) {
if (rec->NumberParameters >= 2) {
const char* op =
rec->ExceptionInformation[0] == 0 ? "read" :
rec->ExceptionInformation[0] == 1 ? "written" : "executed";
UExceptionErrLogFormatLine(fs, "The data at memory address 0x%" PRI_XPTR_LEFT_PADDING PRIxPTR " could not be %s.",
rec->ExceptionInformation[1], op);
}
}
// output stacktrace
UExceptionBacktrace(fs, info->ContextRecord, 1024);
// close file if necessary
if (fs != nullptr) {
std::fclose(fs);
}
}
static void UExceptionCoreDump(const std::string& u8_filename, LPEXCEPTION_POINTERS info) {
// convert file encoding
std::wstring filename;
if (u8_filename.empty())
return; // if no given file name, return
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_filename.c_str(), filename))
return; // if convertion failed, return
// open file and write // open file and write
HANDLE hFile = CreateFileW(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); HANDLE hFile = CreateFileW(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) { if (hFile != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION exception_info; MINIDUMP_EXCEPTION_INFORMATION exception_info;
exception_info.ThreadId = GetCurrentThreadId(); exception_info.ThreadId = GetCurrentThreadId();
@ -236,68 +315,84 @@ namespace YYCC::ExceptionHelper {
} }
} }
static bool UExceptionFetchRecordPath(std::string& log_path, std::string& coredump_path) {
// build two file names like: "module.dll.1234.log" and "module.dll.1234.dmp".
// "module.dll" is the name of current module. "1234" is current process id.
// get self module name
std::string u8_self_module_name;
{
// get module handle
HMODULE hSelfModule = YYCC::WinFctHelper::GetCurrentModule();
if (hSelfModule == nullptr)
return false;
// get full path of self module
std::string u8_self_module_path;
if (!YYCC::WinFctHelper::GetModuleFileName(hSelfModule, u8_self_module_path))
return false;
// extract file name from full path by std::filesystem::path
std::filesystem::path self_module_path(FsPathPatch::FromUTF8Path(u8_self_module_path.c_str()));
u8_self_module_name = FsPathPatch::ToUTF8Path(self_module_path.filename());
}
// then get process id
DWORD process_id = GetCurrentProcessId();
// conbine them as a file name prefix
std::string u8_filename_prefix;
if (!YYCC::StringHelper::Printf(u8_filename_prefix, "%s.%" PRIu32, u8_self_module_name.c_str(), process_id))
return false;
// then get file name for log and minidump
std::string u8_log_filename = u8_filename_prefix + ".log";
std::string u8_coredump_filename = u8_filename_prefix + ".dmp";
// fetch crash report path
// get local appdata folder
std::string u8_localappdata_path;
if (!WinFctHelper::GetLocalAppData(u8_localappdata_path))
return false;
// convert to std::filesystem::path
std::filesystem::path crash_report_path(FsPathPatch::FromUTF8Path(u8_localappdata_path.c_str()));
// slash into crash report folder
crash_report_path /= FsPathPatch::FromUTF8Path("CrashDumps");
// use create function to make sure it is existing
std::filesystem::create_directories(crash_report_path);
// build log path and coredump path
// build std::filesystem::path first
std::filesystem::path log_filepath = crash_report_path / FsPathPatch::FromUTF8Path(u8_log_filename.c_str());
std::filesystem::path coredump_filepath = crash_report_path / FsPathPatch::FromUTF8Path(u8_coredump_filename.c_str());
// output to result
log_path = FsPathPatch::ToUTF8Path(log_filepath);
coredump_path = FsPathPatch::ToUTF8Path(coredump_filepath);
return true;
}
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) { static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) {
// detect loop calling // detect loop calling
if (g_IsProcessing) { if (g_IsProcessing) goto end_proc;
goto end_proc;
}
// start process // start process
g_IsProcessing = true; g_IsProcessing = true;
// core implementation
{ {
// get main folder first // fetch error report path first
std::filesystem::path ironpad_path; std::string log_path, coredump_path;
WCHAR module_path[MAX_PATH]; if (!UExceptionFetchRecordPath(log_path, coredump_path)) {
std::memset(module_path, 0, sizeof(module_path)); // fail to fetch path, clear them.
if (GetModuleFileNameW(WinFctHelper::GetCurrentModule(), module_path, MAX_PATH) == 0) { // we still can handle crash without them
goto failed; log_path.clear();
} coredump_path.clear();
ironpad_path = module_path; // and tell user we can not output file
ironpad_path = ironpad_path.parent_path(); ConsoleHelper::ErrWriteLine("Crash occurs, but we can not create crash log and coredump!");
} else {
// create 2 filename // okey. output file path to tell user the path where you can find.
auto logfilename = ironpad_path / "IronPad.log"; ConsoleHelper::ErrFormatLine("Crash Log: %s", log_path.c_str());
auto dmpfilename = ironpad_path / "IronPad.dmp"; ConsoleHelper::ErrFormatLine("Crash Coredump: %s", coredump_path.c_str());
std::fputc('\n', stdout);
std::fprintf(stdout, "Exception Log: %s\n", logfilename.string().c_str());
std::fprintf(stdout, "Exception Coredump: %s\n", dmpfilename.string().c_str());
// output log file
{
std::FILE* fs = _wfopen(logfilename.wstring().c_str(), L"w");
if (fs == nullptr) {
goto failed;
} }
// record exception type first // write crash log
PEXCEPTION_RECORD rec = info->ExceptionRecord; UExceptionErrorLog(log_path, info);
fprintf(fs, "Unhandled exception occured at 0x%08p: %s (%lu).\n", // write crash coredump
rec->ExceptionAddress, UExceptionCoreDump(coredump_path, info);
UExceptionGetCodeName(rec->ExceptionCode),
rec->ExceptionCode
);
// special proc for 2 exceptions
if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) {
if (rec->NumberParameters >= 2) {
const char* op =
rec->ExceptionInformation[0] == 0 ? "read" :
rec->ExceptionInformation[0] == 1 ? "written" : "executed";
fprintf(fs, "The data at memory address 0x%016" PRIxPTR " could not be %s.\n",
rec->ExceptionInformation[1], op);
}
}
// output stacktrace
UExceptionBacktrace(fs, info->ContextRecord, 1024);
std::fclose(fs);
}
// output minidump
{
UExceptionCoreDump(dmpfilename.wstring().c_str(), info);
}
} }

View File

@ -2,32 +2,40 @@
#include "YYCCInternal.hpp" #include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
/**
* @brief Windows specific unhandled exception processor.
* @details
* This namespace is Windows specific. On other platforms, the whole namespace is unavailable.
*
* This namespace allow user register unhandled exception handler on Windows
* to output error log into \c stderr and log file, and generate coredump if possible.
* This is useful for bug tracing on Windows, especially most Windows user are naive and don't know how to report bug.
*
*/
namespace YYCC::ExceptionHelper { namespace YYCC::ExceptionHelper {
/** /**
* @brief Register unhandled exception handler * @brief Register unhandled exception handler
* @details * @details
* This function will set an internal function as unhandled exception handler on Windows. * This function will set an internal function as unhandled exception handler on Windows.
* \n *
* When unhandled exception raised, * When unhandled exception raised,
* That internal function will output error stacktrace in standard output * That internal function will output error stacktrace in standard output
* and log file (located in temp folder), and also generate a dump file * 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. * 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. * This function usually is called at the start of program.
* @remarks This function is Windows only.
*/ */
void Register(); void Register();
/** /**
* @brief Unregister unhandled exception handler * @brief Unregister unhandled exception handler
* @details * @details
* The reverse operation of Register(). * The reverse operation of Register().
* \n *
* This function and Register() should always be used as a pair. * This function and Register() should always be used as a pair.
* You must call this function if you have called Register() before. * You must call this function if you have called Register() before.
* \n *
* This function usually is called at the end of program. * This function usually is called at the end of program.
* @remarks This function is Windows only.
*/ */
void Unregister(); void Unregister();

41
src/FsPathPatch.cpp Normal file
View File

@ -0,0 +1,41 @@
#include "FsPathPatch.hpp"
#include "EncodingHelper.hpp"
#include <string>
#include <stdexcept>
namespace YYCC::FsPathPatch {
std::filesystem::path FromUTF8Path(const char* u8_path) {
#if YYCC_OS == YYCC_OS_WINDOWS
// convert path to wchar
std::wstring wpath;
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_path, wpath))
throw std::invalid_argument("Fail to convert given UTF8 string.");
// call microsoft specified fopen which support wchar as argument.
return std::filesystem::path(wpath);
#else
return std::filesystem::path(u8_path);
#endif
}
std::string ToUTF8Path(const std::filesystem::path& path) {
#if YYCC_OS == YYCC_OS_WINDOWS
// get and convert to utf8
std::string u8_path;
if (!YYCC::EncodingHelper::WcharToUTF8(path.c_str(), u8_path))
throw std::invalid_argument("Fail to convert to UTF8 string.");
// return utf8 path
return u8_path;
#else
return path.string();
#endif
}
}

41
src/FsPathPatch.hpp Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include "YYCCInternal.hpp"
#include <filesystem>
/**
* @brief The patch namespace resolving \c std::filesystem::path encoding issue.
* @details
* This patch is Windows oriented.
* If you are in Windows, this patch will perform extra operations to achieve goals,
* and in other platforms, they just redirect request to corresponding vanilla C++ functions.
*
* As you know, the underlying char type of \c std::filesystem::path is \c wchar_t on Windows,
* and in other platforms, it is simple \c char.
* Due to this, if you passing UTF8 char sequence to \c std::filesystem::path on Windows,
* the library implementation will assume your input is based on current Windows code page, not UTF8.
* And the final path stored in \c std::filesystem::path is not what you expcected.
*
* This patch namespace always use UTF8 as its argument. There is no ambiguous issue.
* You should use the functions provided by this namespace on any platforms
* instead of vanilla \c std::filesystem::path functions.
*/
namespace YYCC::FsPathPatch {
/**
* @brief Constructs the path from a UTF8 character sequence
* @param[in] u8_path UTF8 path string for building this std::filesystem::path.
* @return std::filesystem::path instance.
* @exception std::invalid_argument Fail to parse given UTF8 string (maybe invalid?).
*/
std::filesystem::path FromUTF8Path(const char* u8_path);
/**
* @brief Returns the UTF8 representation of the pathname
* @param path[in] The string to be output.
* @return UTF8 encoded string representing given path.
* @exception std::invalid_argument Fail to parse to UTF8 string.
*/
std::string ToUTF8Path(const std::filesystem::path& path);
}

View File

@ -1,25 +1,22 @@
#include "IOHelper.hpp" #include "IOHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include <cstdio> #include <cstdio>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <stdexcept>
#if YYCC_OS == YYCC_OS_WINDOWS
#include "WinImportPrefix.hpp" #include "WinImportPrefix.hpp"
#include <Windows.h> #include <Windows.h>
#include <io.h>
#include <fcntl.h>
#include "WinImportSuffix.hpp" #include "WinImportSuffix.hpp"
#endif
namespace YYCC::IOHelper { namespace YYCC::IOHelper {
bool futf8(FILE* fs) { FILE* UTF8FOpen(const char* u8_filepath, const char* u8_mode) {
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setmode?view=msvc-170 #if YYCC_OS == YYCC_OS_WINDOWS
return _setmode(_fileno(fs), _O_U8TEXT) != -1;
}
FILE* fopen(const char* u8_filepath, const char* u8_mode) {
// convert mode and file path to wchar // convert mode and file path to wchar
std::wstring wmode, wpath; std::wstring wmode, wpath;
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_mode, wmode)) if (!YYCC::EncodingHelper::UTF8ToWchar(u8_mode, wmode))
@ -29,8 +26,11 @@ namespace YYCC::IOHelper {
// call microsoft specified fopen which support wchar as argument. // call microsoft specified fopen which support wchar as argument.
return _wfopen(wpath.c_str(), wmode.c_str()); return _wfopen(wpath.c_str(), wmode.c_str());
#else
return std::fopen(u8_filepath, u8_mode);
#endif
} }
} }
#endif

View File

@ -1,23 +1,37 @@
#pragma once #pragma once
#include "YYCCInternal.hpp" #include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#include <cstdio> #include <cstdio>
#include <filesystem>
namespace YYCC::IOHelper { namespace YYCC::IOHelper {
#if UINTPTR_MAX == UINT32_MAX
#define PRI_XPTR_LEFT_PADDING "08"
#elif UINTPTR_MAX == UINT64_MAX
/** /**
* @brief Set given FILE* as UTF8 mode. * @brief The left-padding zero format string of HEX-printed pointer type.
* @param fs[in] The FILE* need to be set as UTF8 mode. * @details
* @return True if success, otherwise false. * When printing a pointer with HEX style, we always hope it can be left-padded with some zero for easy reading.
* @warning Once this function success, you can not use any narrow char function on this FILE*, * In different architecture, the size of this padding is differnet too so we create this macro.
* such as std::fputs, std::fprintf and etc. You only can use wide char function on it, *
* or use the functions provided in this namespace by providing UTF8 string as their argument. * In 32-bit environment, it will be "08" meaning left pad zero until 8 number position.
* In 64-bit environment, it will be "016" meaning left pad zero until 16 number position.
*/ */
bool futf8(FILE* fs); #define PRI_XPTR_LEFT_PADDING "016"
#else
#error "Not supported pointer size."
#endif
FILE* fopen(const char* u8_filepath, const char* u8_mode); /**
* @brief The UTF8 version of std::fopen.
* @param[in] u8_filepath The UTF8 encoded path to the file to be opened.
* @param[in] u8_mode UTF8 encoded mode string of the file to be opened.
* @remarks
* This function is suit for Windows because std::fopen do not support UTF8 on Windows.
* On other platforms, this function will delegate request directly to std::fopen.
* @return FILE* of the file to be opened, or nullptr if failed.
*/
FILE* UTF8FOpen(const char* u8_filepath, const char* u8_mode);
} }
#endif

View File

View File

@ -2,6 +2,7 @@
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include "COMHelper.hpp"
namespace YYCC::WinFctHelper { namespace YYCC::WinFctHelper {
@ -16,7 +17,7 @@ namespace YYCC::WinFctHelper {
return hModule; return hModule;
} }
std::string GetTempDirectory() { bool GetTempDirectory(std::string& ret) {
// create wchar buffer for receiving the temp path. // create wchar buffer for receiving the temp path.
std::wstring wpath(MAX_PATH + 1u, L'\0'); std::wstring wpath(MAX_PATH + 1u, L'\0');
DWORD expected_size; DWORD expected_size;
@ -25,9 +26,7 @@ namespace YYCC::WinFctHelper {
while (true) { while (true) {
if ((expected_size = GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) { if ((expected_size = GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) {
// failed, set to empty // failed, set to empty
expected_size = 0; return false;
// and break while
break;
} }
if (expected_size > static_cast<DWORD>(wpath.size())) { if (expected_size > static_cast<DWORD>(wpath.size())) {
@ -42,19 +41,18 @@ namespace YYCC::WinFctHelper {
// resize result // resize result
wpath.resize(expected_size); wpath.resize(expected_size);
// convert to utf8 and return // convert to utf8 and return
return YYCC::EncodingHelper::WcharToUTF8(wpath.c_str()); return YYCC::EncodingHelper::WcharToUTF8(wpath.c_str(), ret);
} }
std::string GetModuleName(HINSTANCE hModule) { bool GetModuleFileName(HINSTANCE hModule, std::string& ret) {
// create wchar buffer for receiving the temp path. // create wchar buffer for receiving the temp path.
std::wstring wpath(MAX_PATH + 1u, L'\0'); std::wstring wpath(MAX_PATH + 1u, L'\0');
DWORD copied_size; DWORD copied_size;
while (true) { while (true) {
if ((copied_size = GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) { if ((copied_size = GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) {
// failed, return empty string // failed, return
copied_size = 0; return false;
break;
} }
// check insufficient buffer // check insufficient buffer
@ -70,7 +68,18 @@ namespace YYCC::WinFctHelper {
// resize result // resize result
wpath.resize(copied_size); wpath.resize(copied_size);
// convert to utf8 and return // convert to utf8 and return
return YYCC::EncodingHelper::WcharToUTF8(wpath.c_str()); return YYCC::EncodingHelper::WcharToUTF8(wpath.c_str(), ret);
}
bool GetLocalAppData(std::string& ret) {
// fetch path
LPWSTR _known_path;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &_known_path);
if (FAILED(hr)) return false;
COMHelper::SmartLPWSTR known_path(_known_path);
// convert to utf8
return YYCC::EncodingHelper::WcharToUTF8(known_path.get(), ret);
} }
} }

View File

@ -8,6 +8,12 @@
#include <Windows.h> #include <Windows.h>
#include "WinImportSuffix.hpp" #include "WinImportSuffix.hpp"
/**
* @brief The helper providing assistance to Win32 functions.
* @details
* This helper is Windows specific.
* If current environment is not Windows, the whole namespace will be unavailable.
*/
namespace YYCC::WinFctHelper { namespace YYCC::WinFctHelper {
/** /**
@ -15,7 +21,7 @@ namespace YYCC::WinFctHelper {
* @details * @details
* If your target is EXE, the current module simply is your program self. * 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. * 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. * This function is frequently used by DLL.
* Because some design need the HANDLE of current module, not the host EXE loading your 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. * 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.
@ -26,18 +32,32 @@ namespace YYCC::WinFctHelper {
/** /**
* @brief Get path to Windows temp folder. * @brief Get path to Windows temp folder.
* @return UTF8 encoded path to Windows temp folder. Empty string if failed. * @param[out] ret
* The variable receiving UTF8 encoded path to Windows temp folder.
* @return True if success, otherwise false.
*/ */
std::string GetTempDirectory(); bool GetTempDirectory(std::string& ret);
/** /**
* @brief Get the file name of given module HANDLE * @brief Get the file name of given module HANDLE
* @param hModule[in] * @param[in] hModule
* The HANDLE to the module where we want get file name. * The HANDLE to the module where we want get file name.
* It is same as the HANDLE parameter of GetModuleFileName. * It is same as the HANDLE parameter of \c GetModuleFileName.
* @return UTF8 encoded file name of given module. Empty string if failed. * @param[out] ret
* The variable receiving UTF8 encoded file name of given module.
* @return True if success, otherwise false.
*/ */
std::string GetModuleName(HINSTANCE hModule); bool GetModuleFileName(HINSTANCE hModule, std::string& ret);
/**
* @brief Get the path to LOCALAPPDATA.
* @details LOCALAPPDATA usually was used as putting local app data files
* @param[out] ret
* The variable receiving UTF8 encoded path to LOCALAPPDATA.
* @return
*/
bool GetLocalAppData(std::string& ret);
} }
#endif #endif

View File

@ -15,5 +15,6 @@
#undef GetClassName #undef GetClassName
#undef LoadImage #undef LoadImage
#undef GetTempPath #undef GetTempPath
#undef GetModuleFileName
#endif #endif

View File

@ -5,8 +5,10 @@
#include "StringHelper.hpp" #include "StringHelper.hpp"
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include "ConsoleHelper.hpp" #include "ConsoleHelper.hpp"
#include "COMHelper.hpp"
#include "DialogHelper.hpp" #include "DialogHelper.hpp"
#include "ParserHelper.hpp" #include "ParserHelper.hpp"
#include "ExceptionHelper.hpp" #include "ExceptionHelper.hpp"
#include "IOHelper.hpp" #include "IOHelper.hpp"
#include "WinFctHelper.hpp" #include "WinFctHelper.hpp"
#include "FsPathPatch.hpp"

View File

@ -3,7 +3,7 @@ add_executable(YYCCTestbench "")
# Setup testbench sources # Setup testbench sources
target_sources(YYCCTestbench target_sources(YYCCTestbench
PRIVATE PRIVATE
${CMAKE_CURRENT_LIST_DIR}/main.cpp main.cpp
) )
# Add YYCC as its library # Add YYCC as its library
target_include_directories(YYCCTestbench target_include_directories(YYCCTestbench

View File

@ -5,11 +5,99 @@ namespace Console = YYCC::ConsoleHelper;
namespace YYCCTestbench { namespace YYCCTestbench {
#pragma region Unicode Test Data
// UNICODE Test Strings
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
#define TEST_UNICODE_STR_JAPAN "\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8"
#define TEST_UNICODE_STR_CHINA "\u7B80\u4F53\u4E2D\u6587"
#define TEST_UNICODE_STR_KOREA "\uD06C\uB85C\uC2A4 \uD50C\uB7AB\uD3FC\uC73C\uB85C"
#define TEST_UNICODE_STR_ISRAEL "\u05DE\u05D3\u05D5\u05E8\u05D9\u05DD \u05DE\u05D1\u05D5\u05E7\u05E9\u05D9\u05DD"
#define TEST_UNICODE_STR_EGYPT "\u0623\u0641\u0636\u0644 \u0627\u0644\u0628\u062D\u0648\u062B"
#define TEST_UNICODE_STR_GREECE "\u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u03AF\u03B6\u03C9 \u1F00\u03C0\u1F78"
#define TEST_UNICODE_STR_RUSSIA "\u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E"
#define TEST_UNICODE_STR_THAILAND "\u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A"
#define TEST_UNICODE_STR_FRANCE "fran\u00E7ais langue \u00E9trang\u00E8re"
#define TEST_UNICODE_STR_SPAIN "ma\u00F1ana ol\u00E9"
#define TEST_UNICODE_STR_MATHMATICS "\u222E E\u22C5da = Q, n \u2192 \u221E, \u2211 f(i) = \u220F g(i)"
#define TEST_UNICODE_STR_EMOJI "\U0001F363 \u2716 \U0001F37A" // sushi x beer mug
#define CONCAT(prefix, strl) prefix ## strl
#define CPP_U8_LITERAL(strl) strl
#define CPP_U16_LITERAL(strl) CONCAT(u, strl)
#define CPP_U32_LITERAL(strl) CONCAT(U, strl)
#define CPP_WSTR_LITERAL(strl) CONCAT(L, strl)
static std::vector<std::string> c_UTF8TestStrTable {
CPP_U8_LITERAL(TEST_UNICODE_STR_JAPAN),
CPP_U8_LITERAL(TEST_UNICODE_STR_CHINA),
CPP_U8_LITERAL(TEST_UNICODE_STR_KOREA),
CPP_U8_LITERAL(TEST_UNICODE_STR_ISRAEL),
CPP_U8_LITERAL(TEST_UNICODE_STR_EGYPT),
CPP_U8_LITERAL(TEST_UNICODE_STR_GREECE),
CPP_U8_LITERAL(TEST_UNICODE_STR_RUSSIA),
CPP_U8_LITERAL(TEST_UNICODE_STR_THAILAND),
CPP_U8_LITERAL(TEST_UNICODE_STR_FRANCE),
CPP_U8_LITERAL(TEST_UNICODE_STR_SPAIN),
CPP_U8_LITERAL(TEST_UNICODE_STR_MATHMATICS),
CPP_U8_LITERAL(TEST_UNICODE_STR_EMOJI),
};
static std::vector<std::wstring> c_WStrTestStrTable {
CPP_WSTR_LITERAL(TEST_UNICODE_STR_JAPAN),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_CHINA),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_KOREA),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_ISRAEL),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EGYPT),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_GREECE),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_RUSSIA),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_THAILAND),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_FRANCE),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_SPAIN),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_MATHMATICS),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EMOJI),
};
static std::vector<std::u16string> c_UTF16TestStrTable {
CPP_U16_LITERAL(TEST_UNICODE_STR_JAPAN),
CPP_U16_LITERAL(TEST_UNICODE_STR_CHINA),
CPP_U16_LITERAL(TEST_UNICODE_STR_KOREA),
CPP_U16_LITERAL(TEST_UNICODE_STR_ISRAEL),
CPP_U16_LITERAL(TEST_UNICODE_STR_EGYPT),
CPP_U16_LITERAL(TEST_UNICODE_STR_GREECE),
CPP_U16_LITERAL(TEST_UNICODE_STR_RUSSIA),
CPP_U16_LITERAL(TEST_UNICODE_STR_THAILAND),
CPP_U16_LITERAL(TEST_UNICODE_STR_FRANCE),
CPP_U16_LITERAL(TEST_UNICODE_STR_SPAIN),
CPP_U16_LITERAL(TEST_UNICODE_STR_MATHMATICS),
CPP_U16_LITERAL(TEST_UNICODE_STR_EMOJI),
};
static std::vector<std::u32string> c_UTF32TestStrTable {
CPP_U32_LITERAL(TEST_UNICODE_STR_JAPAN),
CPP_U32_LITERAL(TEST_UNICODE_STR_CHINA),
CPP_U32_LITERAL(TEST_UNICODE_STR_KOREA),
CPP_U32_LITERAL(TEST_UNICODE_STR_ISRAEL),
CPP_U32_LITERAL(TEST_UNICODE_STR_EGYPT),
CPP_U32_LITERAL(TEST_UNICODE_STR_GREECE),
CPP_U32_LITERAL(TEST_UNICODE_STR_RUSSIA),
CPP_U32_LITERAL(TEST_UNICODE_STR_THAILAND),
CPP_U32_LITERAL(TEST_UNICODE_STR_FRANCE),
CPP_U32_LITERAL(TEST_UNICODE_STR_SPAIN),
CPP_U32_LITERAL(TEST_UNICODE_STR_MATHMATICS),
CPP_U32_LITERAL(TEST_UNICODE_STR_EMOJI),
};
#undef CPP_WSTR_LITERAL
#undef CPP_U32_LITERAL
#undef CPP_U16_LITERAL
#undef CPP_U8_LITERAL
#undef CONCAT
#pragma endregion
static void Assert(bool condition, const char* description) { static void Assert(bool condition, const char* description) {
if (condition) { if (condition) {
Console::WriteLine(YYCC_COLOR_LIGHT_GREEN("OK: %s"), description); Console::FormatLine(YYCC_COLOR_LIGHT_GREEN("OK: %s"), description);
} else { } else {
Console::WriteLine(YYCC_COLOR_LIGHT_RED("Failed: %s\n"), description); Console::FormatLine(YYCC_COLOR_LIGHT_RED("Failed: %s\n"), description);
std::abort(); std::abort();
} }
} }
@ -34,34 +122,60 @@ namespace YYCCTestbench {
#undef TEST_MACRO #undef TEST_MACRO
// UTF8 Output Test // UTF8 Output Test
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
Console::WriteLine("UTF8 Output Test:"); Console::WriteLine("UTF8 Output Test:");
static std::vector<const char*> c_TestStrings { for (const auto& strl : c_UTF8TestStrTable) {
"\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8", // JAPAN Console::FormatLine("\t%s", strl.c_str());
"\u7B80\u4F53\u4E2D\u6587", // CHINA
"\uD06C\uB85C\uC2A4 \uD50C\uB7AB\uD3FC\uC73C\uB85C", // KOREA
"\u05DE\u05D3\u05D5\u05E8\u05D9\u05DD \u05DE\u05D1\u05D5\u05E7\u05E9\u05D9\u05DD", // ISRAEL
"\u0623\u0641\u0636\u0644 \u0627\u0644\u0628\u062D\u0648\u062B", // EGYPT
"\u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u03AF\u03B6\u03C9 \u1F00\u03C0\u1F78", // GREECE
"\u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E", // RUSSIA
"\u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A", // THAILAND
"fran\u00E7ais langue \u00E9trang\u00E8re", // FRANCE
"ma\u00F1ana ol\u00E9", // SPAIN
"\u222E E\u22C5da = Q, n \u2192 \u221E, \u2211 f(i) = \u220F g(i)", // MATHMATICS
"\xF0\x9F\x8D\xA3 \xE2\x9C\x96 \xF0\x9F\x8D\xBA", // EMOJI
};
for (const auto* ptr : c_TestStrings) {
Console::WriteLine("\t%s", ptr);
} }
// UTF8 Input Test // UTF8 Input Test
Console::WriteLine("UTF8 Input Test:"); Console::WriteLine("UTF8 Input Test:");
for (const auto* ptr : c_TestStrings) { for (const auto& strl : c_UTF8TestStrTable) {
Console::WriteLine("\tPlease type: %s", ptr); Console::FormatLine("\tPlease type: %s", strl.c_str());
Console::Write("\t> "); Console::Write("\t> ");
std::string gotten(Console::ReadLine()); std::string gotten(Console::ReadLine());
Assert(gotten == ptr, YYCC::StringHelper::Printf("Got: %s", gotten.c_str()).c_str()); Assert(gotten == strl, YYCC::StringHelper::Printf("Got: %s", gotten.c_str()).c_str());
}
}
static void EncodingTestbench() {
// get test tuple size
size_t count = c_UTF8TestStrTable.size();
// check the convertion between given string
for (size_t i = 0u; i < count; ++i) {
// get item
const auto& u8str = c_UTF8TestStrTable[i];
const auto& u16str = c_UTF16TestStrTable[i];
const auto& u32str = c_UTF32TestStrTable[i];
// create cache variables
std::string u8cache;
std::u16string u16cache;
std::u32string u32cache;
// do convertion check
Assert(YYCC::EncodingHelper::UTF8ToUTF16(u8str.c_str(), u16cache) && u16cache == u16str, "YYCC::EncodingHelper::UTF8ToUTF16");
Assert(YYCC::EncodingHelper::UTF8ToUTF32(u8str.c_str(), u32cache) && u32cache == u32str, "YYCC::EncodingHelper::UTF8ToUTF32");
Assert(YYCC::EncodingHelper::UTF16ToUTF8(u16str.c_str(), u8cache) && u8cache == u8str, "YYCC::EncodingHelper::UTF16ToUTF8");
Assert(YYCC::EncodingHelper::UTF32ToUTF8(u32str.c_str(), u8cache) && u8cache == u8str, "YYCC::EncodingHelper::UTF32ToUTF8");
}
// check wstring convertion on windows
for (size_t i = 0u; i < count; ++i) {
// get item
const auto& u8str = c_UTF8TestStrTable[i];
const auto& wstr = c_WStrTestStrTable[i];
// create cache variables
std::string u8cache;
std::wstring wcache;
// do convertion check
Assert(YYCC::EncodingHelper::UTF8ToWchar(u8str.c_str(), wcache) && wcache == wstr, "YYCC::EncodingHelper::UTF8ToWchar");
Assert(YYCC::EncodingHelper::WcharToUTF8(wstr.c_str(), u8cache) && u8cache == u8str, "YYCC::EncodingHelper::WcharToUTF8");
} }
} }
@ -191,42 +305,85 @@ namespace YYCCTestbench {
YYCC::DialogHelper::FileDialog params; YYCC::DialogHelper::FileDialog params;
auto& filters = params.ConfigreFileTypes(); auto& filters = params.ConfigreFileTypes();
filters.Add("Microsoft Word (*.docx; *.doc)", {"*.docx", "*.doc"}); filters.Add("Microsoft Word (*.docx; *.doc)", { "*.docx", "*.doc" });
filters.Add("Microsoft Excel (*.xlsx; *.xls)", {"*.xlsx", "*.xls"}); filters.Add("Microsoft Excel (*.xlsx; *.xls)", { "*.xlsx", "*.xls" });
filters.Add("Microsoft PowerPoint (*.pptx; *.ppt)", {"*.pptx", "*.ppt"}); filters.Add("Microsoft PowerPoint (*.pptx; *.ppt)", { "*.pptx", "*.ppt" });
filters.Add("Text File (*.txt)", {"*.txt"}); filters.Add("Text File (*.txt)", { "*.txt" });
filters.Add("All Files (*.*)", {"*.*"}); filters.Add("All Files (*.*)", { "*.*" });
params.SetDefaultFileTypeIndex(0u); params.SetDefaultFileTypeIndex(0u);
if (YYCC::DialogHelper::OpenFileDialog(params, ret)) { if (YYCC::DialogHelper::OpenFileDialog(params, ret)) {
Console::WriteLine("Open File: %s", ret.c_str()); Console::FormatLine("Open File: %s", ret.c_str());
} }
if (YYCC::DialogHelper::OpenMultipleFileDialog(params, rets)) { if (YYCC::DialogHelper::OpenMultipleFileDialog(params, rets)) {
Console::WriteLine("Open Multiple Files:"); Console::WriteLine("Open Multiple Files:");
for (const auto& item : rets) { for (const auto& item : rets) {
Console::WriteLine("\t%s", item.c_str()); Console::FormatLine("\t%s", item.c_str());
} }
} }
if (YYCC::DialogHelper::SaveFileDialog(params, ret)) { if (YYCC::DialogHelper::SaveFileDialog(params, ret)) {
Console::WriteLine("Save File: %s", ret.c_str()); Console::FormatLine("Save File: %s", ret.c_str());
} }
params.Clear(); params.Clear();
if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) { if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) {
Console::WriteLine("Open Folder: %s", ret.c_str()); Console::FormatLine("Open Folder: %s", ret.c_str());
} }
} }
static void ExceptionTestbench() {
YYCC::ExceptionHelper::Register();
// Perform a div zero exception.
int i = 1, j = 0;
int k = i / j;
YYCC::ExceptionHelper::Unregister();
}
static void WinFctTestbench() { static void WinFctTestbench() {
Console::WriteLine("Current Module HANDLE: 0x%016" PRIXPTR, YYCC::WinFctHelper::GetCurrentModule()); Console::FormatLine("Current Module HANDLE: 0x%" PRI_XPTR_LEFT_PADDING 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()); std::string test_temp;
Assert(YYCC::WinFctHelper::GetTempDirectory(test_temp), "YYCC::WinFctHelper::GetTempDirectory");
Console::FormatLine("Temp Directory: %s", test_temp.c_str());
std::string test_module_name;
Assert(YYCC::WinFctHelper::GetModuleFileName(YYCC::WinFctHelper::GetCurrentModule(), test_module_name), "YYCC::WinFctHelper::GetModuleFileName");
Console::FormatLine("Current Module File Name: %s", test_module_name.c_str());
std::string test_localappdata_path;
Assert(YYCC::WinFctHelper::GetLocalAppData(test_localappdata_path), "YYCC::WinFctHelper::GetLocalAppData");
Console::FormatLine("Local AppData: %s", test_localappdata_path.c_str());
}
static void FsPathPatch() {
std::filesystem::path test_path;
for (const auto& strl : c_UTF8TestStrTable) {
test_path /= YYCC::FsPathPatch::FromUTF8Path(strl.c_str());
}
std::string test_slashed_path(YYCC::FsPathPatch::ToUTF8Path(test_path));
#if YYCC_OS == YYCC_OS_WINDOWS
std::wstring wdecilmer(1u, std::filesystem::path::preferred_separator);
std::string decilmer(YYCC::EncodingHelper::WcharToUTF8(wdecilmer.c_str()));
#else
std::string decilmer(1u, std::filesystem::path::preferred_separator);
#endif
std::string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable, decilmer.c_str()));
Assert(test_slashed_path == test_joined_path, "YYCC::FsPathPatch");
} }
} }
int main(int argc, char** args) { int main(int argc, char** args) {
//YYCCTestbench::ConsoleTestbench(); //YYCCTestbench::ConsoleTestbench();
YYCCTestbench::EncodingTestbench();
//YYCCTestbench::StringTestbench(); //YYCCTestbench::StringTestbench();
//YYCCTestbench::ParserTestbench(); //YYCCTestbench::ParserTestbench();
//YYCCTestbench::DialogTestbench(); //YYCCTestbench::DialogTestbench();
YYCCTestbench::WinFctTestbench(); //YYCCTestbench::ExceptionTestbench();
//YYCCTestbench::WinFctTestbench();
//YYCCTestbench::FsPathPatch();
} }