Compare commits

..

No commits in common. "f153f9bc222977bbed3cadd13094838e0498945f" and "015ff874f8616af7fc3f648caae9ce8efe5811e2" have entirely different histories.

23 changed files with 362 additions and 1076 deletions

View File

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

View File

@ -3,39 +3,32 @@ 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
COMHelper.hpp ${CMAKE_CURRENT_LIST_DIR}/ConsoleHelper.hpp
ConsoleHelper.hpp ${CMAKE_CURRENT_LIST_DIR}/DialogHelper.hpp
DialogHelper.hpp ${CMAKE_CURRENT_LIST_DIR}/EncodingHelper.hpp
EncodingHelper.hpp ${CMAKE_CURRENT_LIST_DIR}/ExceptionHelper.hpp
ExceptionHelper.hpp ${CMAKE_CURRENT_LIST_DIR}/IOHelper.hpp
FsPathPatch.hpp ${CMAKE_CURRENT_LIST_DIR}/ParserHelper.hpp
IOHelper.hpp ${CMAKE_CURRENT_LIST_DIR}/StringHelper.hpp
ParserHelper.hpp ${CMAKE_CURRENT_LIST_DIR}/WinFctHelper.hpp
StringHelper.hpp
WinFctHelper.hpp
# Windows including guard pair # Windows including guard pair
WinImportPrefix.hpp ${CMAKE_CURRENT_LIST_DIR}/WinImportPrefix.hpp
WinImportSuffix.hpp ${CMAKE_CURRENT_LIST_DIR}/WinImportSuffix.hpp
# Misc # Misc
YYCCInternal.hpp ${CMAKE_CURRENT_LIST_DIR}/YYCCInternal.hpp
YYCCommonplace.hpp ${CMAKE_CURRENT_LIST_DIR}/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
@ -43,11 +36,6 @@ 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
@ -75,5 +63,23 @@ 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
) )

View File

@ -1,40 +0,0 @@
#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

View File

@ -1,69 +0,0 @@
#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, bool to_stderr) { static void WinConsoleWrite(const std::string& strl) {
// Prepare some Win32 variables // Prepare some Win32 variables
// fetch stdout handle first // fetch stdout handle first
HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE); HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwConsoleMode; DWORD dwConsoleMode;
DWORD dwWrittenNumberOfChars; DWORD dwWrittenNumberOfChars;
@ -163,10 +163,9 @@ namespace YYCC::ConsoleHelper {
bool EnableColorfulConsole() { bool EnableColorfulConsole() {
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
bool ret = true; if (!RawEnableColorfulConsole(stdout)) return false;
ret &= RawEnableColorfulConsole(stdout); if (!RawEnableColorfulConsole(stderr)) return false;
ret &= RawEnableColorfulConsole(stderr); return true;
return ret;
#else #else
@ -199,80 +198,35 @@ namespace YYCC::ConsoleHelper {
#endif #endif
} }
template<bool bNeedFmt, bool bIsErr, bool bHasEOL> static void RawWrite(const std::string& strl) {
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, bIsErr); WinConsoleWrite(strl);
#else #else
// in linux, directly use C function to write. // in linux, directly use C function to write.
std::fputs(strl.c_str(), to_stderr ? stderr : stdout); std::fputs(strl.c_str(), stdout);
#endif #endif
} }
void Format(const char* u8_fmt, ...) { void Write(const char* u8_fmt, ...) {
va_list argptr; va_list argptr;
va_start(argptr, u8_fmt); va_start(argptr, u8_fmt);
RawWrite<true, false, false>(u8_fmt, argptr); RawWrite(YYCC::StringHelper::VPrintf(u8_fmt, argptr));
va_end(argptr); va_end(argptr);
} }
void FormatLine(const char* u8_fmt, ...) { void WriteLine(const char* u8_fmt, ...) {
va_list argptr; va_list argptr;
va_start(argptr, u8_fmt); va_start(argptr, u8_fmt);
RawWrite<true, false, true>(u8_fmt, argptr); std::string cache(YYCC::StringHelper::VPrintf(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,64 +4,6 @@
#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"
@ -114,62 +56,54 @@ namespace YYCC::ConsoleHelper {
/** /**
* @brief Universal console read function * @brief Universal console read function
* @return * @details This function is more like C# Console.ReadLine().
* 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 with format feature. * @brief Universal console write function
* @param[in] u8_fmt The format string. * @details This function is more like C# Console.Write().
* @param[in] ... The arguments to be formatted. * It write user given UTF8 string into console.
*
* 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 Format(const char* u8_fmt, ...); void Write(const char* u8_fmt, ...);
/** /**
* @brief Universal console write function with format and auto EOL feature. * @brief Universal console write function with automatic EOL
* @param[in] u8_fmt The format string. * @details This function is same as Write(const char*, ...),
* @param[in] ... The arguments to be formatted. * but it will automatically add EOL in output to break line.
* 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 FormatLine(const char* u8_fmt, ...); void WriteLine(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,6 +6,43 @@
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) {
@ -143,12 +180,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.
*/ */
static bool ExtractDisplayName(IShellItem* item, std::string& ret) { 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;
COMHelper::SmartLPWSTR display_name(_name); SmartLPWSTR display_name(_name);
// convert result // convert result
if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret)) if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret))
@ -168,7 +205,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>
static bool CommonFileDialog(const FileDialog& params, std::vector<std::string>& ret) { 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;
@ -198,7 +235,7 @@ namespace YYCC::DialogHelper {
); );
if (FAILED(hr)) return false; if (FAILED(hr)) return false;
// create memory-safe dialog pointer // create memory-safe dialog pointer
COMHelper::SmartIFileDialog pfd(_pfd); 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.
@ -283,7 +320,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;
COMHelper::SmartIShellItem result_item(_item); SmartIShellItem result_item(_item);
// extract display name // extract display name
std::string result_name; std::string result_name;
@ -301,13 +338,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;
COMHelper::SmartIFileOpenDialog pfod(_pfod); 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;
COMHelper::SmartIShellItemArray result_items(_items); SmartIShellItemArray result_items(_items);
// analyze file entries // analyze file entries
// get array count first // get array count first
@ -320,7 +357,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;
COMHelper::SmartIShellItem result_item(_item); 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,6 +14,44 @@
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.
@ -131,7 +169,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;
COMHelper::SmartIShellItem m_WinInitDirectory; SmartIShellItem m_WinInitDirectory;
void Clear() { void Clear() {
m_WinOwner = nullptr; m_WinOwner = nullptr;

View File

@ -1,11 +1,8 @@
#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;
@ -68,137 +65,6 @@ namespace YYCC::EncodingHelper {
return ret; return ret;
} }
}
#endif #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;
}
}

