Compare commits

...

7 Commits

Author SHA1 Message Date
4f1e2447d0 fix: make exception handler be singleton in same process.
- use CreateMutexW to make sure exception handler is singleton in the same process.
	- Because one process may load multiple DLLs using YYCC.
	- According to this, it will produce N times error log. N is the count of DLLs using YYCC exception handler.
- refactor exception handler. use a class instance to manage all global variables and add a std::mutex to let module be thread safe.
- change the way of check in console input testbench.
2024-07-07 17:29:26 +08:00
3075ec583d fix: in config manager throw exception when facing duplicated setting name instead of slient skip 2024-07-07 16:08:42 +08:00
2e28dd4c48 chore: update build script.
- update build script to split documentation generation.
- bump up version from 1.0.0 to 1.1.0
2024-07-06 15:28:30 +08:00
a1699f13db doc: update documentation
- rename Constrain to Constraint in code and documentation.
- remove massive annotation of encoding helper because the documentation is enough.
- fix doxygen character shift warnings.
2024-07-05 22:25:14 +08:00
65b81f5cfa refactor: rename Native String to Ordinary String.
- rename Native to Ordinary in code and documentation.
- fulfill some documentations.
2024-07-05 10:36:24 +08:00
1c5a85bbb2 doc: update documentation.
- add documentation for platform checker.
- finish documentation of encoding helper.
2024-07-05 09:18:31 +08:00
1f04e23092 doc: update documentation for encoding helper 2024-07-04 20:26:59 +08:00
17 changed files with 438 additions and 162 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.23)
project(YYCC
VERSION 1.0.0
VERSION 1.1.0
LANGUAGES CXX
)

View File

