Compare commits
10 Commits
015ff874f8
...
f153f9bc22
Author | SHA1 | Date | |
---|---|---|---|
f153f9bc22 | |||
77b6f439f7 | |||
0319be7e19 | |||
e20c03a5f1 | |||
8465d80a54 | |||
5481898ad9 | |||
e7b13768ec | |||
d0a8733379 | |||
c32806ea03 | |||
0ef1939e6e |
@ -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
|
||||||
|
@ -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
40
src/COMHelper.cpp
Normal 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
69
src/COMHelper.hpp
Normal 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
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
41
src/FsPathPatch.cpp
Normal 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
41
src/FsPathPatch.hpp
Normal 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);
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -15,5 +15,6 @@
|
|||||||
#undef GetClassName
|
#undef GetClassName
|
||||||
#undef LoadImage
|
#undef LoadImage
|
||||||
#undef GetTempPath
|
#undef GetTempPath
|
||||||
|
#undef GetModuleFileName
|
||||||
|
|
||||||
#endif
|
#endif
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -198,35 +312,78 @@ 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::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();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user