View File

@ -1,57 +1,15 @@
#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);
@ -65,16 +23,6 @@ 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
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);
} }
#endif

View File

@ -1,16 +1,11 @@
#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>
@ -54,11 +49,6 @@ 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:
@ -106,50 +96,24 @@ 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);
ConsoleHelper::ErrWriteLine(YYCC::StringHelper::VPrintf(fmt, arg2).c_str()); std::vfprintf(stdout, fmt, arg2);
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);
std::fputs("\n", fs); // write to stdout
} 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) {
@ -162,8 +126,8 @@ namespace YYCC::ExceptionHelper {
// init symbol // init symbol
if (!SymInitialize(process, 0, TRUE)) { if (!SymInitialize(process, 0, TRUE)) {
// fail to init. return // fail to load. return
UExceptionErrLogWriteLine(fs, "Fail to initialize symbol handle for process!"); UExceptionPrint(fs, "Lost symbol file!\n");
return; return;
} }
@ -208,6 +172,9 @@ 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)) {
@ -215,22 +182,19 @@ namespace YYCC::ExceptionHelper {
// depth breaker // depth breaker
--maxdepth; --maxdepth;
if (maxdepth < 0) { if (maxdepth < 0) {
UExceptionErrLogWriteLine(fs, "..."); // indicate there are some frames not listed UExceptionPrint(fs, "...\n"); // indicate there are some frames not listed
break; break;
} }
// get module name // get module name
const char* module_name = "<unknown module>"; DWORD64 module_base = SymGetModuleBase64(process, frame.AddrPC.Offset);
std::string module_name_raw; const char* module_name = "[unknown module]";
DWORD64 module_base; if (module_base && GetModuleFileNameA((HINSTANCE)module_base, module_name_raw, MAX_PATH)) {
if (module_base = SymGetModuleBase64(process, frame.AddrPC.Offset)) { module_name = module_name_raw;
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 = "<unknown source>"; const char* source_file = "[unknow_source_file]";
DWORD64 source_file_line = 0; DWORD64 source_file_line = 0;
DWORD dwDisplacement; DWORD dwDisplacement;
IMAGEHLP_LINE64 winline; IMAGEHLP_LINE64 winline;
@ -241,10 +205,9 @@ namespace YYCC::ExceptionHelper {
} }
// write to file // write to file
UExceptionErrLogFormatLine(fs, "0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "[%s+0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "]\t%s#L%" PRIu64, UExceptionFormat(fs, "0x%016llx(rel: 0x%016llx)[%s]\t%s#%llu\n",
frame.AddrPC.Offset, // memory adress frame.AddrPC.Offset, frame.AddrPC.Offset - module_base, module_name,
module_name, frame.AddrPC.Offset - module_base, // module name + relative address source_file, source_file_line
source_file, source_file_line // source file + source line
); );
} }
@ -255,51 +218,9 @@ namespace YYCC::ExceptionHelper {
SymCleanup(process); SymCleanup(process);
} }
static void UExceptionErrorLog(const std::string& u8_filename, LPEXCEPTION_POINTERS info) { static void UExceptionCoreDump(LPCWSTR 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.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); HANDLE hFile = CreateFileW(filename, 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();
@ -315,84 +236,68 @@ 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) goto end_proc; if (g_IsProcessing) {
goto end_proc;
}
// start process // start process
g_IsProcessing = true; g_IsProcessing = true;
// core implementation
{ {
// fetch error report path first // get main folder first
std::string log_path, coredump_path; std::filesystem::path ironpad_path;
if (!UExceptionFetchRecordPath(log_path, coredump_path)) { WCHAR module_path[MAX_PATH];
// fail to fetch path, clear them. std::memset(module_path, 0, sizeof(module_path));
// we still can handle crash without them if (GetModuleFileNameW(WinFctHelper::GetCurrentModule(), module_path, MAX_PATH) == 0) {
log_path.clear(); goto failed;
coredump_path.clear(); }
// and tell user we can not output file ironpad_path = module_path;
ConsoleHelper::ErrWriteLine("Crash occurs, but we can not create crash log and coredump!"); ironpad_path = ironpad_path.parent_path();
} else {
// okey. output file path to tell user the path where you can find. // create 2 filename
ConsoleHelper::ErrFormatLine("Crash Log: %s", log_path.c_str()); auto logfilename = ironpad_path / "IronPad.log";
ConsoleHelper::ErrFormatLine("Crash Coredump: %s", coredump_path.c_str()); auto dmpfilename = ironpad_path / "IronPad.dmp";
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;
} }
// write crash log // record exception type first
UExceptionErrorLog(log_path, info); PEXCEPTION_RECORD rec = info->ExceptionRecord;
// write crash coredump fprintf(fs, "Unhandled exception occured at 0x%08p: %s (%lu).\n",
UExceptionCoreDump(coredump_path, info); 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";
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,40 +2,32 @@
#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();

View File

@ -1,41 +0,0 @@
#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
}
}