@ -4,8 +4,141 @@
YYCC::EncodingHelper namespace include all encoding related functions:
\li The convertion between native string and UTF8 string which has been introduced in chapter \ref library_encoding.
\li The convertion between ordinary string and UTF8 string which has been introduced in chapter \ref library_encoding.
\li Windows specific convertion between \c WCHAR, UTF8 string and string encoded by other encoding.
\li The convertion among UTF8, UTF16 and UTF32.
\section encoding_helper__ordinary_utf8_conv Ordinary & UTF8 Convertion
These convertion functions have been introduced in previous page.
See \ref library_encoding for more infomation.
YYCC supports following convertions:
\li YYCC::EncodingHelper::ToUTF8: Convert ordinary string to UTF8 string.
\li YYCC::EncodingHelper::ToUTF8View: Same as ToUTF8, but return string view instead.
\li YYCC::EncodingHelper::ToOrdinary: Convert UTF8 string to ordinary string.
\li YYCC::EncodingHelper::ToOrdinaryView: Same as ToOrdinary, but return string view instead.
\section encoding_helper__win_conv Windows Specific Convertion
During Windows programming, the convertion between Microsoft specified \c wchar_t and \c char is an essential operation.
Because Windows has 2 different function system, the functions ended with A and the functions ended with W.
(Microsoft specified \c wchar_t is \c 2 bytes long. It's different with Linux defined common 4 bytes long).
Thus YYCC provides these convertion functions in Windows to help programmer have better programming experience.
These functions are Windows specific, so they will be invisible in other platforms.
Please use them carefully (make sure that you are using them only in Windows environment).
YYCC supports following convertions:
\li YYCC::EncodingHelper::WcharToChar: Convert \c wchar_t string to code page specified string.
\li YYCC::EncodingHelper::CharToWchar: The reversed convertion of WcharToChar.
\li YYCC::EncodingHelper::CharToChar: Convert string between 2 different code pages. It's a shortcut of calling CharToWchar and WcharToChar successively.
\li YYCC::EncodingHelper::WcharToUTF8: Convert \c wchar_t string to UTF8 string.
\li YYCC::EncodingHelper::UTF8ToWchar: The reversed convertion of WcharToUTF8.
Code Page is a Windows concept.
If you don't understand it, please view corresponding Microsoft documentation.
\section encoding_helper__utf_conv UTF8 UTF16 UTF32 Convertion
The convertion between UTF8, UTF16 and UTF32 is not common but essential.
These convertions can be achieved by standard library functions and classes.
(they are actually done by standard library functions in our implementation)
But we provided functions are easy to use and have clear interface.
These functions are different with the functions introduced above.
They can be used in any platform, not confined in Windows platforms.
YYCC supports following convertions:
\li YYCC::EncodingHelper::UTF8ToUTF16: Convert UTF8 string to UTF16 string.
\li YYCC::EncodingHelper::UTF16ToUTF8: The reversed convertion of UTF8ToUTF16.
\li YYCC::EncodingHelper::UTF8ToUTF32: Convert UTF8 string to UTF32 string.
\li YYCC::EncodingHelper::UTF32ToUTF8: The reversed convertion of UTF8ToUTF32.
\section encoding_helper__overloads Function Overloads
Every encoding convertion functions (except the convertion between UTF8 and ordinary string) have 4 different overloads for different scenarios.
Take YYCC::EncodingHelper::WcharToChar for example.
There are following 4 overloads:
\code
bool WcharToChar(const std::wstring_view& src, std::string& dst, UINT code_page);
bool WcharToChar(const wchar_t* src, std::string& dst, UINT code_page);
std::string WcharToChar(const std::wstring_view& src, UINT code_page);
std::string WcharToChar(const wchar_t* src, UINT code_page);
\endcode
\subsection encoding_helper__overloads_destination Destination String
According to the return value, these 4 overload can be divided into 2 types.
The first type returns bool. The second type returns \c std::string instance.
For the first type, it always return bool to indicate whether the convertion is success.
Due to this, the function must require an argument for holding the result string.
So you can see the functions belonging to this type always require a reference to \c std::string in argument.
Oppositely, the second directly returns result by return value.
It doesn't care the success of convertion and will return empty string if convertion failed.
Programmer can more naturally use it because the retuen value itself is the result.
There is no need to declare a variable before calling convertion function for holding result.
All in all, the first type overload should be used in strict scope.
The success of convertion will massively affect the behavior of your following code.
For example, the convertion code is delivered to some system function and it should not be empty and etc.
The second type overload usually is used in lossen scenarios.
For exmaple, this overload usually is used in console output because it usually doesn't matter.
There is no risk even if the convertion failed (just output a blank string).
For the first type, please note that there is \b NO guarantee that the argument holding return value is not changed.
Even the convertion is failed, the argument holding return value may still be changed by function itself.
In this case, the type of result is \c std::string because this is function required.
In other functions, such as YYCC::EncodingHelper::WcharToUTF8, the type of result can be \c yycc_u8string or etc.
So please note the type of result is decided by convertion function itself, not only \c std::string.
\subsection encoding_helper__overloads__source Source String
According to the way providing source string,
these 4 overload also can be divided into 2 types.
The first type take a reference to constant \c std::wstring_view.
The second type take a pointer to constant wchar_t.
For first type, it will take the whole string for convertion, including \b embedded NUL terminal.
Please note we use string view as argument.
It is compatible with corresponding raw string pointer and string container.
So it is safe to directly pass \c std::wstring for this function.
For second type, it will assume that you passed argument is a NUL terminated string and send it for convertion.
The result is clear.
If you want to process string with \b embedded NUL terminal, please choose first type overload.
Otherwise the second type overload is enough.
Same as destination string, the type of source is also decided by the convertion function itself.
For exmaple, the type of source in YYCC::EncodingHelper::UTF8ToWchar is \c yycc_u8string_view and \c yycc_char8_t,
not \c std::wstring and \c wchar_t.
\subsection encoding_helper__overloads__extra Extra Argument
There is an extra argument called \c code_page for YYCC::EncodingHelper::WcharToChar.
It indicates the code page of destination string,
because this function will convert \c wchar_t string to the string with specified code page encoding.
Some convertion functions have extra argument like this,
because they need more infomations to decide what they need to do.
Some convertion functions don't have extra argument.
For exmaple, the convertion between \c wchar_t string and UTF8 string.
Because both source string and destination string are concrete.
There is no need to provide any more infomations.
\subsection encoding_helper__overloads__conclusion Conclusion
Mixing 2 types of source string and 2 types of destination string,
we have 4 different overload as we illustrated before.
Programmer can use them freely according to your requirements.
And don't forget to provide extra argument if function required.
*/

View File

@ -29,6 +29,8 @@
\li \subpage intro
\li \subpage platform_checker
\li \subpage library_encoding
\li \subpage encoding_helper

View File

@ -59,6 +59,23 @@ I notice standard library change UTF8 related functions frequently and its API a
For example, standard library brings \c std::codecvt_utf8 in C++ 11, deprecate it in C++ 17 and even remove it in C++ 26.
That's unacceptable! So I create my own UTF8 type to avoid the scenario that standard library remove \c char8_t in future.
\section library_encoding__concept Concepts
In following content, you may be face with 2 words: ordinary string and UTF8 string.
UTF8 string, as its name, is the string encoded with UTF8.
The char type of it must is \c yycc_char8_t.
(equivalent to \c char8_t after C++ 20.)
Ordinary string means the plain, native string.
The result of C++ string literal without any prefix \c "foo bar" is a rdinary string.
The char type of it is \c char.
Its encoding depends on compiler and environment.
(UTF8 in Linux, or system code page in Windows if UTF8 switch was not enabled in MSVC.)
For more infomation, please browse CppReference:
https://en.cppreference.com/w/cpp/language/string_literal
\section library_encoding__utf8_literal UTF8 Literal
String literal is a C++ concept.
@ -123,35 +140,35 @@ char* mutable_utf8 = const_cast<char*>(absolutely_is_utf8); // This is not safe.
yycc_char8_t* mutable_converted = YYCC::EncodingHelper::ToUTF8(mutable_utf8);
\endcode
YYCC::EncodingHelper::ToUTF8 has 2 overloads which can handle const and mutable stirng pointer convertion respectively.
YYCC::EncodingHelper::ToUTF8 has 2 overloads which can handle constant and mutable stirng pointer convertion respectively.
YYCC also has ability that convert YYCC UTF8 char type to native char type by YYCC::EncodingHelper::ToNative.
YYCC also has ability that convert YYCC UTF8 char type to ordinary char type by YYCC::EncodingHelper::ToOrdinary.
Here is an exmaple:
\code
const yycc_char8_t* yycc_utf8 = YYCC_U8("I am UTF8 string.");
const char* converted = YYCC::EncodingHelper::ToNative(yycc_utf8);
const char* converted = YYCC::EncodingHelper::ToOrdinary(yycc_utf8);
yycc_char8_t* mutable_yycc_utf8 = const_cast<char*>(yycc_utf8); // Not safe. Also just for example.
char* mutable_converted = YYCC::EncodingHelper::ToNative(mutable_yycc_utf8);
char* mutable_converted = YYCC::EncodingHelper::ToOrdinary(mutable_yycc_utf8);
\endcode
Same as YYCC::EncodingHelper::ToUTF8, YYCC::EncodingHelper::ToNative also has 2 overloads to handle const and mutable string pointer.
Same as YYCC::EncodingHelper::ToUTF8, YYCC::EncodingHelper::ToOrdinary also has 2 overloads to handle constant and mutable string pointer.
\section library_encoding__utf8_container UTF8 String Container
String container usually means the standard library string container, such as \c std::string, \c std::wstring, \c std::u32string and etc.
In many personal project, programmer may use \c std::string everywhere because \c std::u8string may not be presented when writing peoject.
How to do convertion between native string container and YYCC UTF8 string container?
How to do convertion between ordinary string container and YYCC UTF8 string container?
It is definitely illegal that directly do force convertion. Because they may have different class layout.
Calm down and I will tell you how to do correct convertion.
YYCC provides YYCC::EncodingHelper::ToUTF8 to convert native string container to YYCC UTF8 string container.
YYCC provides YYCC::EncodingHelper::ToUTF8 to convert ordinary string container to YYCC UTF8 string container.
There is an exmaple:
\code
std::string native_string("I am UTF8");
yycc_u8string yycc_string = YYCC::EncodingHelper::ToUTF8(native_string);
std::string ordinary_string("I am UTF8");
yycc_u8string yycc_string = YYCC::EncodingHelper::ToUTF8(ordinary_string);
auto result = YYCC::EncodingHelper::UTF8ToUTF32(yycc_string);
\endcode
@ -160,19 +177,19 @@ However, there is a implicit convertion from \c std::string to \c std::string_vi
so you can directly pass a \c std::string instance to it.
String view will reduce unnecessary memory copy.
If you just want to pass native string container to function, and this function accepts \c yycc_u8string_view as its argument,
If you just want to pass ordinary string container to function, and this function accepts \c yycc_u8string_view as its argument,
you can use alternative YYCC::EncodingHelper::ToUTF8View.
\code
std::string native_string("I am UTF8");
yycc_u8string_view yycc_string = YYCC::EncodingHelper::ToUTF8View(native_string);
std::string ordinary_string("I am UTF8");
yycc_u8string_view yycc_string = YYCC::EncodingHelper::ToUTF8View(ordinary_string);
auto result = YYCC::EncodingHelper::UTF8ToUTF32(yycc_string);
\endcode
Comparing with previous one, this example use less memory.
The reduced memory is the content of \c yycc_string because string view is a view, not the copy of original string.
Same as UTF8 string pointer, we also have YYCC::EncodingHelper::ToNative and YYCC::EncodingHelper::ToNativeView do correspondant reverse convertion.
Same as UTF8 string pointer, we also have YYCC::EncodingHelper::ToOrdinary and YYCC::EncodingHelper::ToOrdinaryView do correspondant reverse convertion.
Try to do your own research and figure out how to use them.
It's pretty easy.

View File

@ -0,0 +1,35 @@
/**
\page platform_checker Platform Checker
In many cross platform applications,
programmer usually write code adapted to different platforms in one source file
and enable them respectively by macros representing the target platform.
As a cross platform library,
YYCC also has this feature and you can utilize it if you don't have other ways to so the same things.
\section platform_checker__values Values
YYCC always define a macro called \c YYCC_OS to indicate the system of target platform.
In implementation, it will check following list from top to bottom to set matched value for it.
\li \c YYCC_OS_WINDOWS: Windows environment. It is done by checking whether environment define \c _WIN32 macro.
\li \c YYCC_OS_LINUX: In current implementation, this means target platform is \b NOT Windows.
\section platform_checker__usage Usage
Now you know any possible value of \c YYCC_OS.
The next step is how to use it to enable specified code in specific target platform.
We take Windows platform for example.
Assume \c blabla() function is Windows specific.
We have following example code:
\code
#if YYCC_OS == YYCC_OS_WINDOWS
blabla();
#endif
\endcode
It's enough and simple that use \c \#if to bracket the Windows specified code.
*/

View File

@ -36,7 +36,7 @@ This guard can solve following issues:
Programmer will not be affected by the automatical rename of \c GetObject, \c GetClassName and etc.
<UL>
<LI>These are all macros for Windows personal use to automatically redirect calling to A function and W function by compiling environment.</LI>
<LI>Guard \c #undef these annoy macros.</LI>
<LI>Guard \c \#undef these annoy macros.</LI>
</UL>
</LI>
<LI>
@ -56,13 +56,13 @@ Because this guard operate some Windows macros as we introduced above.
The headers depending on Windows may throw error if you put them outside of this pair.
Please note WinImportPrefix.hpp and WinImportSuffix.hpp can be included multiple times.
Because they do not have the proprocessor command like <I>#pragma once</I> or etc to make sure they only can be included once.
Because they do not have the proprocessor command like <I>\#pragma once</I> or etc to make sure they only can be included once.
That's by design. Because we actually may use this pair multiple times.
The only thing you should pledge is that you must make sure they are presented by pair.
This guard is Windows specific.
It does nothing if you accidently use it in other platforms such as Linux,
because the headers use \c #if to check environment out and will do nothing in non-Windows environment.
because the headers use \c \#if to check environment out and will do nothing in non-Windows environment.
However, we still highly recommend you use this pair with platform checker bracket like example does,
if your program need to be run on multiple platforms.

View File

@ -12,6 +12,7 @@ MKDIR bin
CD bin
MKDIR Win32
MKDIR x64
MKDIR documentation
MKDIR install
:: Build for Win32
@ -32,4 +33,11 @@ cmake --build . --config Release
cmake --install . --prefix=../install --config Release
CD ..
:: Build for documentation
CD documentation
cmake -DYYCC_BUILD_DOC=ON ../..
cmake --build . --config Release
:: cmake --install . --prefix=../install --config Release
CD ..
ECHO DONE

View File

@ -2,6 +2,7 @@
#include "EncodingHelper.hpp"
#include "IOHelper.hpp"
#include <stdexcept>
namespace YYCC::ConfigManager {
@ -17,7 +18,11 @@ namespace YYCC::ConfigManager {
m_CfgFilePath = cfg_file_path;
// assign settings
for (auto* setting : settings) {
m_Settings.try_emplace(setting->GetName(), setting);
auto result = m_Settings.try_emplace(setting->GetName(), setting);
if (!result.second) {
// if not inserted because duplicated, raise exception
throw std::invalid_argument("Duplicated setting name");
}
}
}

View File

@ -13,7 +13,7 @@
namespace YYCC::ConfigManager {
template<typename _Ty>
struct Constrain {
struct Constraint {
using CheckFct_t = std::function<bool(const _Ty&)>;
//using CorrectFct_t = std::function<_Ty(const _Ty&)>;
CheckFct_t m_CheckFct;
@ -24,13 +24,13 @@ namespace YYCC::ConfigManager {
}
};
namespace ConstrainPresets {
namespace ConstraintPresets {
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_enum_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
Constrain<_Ty> GetNumberRangeConstrain(_Ty min_value, _Ty max_value) {
Constraint<_Ty> GetNumberRangeConstraint(_Ty min_value, _Ty max_value) {
if (min_value > max_value)
throw std::invalid_argument("invalid min max value for NumberRangeConstrain");
return Constrain<_Ty> {
throw std::invalid_argument("invalid min max value for NumberRangeConstraint");
return Constraint<_Ty> {
[min_value, max_value](const _Ty& val) -> bool { return (val <= max_value) && (val >= min_value); }
/*[min_value, max_value](const _Ty& val) -> _Ty { return std::clamp(val, min_value, max_value); }*/
};
@ -96,14 +96,14 @@ namespace YYCC::ConfigManager {
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> || std::is_enum_v<_Ty>, int> = 0>
class NumberSetting : public AbstractSetting {
public:
NumberSetting(const yycc_char8_t* name, _Ty default_value, Constrain<_Ty> constrain = Constrain<_Ty> {}) :
AbstractSetting(name), m_Data(default_value), m_DefaultData(default_value), m_Constrain(constrain) {}
NumberSetting(const yycc_char8_t* name, _Ty default_value, Constraint<_Ty> constraint = Constraint<_Ty> {}) :
AbstractSetting(name), m_Data(default_value), m_DefaultData(default_value), m_Constraint(constraint) {}
virtual ~NumberSetting() {}
_Ty Get() const { return m_Data; }
bool Set(_Ty new_data) {
// validate data
if (m_Constrain.IsValid() && !m_Constrain.m_CheckFct(new_data))
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data))
return false;
// assign data
m_Data = new_data;
@ -117,7 +117,7 @@ namespace YYCC::ConfigManager {
return false;
m_Data = *reinterpret_cast<const _Ty*>(GetDataPtr());
// check data
if (m_Constrain.IsValid() && !m_Constrain.m_CheckFct(m_Data))
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
return false;
return true;
}
@ -132,13 +132,13 @@ namespace YYCC::ConfigManager {
}
_Ty m_Data, m_DefaultData;
Constrain<_Ty> m_Constrain;
Constraint<_Ty> m_Constraint;
};
class StringSetting : public AbstractSetting {
public:
StringSetting(const yycc_char8_t* name, const yycc_char8_t* default_value, Constrain<yycc_u8string> constrain = Constrain<yycc_u8string> {}) :
AbstractSetting(name), m_Data(), m_DefaultData(), m_Constrain(constrain) {
StringSetting(const yycc_char8_t* name, const yycc_char8_t* default_value, Constraint<yycc_u8string> constraint = Constraint<yycc_u8string> {}) :
AbstractSetting(name), m_Data(), m_DefaultData(), m_Constraint(constraint) {
if (default_value != nullptr) {
m_Data = default_value;
m_DefaultData = default_value;
@ -151,7 +151,7 @@ namespace YYCC::ConfigManager {
// check data validation
if (new_data == nullptr)
return false;
if (m_Constrain.IsValid() && !m_Constrain.m_CheckFct(m_Data))
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
return false;
// assign data
m_Data = new_data;
@ -173,7 +173,7 @@ namespace YYCC::ConfigManager {
string_length
);
// check data
if (m_Constrain.IsValid() && !m_Constrain.m_CheckFct(m_Data))
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
return false;
return true;
}
@ -194,7 +194,7 @@ namespace YYCC::ConfigManager {
}
yycc_u8string m_Data, m_DefaultData;
Constrain<yycc_u8string> m_Constrain;
Constraint<yycc_u8string> m_Constraint;
};
#pragma endregion

View File

@ -226,7 +226,7 @@ namespace YYCC::ConsoleHelper {
WinConsoleWrite(strl, bIsErr);
#else
// in linux, directly use C function to write.
std::fputs(EncodingHelper::ToNative(strl.c_str()), bIsErr ? stderr : stdout);
std::fputs(EncodingHelper::ToOrdinary(strl.c_str()), bIsErr ? stderr : stdout);
#endif
}

View File

@ -4,7 +4,7 @@
namespace YYCC::EncodingHelper {
#pragma region UTF8 Native Convertion
#pragma region UTF8 Ordinary Convertion
const yycc_char8_t* ToUTF8(const char* src) {
return reinterpret_cast<const yycc_char8_t*>(src);
@ -19,16 +19,16 @@ namespace YYCC::EncodingHelper {
return yycc_u8string_view(reinterpret_cast<const yycc_char8_t*>(src.data()), src.size());
}
const char* ToNative(const yycc_char8_t* src) {
const char* ToOrdinary(const yycc_char8_t* src) {
return reinterpret_cast<const char*>(src);
}
char* ToNative(yycc_char8_t* src) {
char* ToOrdinary(yycc_char8_t* src) {
return reinterpret_cast<char*>(src);
}
std::string ToNative(const yycc_u8string_view& src) {
std::string ToOrdinary(const yycc_u8string_view& src) {
return std::string(reinterpret_cast<const char*>(src.data()), src.size());
}
std::string_view ToNativeView(const yycc_u8string_view& src) {
std::string_view ToOrdinaryView(const yycc_u8string_view& src) {
return std::string_view(reinterpret_cast<const char*>(src.data()), src.size());
}
@ -176,7 +176,7 @@ return ret;
#pragma region UTF8ToWchar
bool UTF8ToWchar(const yycc_u8string_view& src, std::wstring& dst) {
std::string_view adapted_src(ToNativeView(src));
std::string_view adapted_src(ToOrdinaryView(src));
return CharToWchar(adapted_src, dst, CP_UTF8);
}
bool UTF8ToWchar(const yycc_char8_t* src, std::wstring& dst) {

View File

@ -10,58 +10,25 @@
#endif
/**
* @brief The namespace handling encoding issues.
* @brief The helper for all encoding aspects.
* @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.
*
* For more infomations about how to use the functions provided by this namespace,
* please see \ref library_encoding and \ref encoding_helper.
*/
namespace YYCC::EncodingHelper {
#define _YYCC_U8(strl) u8 ## strl
#define YYCC_U8(strl) (reinterpret_cast<const ::YYCC::yycc_char8_t*>(_YYCC_U8(strl)))
#define _YYCC_U8(strl) u8 ## strl ///< The assistant macro for YYCC_U8.
#define YYCC_U8(strl) (reinterpret_cast<const ::YYCC::yycc_char8_t*>(_YYCC_U8(strl))) ///< The macro for creating UTF8 string literal. See \ref library_encoding.
const yycc_char8_t* ToUTF8(const char* src);
yycc_char8_t* ToUTF8(char* src);
yycc_u8string ToUTF8(const std::string_view& src);
yycc_u8string_view ToUTF8View(const std::string_view& src);
const char* ToNative(const yycc_char8_t* src);
char* ToNative(yycc_char8_t* src);
std::string ToNative(const yycc_u8string_view& src);
std::string_view ToNativeView(const yycc_u8string_view& src);
const char* ToOrdinary(const yycc_char8_t* src);
char* ToOrdinary(yycc_char8_t* src);
std::string ToOrdinary(const yycc_u8string_view& src);
std::string_view ToOrdinaryView(const yycc_u8string_view& src);
#if YYCC_OS == YYCC_OS_WINDOWS

View File

@ -11,6 +11,7 @@
#include <cstdarg>
#include <cstdio>
#include <cinttypes>
#include <mutex>
#include "WinImportPrefix.hpp"
#include <Windows.h>
@ -19,38 +20,153 @@
namespace YYCC::ExceptionHelper {
/**
* @brief True if the exception handler already registered, otherwise false.
* @details
* This variable is designed to prevent multiple register operation
* because unhandled exception handler should only be registered once.
* \n
* Register function should check whether this variable is false before registering,
* and set this variable to true after registing.
* Unregister as well as should do the same check.
*/
static bool g_IsRegistered = false;
/**
* @brief True if a exception handler is running, otherwise false.
* @details
* This variable is served for blocking possible infinity recursive exception handling.
* \n
* When entering unhandled exception handler, we must check whether this variable is true.
* If it is true, it mean that there is another unhandled exception handler running.
* Then we should exit immediately.
* Otherwise, this variable should be set to true indicating we are processing unhandled exception.
* After processing exception, at the end of unhandled exception handler,
* we should restore this value to false.
*
*/
static bool g_IsProcessing = false;
/**
* @brief The backup of original exception handler.
* @details
* This variable was set when registering unhandled exception handler.
* And will be used when unregistering for restoring.
*/
static LPTOP_LEVEL_EXCEPTION_FILTER g_ProcBackup;
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS);
class ExceptionRegister {
public:
ExceptionRegister() :
m_CoreMutex(),
m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr),
m_SingletonMutex(NULL) {}
~ExceptionRegister() {
Unregister();
}
public:
/**
* @brief Try to register unhandled exception handler.
*/
void Register() {
std::lock_guard<std::mutex> locker(m_CoreMutex);
// if we have registered, return
if (m_IsRegistered) return;
// check singleton
// build mutex string first
yycc_u8string mutex_name;
if (!StringHelper::Printf(mutex_name, YYCC_U8("Global\\%" PRIu32 ".{61634294-d23c-43f9-8490-b5e09837eede}"), GetCurrentProcessId()))
return;
std::wstring mutex_wname;
if (!EncodingHelper::UTF8ToWchar(mutex_name, mutex_wname))
return;
// create mutex
m_SingletonMutex = CreateMutexW(NULL, FALSE, mutex_wname.c_str());
DWORD errcode = GetLastError();
// check whether be created
if (m_SingletonMutex == NULL)
return;
if (errcode == ERROR_ALREADY_EXISTS) {
CloseHandle(m_SingletonMutex);
m_SingletonMutex = NULL;
return;
}
// okey, we can register it.
// backup old handler
m_PrevProcHandler = SetUnhandledExceptionFilter(UExceptionImpl);
// mark registered
m_IsRegistered = true;
}
/**
* @brief Try to unregister unhandled exception handler.
*/
void Unregister() {
std::lock_guard<std::mutex> locker(m_CoreMutex);
// if we are not registered, skip
if (!m_IsRegistered) return;
// unregister handler
// restore old handler
SetUnhandledExceptionFilter(m_PrevProcHandler);
m_PrevProcHandler = nullptr;
// release singleton handler
if (m_SingletonMutex != NULL) {
CloseHandle(m_SingletonMutex);
m_SingletonMutex = NULL;
}
// mark unregistered
m_IsRegistered = false;
}
public:
/**
* @brief Check whether handler is registered.
* @return True if it is, otherwise false.
*/
bool IsRegistered() const {
std::lock_guard<std::mutex> locker(m_CoreMutex);
return m_IsRegistered;
}
/**
* @brief Check whether we are processing unhandled exception.
* @return True if it is, otherwise false.
*/
bool IsProcessing() const {
std::lock_guard<std::mutex> locker(m_CoreMutex);
return m_IsProcessing;
}
/**
* @brief Get the old unhandled exception handler before registering.
* @return The fucntion pointer to old unhandled exception handler. May be nullptr.
*/
LPTOP_LEVEL_EXCEPTION_FILTER GetPrevProcHandler() const {
std::lock_guard<std::mutex> locker(m_CoreMutex);
return m_PrevProcHandler;
}
/**
* @brief Try to start process unhandled exception.
* @return True if you can start to process.
* False means there is already a process running. You should not process it now.
*/
bool StartProcessing() {
std::lock_guard<std::mutex> locker(m_CoreMutex);
if (m_IsProcessing) return false;
else {
m_IsProcessing = true;
return true;
}
}
/**
* @brief Mark current process of unhandled exception has done.
* @details This should only be called when StartProcessing() return true.
*/
void StopProcessing() {
std::lock_guard<std::mutex> locker(m_CoreMutex);
m_IsProcessing = false;
}
private:
/**
* @brief The core mutex for keeping this class is in synchronized.
*/
mutable std::mutex m_CoreMutex;
/**
* @brief Whether we have registered unhandled exception handler.
* True if it is, otherwise false.
*/
bool m_IsRegistered;
/**
* @brief Whether we are processing unhandled exception.
* True if it is, otherwise false.
*/
bool m_IsProcessing;
/**
* @brief The backup of old unhandled exception handler.
*/
LPTOP_LEVEL_EXCEPTION_FILTER m_PrevProcHandler;
/**
* @brief The Windows mutex handle for singleton implementation.
* Because we may have many DLLs using YYCC in the same process.
* But the unhandled exception handler only need to be registered once.
*/
HANDLE m_SingletonMutex;
};
static ExceptionRegister g_ExceptionRegister;
#pragma region Exception Handler Implementation
@ -122,7 +238,7 @@ namespace YYCC::ExceptionHelper {
if (fs != nullptr) {
va_list arg1;
va_start(arg1, fmt);
std::vfprintf(fs, EncodingHelper::ToNative(fmt), arg1);
std::vfprintf(fs, EncodingHelper::ToOrdinary(fmt), arg1);
std::fputs("\n", fs);
va_end(arg1);
}
@ -145,7 +261,7 @@ namespace YYCC::ExceptionHelper {
static void UExceptionErrLogWriteLine(std::FILE* fs, const yycc_char8_t* strl) {
// write to file
if (fs != nullptr) {
std::fputs(EncodingHelper::ToNative(strl), fs);
std::fputs(EncodingHelper::ToOrdinary(strl), fs);
std::fputs("\n", fs);
}
// write to stderr
@ -316,28 +432,24 @@ namespace YYCC::ExceptionHelper {
}
static bool UExceptionFetchRecordPath(yycc_u8string& log_path, yycc_u8string& 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
yycc_u8string u8_self_module_name;
// build two file names like: "error.exe.1234.log" and "error.exe.1234.dmp".
// "error.exe" is the name of current process. "1234" is current process id.
// get process name
yycc_u8string u8_process_name;
{
// get module handle
HMODULE hSelfModule = YYCC::WinFctHelper::GetCurrentModule();
if (hSelfModule == nullptr)
return false;
// get full path of self module
yycc_u8string u8_self_module_path;
if (!YYCC::WinFctHelper::GetModuleFileName(hSelfModule, u8_self_module_path))
// get full path of process
yycc_u8string u8_process_path;
if (!YYCC::WinFctHelper::GetModuleFileName(NULL, u8_process_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());
std::filesystem::path process_path(FsPathPatch::FromUTF8Path(u8_process_path.c_str()));
u8_process_name = FsPathPatch::ToUTF8Path(process_path.filename());
}
// then get process id
DWORD process_id = GetCurrentProcessId();
// conbine them as a file name prefix
yycc_u8string u8_filename_prefix;
if (!YYCC::StringHelper::Printf(u8_filename_prefix, YYCC_U8("%s.%" PRIu32), u8_self_module_name.c_str(), process_id))
if (!YYCC::StringHelper::Printf(u8_filename_prefix, YYCC_U8("%s.%" PRIu32), u8_process_name.c_str(), process_id))
return false;
// then get file name for log and minidump
yycc_u8string u8_log_filename = u8_filename_prefix + YYCC_U8(".log");
@ -367,10 +479,9 @@ namespace YYCC::ExceptionHelper {
}
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) {
// detect loop calling
if (g_IsProcessing) goto end_proc;
// start process
g_IsProcessing = true;
// try to start process current unhandled exception
// to prevent any possible recursive calling.
if (!g_ExceptionRegister.StartProcessing()) goto end_proc;
// core implementation
{
@ -396,14 +507,15 @@ namespace YYCC::ExceptionHelper {
}
// end process
failed:
g_IsProcessing = false;
// stop process
g_ExceptionRegister.StartProcessing();
end_proc:
// if backup proc can be run, run it
// otherwise directly return.
end_proc:
if (g_ProcBackup != nullptr) {
return g_ProcBackup(info);
auto prev_proc = g_ExceptionRegister.GetPrevProcHandler();
if (prev_proc != nullptr) {
return prev_proc(info);
} else {
return EXCEPTION_CONTINUE_SEARCH;
}
@ -412,15 +524,11 @@ namespace YYCC::ExceptionHelper {
#pragma endregion
void Register() {
if (g_IsRegistered) return;
g_ProcBackup = SetUnhandledExceptionFilter(UExceptionImpl);
g_IsRegistered = true;
g_ExceptionRegister.Register();
}
void Unregister() {
if (!g_IsRegistered) return;
SetUnhandledExceptionFilter(g_ProcBackup);
g_IsRegistered = false;
g_ExceptionRegister.Unregister();
}
}

View File

@ -18,7 +18,7 @@ namespace YYCC::FsPathPatch {
return std::filesystem::path(wpath);
#else
return std::filesystem::path(EncodingHelper::ToNative(u8_path));
return std::filesystem::path(EncodingHelper::ToOrdinary(u8_path));
#endif
}

View File

@ -16,13 +16,13 @@ namespace YYCC::ParserHelper {
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
bool TryParse(const yycc_u8string_view& strl, _Ty& num) {
auto [ptr, ec] = std::from_chars(
EncodingHelper::ToNative(strl.data()),
EncodingHelper::ToNative(strl.data() + strl.size()),
EncodingHelper::ToOrdinary(strl.data()),
EncodingHelper::ToOrdinary(strl.data() + strl.size()),
num, std::chars_format::general
);
if (ec == std::errc()) {
// check whether the full string is matched
return ptr == EncodingHelper::ToNative(strl.data() + strl.size());
return ptr == EncodingHelper::ToOrdinary(strl.data() + strl.size());
} else if (ec == std::errc::invalid_argument) {
// given string is invalid
return false;
@ -37,13 +37,13 @@ namespace YYCC::ParserHelper {
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
bool TryParse(const yycc_u8string_view& strl, _Ty& num, int base = 10) {
auto [ptr, ec] = std::from_chars(
EncodingHelper::ToNative(strl.data()),
EncodingHelper::ToNative(strl.data() + strl.size()),
EncodingHelper::ToOrdinary(strl.data()),
EncodingHelper::ToOrdinary(strl.data() + strl.size()),
num, base
);
if (ec == std::errc()) {
// check whether the full string is matched
return ptr == EncodingHelper::ToNative(strl.data() + strl.size());
return ptr == EncodingHelper::ToOrdinary(strl.data() + strl.size());
} else if (ec == std::errc::invalid_argument) {
// given string is invalid
return false;
@ -76,8 +76,8 @@ namespace YYCC::ParserHelper {
yycc_u8string ToString(_Ty num) {
std::array<yycc_char8_t, 64> buffer;
auto [ptr, ec] = std::to_chars(
EncodingHelper::ToNative(buffer.data()),
EncodingHelper::ToNative(buffer.data() + buffer.size()),
EncodingHelper::ToOrdinary(buffer.data()),
EncodingHelper::ToOrdinary(buffer.data() + buffer.size()),
num
);
if (ec == std::errc()) {

View File

@ -25,7 +25,7 @@ namespace YYCC::StringHelper {
int count = std::vsnprintf(
nullptr,
0,
EncodingHelper::ToNative(format),
EncodingHelper::ToOrdinary(format),
args1
);
if (count < 0) {
@ -40,9 +40,9 @@ namespace YYCC::StringHelper {
// however std::vsnprintf already have a trailing NULL, so we plus 1 for it.
strl.resize(count);
int write_result = std::vsnprintf(
EncodingHelper::ToNative(strl.data()),
EncodingHelper::ToOrdinary(strl.data()),
strl.size() + 1,
EncodingHelper::ToNative(format),
EncodingHelper::ToOrdinary(format),
args2
);
va_end(args2);

View File

@ -134,7 +134,8 @@ namespace YYCCTestbench {
Console::Write(YYCC_U8("\t> "));
YYCC::yycc_u8string gotten(Console::ReadLine());
Assert(gotten == strl, YYCC::StringHelper::Printf(YYCC_U8("Got: %s"), gotten.c_str()).c_str());
if (gotten == strl) Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("\tMatched! Got: %s")), gotten.c_str());
else Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("\tNOT Matched! Got: %s")), gotten.c_str());
}
}
@ -401,7 +402,7 @@ namespace YYCCTestbench {
m_FloatSetting(YYCC_U8("float-setting"), 0.0f),
m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")),
m_BoolSetting(YYCC_U8("bool-setting"), false),
m_ClampedFloatSetting(YYCC_U8("clamped-float-setting"), 0.0f, YYCC::ConfigManager::ConstrainPresets::GetNumberRangeConstrain<float>(-1.0f, 1.0f)),
m_ClampedFloatSetting(YYCC_U8("clamped-float-setting"), 0.0f, YYCC::ConfigManager::ConstraintPresets::GetNumberRangeConstraint<float>(-1.0f, 1.0f)),
m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1),
m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), {
&m_IntSetting, &m_FloatSetting, &m_StringSetting, &m_BoolSetting, &m_ClampedFloatSetting, &m_EnumSetting
@ -436,7 +437,7 @@ namespace YYCCTestbench {
// init cfg manager
TestConfigManager test;
// test constrain works
// test constraint works
Assert(!test.m_ClampedFloatSetting.Set(2.0f), YYCC_U8("YYCC::ConfigManager::Constraint"));
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::Constraint"));