View File

@ -1,41 +0,0 @@
#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,22 +1,25 @@
#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 {
FILE* UTF8FOpen(const char* u8_filepath, const char* u8_mode) { bool futf8(FILE* fs) {
#if YYCC_OS == YYCC_OS_WINDOWS // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setmode?view=msvc-170
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))
@ -26,11 +29,8 @@ 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,37 +1,23 @@
#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 The left-padding zero format string of HEX-printed pointer type. * @brief Set given FILE* as UTF8 mode.
* @details * @param fs[in] The FILE* need to be set as UTF8 mode.
* When printing a pointer with HEX style, we always hope it can be left-padded with some zero for easy reading. * @return True if success, otherwise false.
* In different architecture, the size of this padding is differnet too so we create this macro. * @warning Once this function success, you can not use any narrow char function on this FILE*,
* * such as std::fputs, std::fprintf and etc. You only can use wide char function on it,
* In 32-bit environment, it will be "08" meaning left pad zero until 8 number position. * or use the functions provided in this namespace by providing UTF8 string as their argument.
* In 64-bit environment, it will be "016" meaning left pad zero until 16 number position.
*/ */
#define PRI_XPTR_LEFT_PADDING "016" bool futf8(FILE* fs);
#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

0
src/ParserHelper.cpp Normal file
View File

View File

@ -2,7 +2,6 @@
#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 {
@ -17,7 +16,7 @@ namespace YYCC::WinFctHelper {
return hModule; return hModule;
} }
bool GetTempDirectory(std::string& ret) { std::string GetTempDirectory() {
// 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;
@ -26,7 +25,9 @@ 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
return false; expected_size = 0;
// and break while
break;
} }
if (expected_size > static_cast<DWORD>(wpath.size())) { if (expected_size > static_cast<DWORD>(wpath.size())) {
@ -41,18 +42,19 @@ 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(), ret); return YYCC::EncodingHelper::WcharToUTF8(wpath.c_str());
} }
bool GetModuleFileName(HINSTANCE hModule, std::string& ret) { std::string GetModuleName(HINSTANCE hModule) {
// 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 // failed, return empty string
return false; copied_size = 0;
break;
} }
// check insufficient buffer // check insufficient buffer
@ -68,18 +70,7 @@ 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(), ret); return YYCC::EncodingHelper::WcharToUTF8(wpath.c_str());
}
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,12 +8,6 @@
#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 {
/** /**
@ -21,7 +15,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.
@ -32,32 +26,18 @@ namespace YYCC::WinFctHelper {
/** /**
* @brief Get path to Windows temp folder. * @brief Get path to Windows temp folder.
* @param[out] ret * @return UTF8 encoded path to Windows temp folder. Empty string if failed.
* The variable receiving UTF8 encoded path to Windows temp folder.
* @return True if success, otherwise false.
*/ */
bool GetTempDirectory(std::string& ret); std::string GetTempDirectory();
/** /**
* @brief Get the file name of given module HANDLE * @brief Get the file name of given module HANDLE
* @param[in] hModule * @param hModule[in]
* 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 \c GetModuleFileName. * It is same as the HANDLE parameter of GetModuleFileName.
* @param[out] ret * @return UTF8 encoded file name of given module. Empty string if failed.
* The variable receiving UTF8 encoded file name of given module.
* @return True if success, otherwise false.
*/ */
bool GetModuleFileName(HINSTANCE hModule, std::string& ret); std::string GetModuleName(HINSTANCE hModule);
/**
* @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,6 +15,5 @@
#undef GetClassName #undef GetClassName
#undef LoadImage #undef LoadImage
#undef GetTempPath #undef GetTempPath
#undef GetModuleFileName
#endif #endif

View File

@ -5,10 +5,8 @@
#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
main.cpp ${CMAKE_CURRENT_LIST_DIR}/main.cpp
) )
# Add YYCC as its library # Add YYCC as its library
target_include_directories(YYCCTestbench target_include_directories(YYCCTestbench

View File

@ -5,99 +5,11 @@ 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::FormatLine(YYCC_COLOR_LIGHT_GREEN("OK: %s"), description); Console::WriteLine(YYCC_COLOR_LIGHT_GREEN("OK: %s"), description);
} else { } else {
Console::FormatLine(YYCC_COLOR_LIGHT_RED("Failed: %s\n"), description); Console::WriteLine(YYCC_COLOR_LIGHT_RED("Failed: %s\n"), description);
std::abort(); std::abort();
} }
} }
@ -122,60 +34,34 @@ 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:");
for (const auto& strl : c_UTF8TestStrTable) { static std::vector<const char*> c_TestStrings {
Console::FormatLine("\t%s", strl.c_str()); "\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8", // JAPAN
"\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& strl : c_UTF8TestStrTable) { for (const auto* ptr : c_TestStrings) {
Console::FormatLine("\tPlease type: %s", strl.c_str()); Console::WriteLine("\tPlease type: %s", ptr);
Console::Write("\t> "); Console::Write("\t> ");
std::string gotten(Console::ReadLine()); std::string gotten(Console::ReadLine());
Assert(gotten == strl, YYCC::StringHelper::Printf("Got: %s", gotten.c_str()).c_str()); Assert(gotten == ptr, 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");
} }
} }
@ -312,78 +198,35 @@ namespace YYCCTestbench {
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::FormatLine("Open File: %s", ret.c_str()); Console::WriteLine("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::FormatLine("\t%s", item.c_str()); Console::WriteLine("\t%s", item.c_str());
} }
} }
if (YYCC::DialogHelper::SaveFileDialog(params, ret)) { if (YYCC::DialogHelper::SaveFileDialog(params, ret)) {
Console::FormatLine("Save File: %s", ret.c_str()); Console::WriteLine("Save File: %s", ret.c_str());
} }
params.Clear(); params.Clear();
if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) { if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) {
Console::FormatLine("Open Folder: %s", ret.c_str()); Console::WriteLine("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::FormatLine("Current Module HANDLE: 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR, YYCC::WinFctHelper::GetCurrentModule()); Console::WriteLine("Current Module HANDLE: 0x%016" PRIXPTR, YYCC::WinFctHelper::GetCurrentModule());
Console::WriteLine("Temp Directory: %s", YYCC::WinFctHelper::GetTempDirectory().c_str());
std::string test_temp; Console::WriteLine("Current Module Name: %s", YYCC::WinFctHelper::GetModuleName(YYCC::WinFctHelper::GetCurrentModule()).c_str());
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::ExceptionTestbench(); YYCCTestbench::WinFctTestbench();
//YYCCTestbench::WinFctTestbench();
//YYCCTestbench::FsPathPatch();
} }