Compare commits
	
		
			7 Commits
		
	
	
		
			06e75924f1
			...
			4f1e2447d0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4f1e2447d0 | |||
| 3075ec583d | |||
| 2e28dd4c48 | |||
| a1699f13db | |||
| 65b81f5cfa | |||
| 1c5a85bbb2 | |||
| 1f04e23092 | 
| @ -1,6 +1,6 @@ | |||||||
| cmake_minimum_required(VERSION 3.23) | cmake_minimum_required(VERSION 3.23) | ||||||
| project(YYCC | project(YYCC | ||||||
|     VERSION 1.0.0 |     VERSION 1.1.0 | ||||||
|     LANGUAGES CXX |     LANGUAGES CXX | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | |||||||
| @ -4,8 +4,141 @@ | |||||||
|  |  | ||||||
| YYCC::EncodingHelper namespace include all encoding related functions: | 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 Windows specific convertion between \c WCHAR, UTF8 string and string encoded by other encoding. | ||||||
| \li The convertion among UTF8, UTF16 and UTF32. | \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. | ||||||
|  |  | ||||||
| */ | */ | ||||||
| @ -29,6 +29,8 @@ | |||||||
|  |  | ||||||
|     \li \subpage intro |     \li \subpage intro | ||||||
|  |  | ||||||
|  |     \li \subpage platform_checker | ||||||
|  |  | ||||||
|     \li \subpage library_encoding |     \li \subpage library_encoding | ||||||
|  |  | ||||||
|     \li \subpage encoding_helper |     \li \subpage encoding_helper | ||||||
|  | |||||||
| @ -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. | 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. | 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 | \section library_encoding__utf8_literal UTF8 Literal | ||||||
|  |  | ||||||
| String literal is a C++ concept. | 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); | yycc_char8_t* mutable_converted = YYCC::EncodingHelper::ToUTF8(mutable_utf8); | ||||||
| \endcode | \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: | Here is an exmaple: | ||||||
|  |  | ||||||
| \code | \code | ||||||
| const yycc_char8_t* yycc_utf8 = YYCC_U8("I am UTF8 string."); | 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. | 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 | \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 | \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. | 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. | 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. | 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. | 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: | There is an exmaple: | ||||||
|  |  | ||||||
| \code | \code | ||||||
| std::string native_string("I am UTF8"); | std::string ordinary_string("I am UTF8"); | ||||||
| yycc_u8string yycc_string = YYCC::EncodingHelper::ToUTF8(native_string); | yycc_u8string yycc_string = YYCC::EncodingHelper::ToUTF8(ordinary_string); | ||||||
| auto result = YYCC::EncodingHelper::UTF8ToUTF32(yycc_string); | auto result = YYCC::EncodingHelper::UTF8ToUTF32(yycc_string); | ||||||
| \endcode | \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. | so you can directly pass a \c std::string instance to it. | ||||||
|  |  | ||||||
| String view will reduce unnecessary memory copy. | 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. | you can use alternative YYCC::EncodingHelper::ToUTF8View. | ||||||
|  |  | ||||||
| \code | \code | ||||||
| std::string native_string("I am UTF8"); | std::string ordinary_string("I am UTF8"); | ||||||
| yycc_u8string_view yycc_string = YYCC::EncodingHelper::ToUTF8View(native_string); | yycc_u8string_view yycc_string = YYCC::EncodingHelper::ToUTF8View(ordinary_string); | ||||||
| auto result = YYCC::EncodingHelper::UTF8ToUTF32(yycc_string); | auto result = YYCC::EncodingHelper::UTF8ToUTF32(yycc_string); | ||||||
| \endcode | \endcode | ||||||
|  |  | ||||||
| Comparing with previous one, this example use less memory. | 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. | 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. | Try to do your own research and figure out how to use them. | ||||||
| It's pretty easy. | It's pretty easy. | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										35
									
								
								doc/src/platform_checker.dox
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								doc/src/platform_checker.dox
									
									
									
									
									
										Normal 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. | ||||||
|  |  | ||||||
|  | */ | ||||||
| @ -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. |     Programmer will not be affected by the automatical rename of \c GetObject, \c GetClassName and etc. | ||||||
|     <UL> |     <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>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> |     </UL> | ||||||
|     </LI> |     </LI> | ||||||
|     <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. | 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. | 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. | 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. | The only thing you should pledge is that you must make sure they are presented by pair. | ||||||
|  |  | ||||||
| This guard is Windows specific. | This guard is Windows specific. | ||||||
| It does nothing if you accidently use it in other platforms such as Linux, | 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, | 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. | if your program need to be run on multiple platforms. | ||||||
|  |  | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ MKDIR bin | |||||||
| CD bin | CD bin | ||||||
| MKDIR Win32 | MKDIR Win32 | ||||||
| MKDIR x64 | MKDIR x64 | ||||||
|  | MKDIR documentation | ||||||
| MKDIR install | MKDIR install | ||||||
|  |  | ||||||
| :: Build for Win32 | :: Build for Win32 | ||||||
| @ -32,4 +33,11 @@ cmake --build . --config Release | |||||||
| cmake --install . --prefix=../install --config Release | cmake --install . --prefix=../install --config Release | ||||||
| CD .. | CD .. | ||||||
|  |  | ||||||
|  | :: Build for documentation | ||||||
|  | CD documentation | ||||||
|  | cmake -DYYCC_BUILD_DOC=ON ../.. | ||||||
|  | cmake --build . --config Release | ||||||
|  | :: cmake --install . --prefix=../install --config Release | ||||||
|  | CD .. | ||||||
|  |  | ||||||
| ECHO DONE | ECHO DONE | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
| #include "EncodingHelper.hpp" | #include "EncodingHelper.hpp" | ||||||
| #include "IOHelper.hpp" | #include "IOHelper.hpp" | ||||||
|  | #include <stdexcept> | ||||||
|  |  | ||||||
| namespace YYCC::ConfigManager { | namespace YYCC::ConfigManager { | ||||||
|  |  | ||||||
| @ -17,7 +18,11 @@ namespace YYCC::ConfigManager { | |||||||
| 			m_CfgFilePath = cfg_file_path; | 			m_CfgFilePath = cfg_file_path; | ||||||
| 		// assign settings | 		// assign settings | ||||||
| 		for (auto* setting : 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"); | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ | |||||||
| namespace YYCC::ConfigManager { | namespace YYCC::ConfigManager { | ||||||
|  |  | ||||||
| 	template<typename _Ty> | 	template<typename _Ty> | ||||||
| 	struct Constrain { | 	struct Constraint { | ||||||
| 		using CheckFct_t = std::function<bool(const _Ty&)>; | 		using CheckFct_t = std::function<bool(const _Ty&)>; | ||||||
| 		//using CorrectFct_t = std::function<_Ty(const _Ty&)>; | 		//using CorrectFct_t = std::function<_Ty(const _Ty&)>; | ||||||
| 		CheckFct_t m_CheckFct; | 		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> | 		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) | 			if (min_value > max_value) | ||||||
| 				throw std::invalid_argument("invalid min max value for NumberRangeConstrain"); | 				throw std::invalid_argument("invalid min max value for NumberRangeConstraint"); | ||||||
| 			return Constrain<_Ty> { | 			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) -> bool { return (val <= max_value) && (val >= min_value); } | ||||||
| 					/*[min_value, max_value](const _Ty& val) -> _Ty { return std::clamp(val, min_value, max_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> | 	template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> || std::is_enum_v<_Ty>, int> = 0> | ||||||
| 	class NumberSetting : public AbstractSetting { | 	class NumberSetting : public AbstractSetting { | ||||||
| 	public: | 	public: | ||||||
| 		NumberSetting(const yycc_char8_t* name, _Ty default_value, Constrain<_Ty> constrain = Constrain<_Ty> {}) : | 		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_Constrain(constrain) {} | 			AbstractSetting(name), m_Data(default_value), m_DefaultData(default_value), m_Constraint(constraint) {} | ||||||
| 		virtual ~NumberSetting() {} | 		virtual ~NumberSetting() {} | ||||||
|  |  | ||||||
| 		_Ty Get() const { return m_Data; } | 		_Ty Get() const { return m_Data; } | ||||||
| 		bool Set(_Ty new_data) {  | 		bool Set(_Ty new_data) {  | ||||||
| 			// validate 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; | 				return false; | ||||||
| 			// assign data | 			// assign data | ||||||
| 			m_Data = new_data; | 			m_Data = new_data; | ||||||
| @ -117,7 +117,7 @@ namespace YYCC::ConfigManager { | |||||||
| 				return false; | 				return false; | ||||||
| 			m_Data = *reinterpret_cast<const _Ty*>(GetDataPtr()); | 			m_Data = *reinterpret_cast<const _Ty*>(GetDataPtr()); | ||||||
| 			// check data | 			// 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 false; | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| @ -132,13 +132,13 @@ namespace YYCC::ConfigManager { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		_Ty m_Data, m_DefaultData; | 		_Ty m_Data, m_DefaultData; | ||||||
| 		Constrain<_Ty> m_Constrain; | 		Constraint<_Ty> m_Constraint; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	class StringSetting : public AbstractSetting { | 	class StringSetting : public AbstractSetting { | ||||||
| 	public: | 	public: | ||||||
| 		StringSetting(const yycc_char8_t* name, const yycc_char8_t* default_value, Constrain<yycc_u8string> constrain = Constrain<yycc_u8string> {}) : | 		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_Constrain(constrain) { | 			AbstractSetting(name), m_Data(), m_DefaultData(), m_Constraint(constraint) { | ||||||
| 			if (default_value != nullptr) { | 			if (default_value != nullptr) { | ||||||
| 				m_Data = default_value; | 				m_Data = default_value; | ||||||
| 				m_DefaultData = default_value; | 				m_DefaultData = default_value; | ||||||
| @ -151,7 +151,7 @@ namespace YYCC::ConfigManager { | |||||||
| 			// check data validation | 			// check data validation | ||||||
| 			if (new_data == nullptr)  | 			if (new_data == nullptr)  | ||||||
| 				return false; | 				return false; | ||||||
| 			if (m_Constrain.IsValid() && !m_Constrain.m_CheckFct(m_Data)) | 			if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data)) | ||||||
| 				return false; | 				return false; | ||||||
| 			// assign data | 			// assign data | ||||||
| 			m_Data = new_data; | 			m_Data = new_data; | ||||||
| @ -173,7 +173,7 @@ namespace YYCC::ConfigManager { | |||||||
| 				string_length | 				string_length | ||||||
| 			); | 			); | ||||||
| 			// check data | 			// 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 false; | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| @ -194,7 +194,7 @@ namespace YYCC::ConfigManager { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		yycc_u8string m_Data, m_DefaultData; | 		yycc_u8string m_Data, m_DefaultData; | ||||||
| 		Constrain<yycc_u8string> m_Constrain; | 		Constraint<yycc_u8string> m_Constraint; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| #pragma endregion | #pragma endregion | ||||||
|  | |||||||
| @ -226,7 +226,7 @@ namespace YYCC::ConsoleHelper { | |||||||
| 		WinConsoleWrite(strl, bIsErr); | 		WinConsoleWrite(strl, bIsErr); | ||||||
| #else | #else | ||||||
| 		// in linux, directly use C function to write. | 		// 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 | #endif | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
|  |  | ||||||
| namespace YYCC::EncodingHelper { | namespace YYCC::EncodingHelper { | ||||||
|  |  | ||||||
| #pragma region UTF8 Native Convertion | #pragma region UTF8 Ordinary Convertion | ||||||
| 	 | 	 | ||||||
| 	const yycc_char8_t* ToUTF8(const char* src) { | 	const yycc_char8_t* ToUTF8(const char* src) { | ||||||
| 		return reinterpret_cast<const yycc_char8_t*>(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()); | 		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); | 		return reinterpret_cast<const char*>(src); | ||||||
| 	} | 	} | ||||||
| 	char* ToNative(yycc_char8_t* src) { | 	char* ToOrdinary(yycc_char8_t* src) { | ||||||
| 		return reinterpret_cast<char*>(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()); | 		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()); | 		return std::string_view(reinterpret_cast<const char*>(src.data()), src.size()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -176,7 +176,7 @@ return ret; | |||||||
| #pragma region UTF8ToWchar | #pragma region UTF8ToWchar | ||||||
| 	 | 	 | ||||||
| 	bool UTF8ToWchar(const yycc_u8string_view& src, std::wstring& dst) { | 	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); | 		return CharToWchar(adapted_src, dst, CP_UTF8); | ||||||
| 	} | 	} | ||||||
| 	bool UTF8ToWchar(const yycc_char8_t* src, std::wstring& dst) { | 	bool UTF8ToWchar(const yycc_char8_t* src, std::wstring& dst) { | ||||||
|  | |||||||
| @ -10,58 +10,25 @@ | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @brief The namespace handling encoding issues. |  * @brief The helper for all encoding aspects. | ||||||
|  * @details |  * @details | ||||||
|  * \par Windows Encoding Convertion |  * For more infomations about how to use the functions provided by this namespace, | ||||||
|  * This namespace provides the convertion between wchar_t, UTF8 and code-page-based string: |  * please see \ref library_encoding and \ref encoding_helper. | ||||||
|  * 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 { | ||||||
|  |  | ||||||
| #define _YYCC_U8(strl) 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))) | #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); | 	const yycc_char8_t* ToUTF8(const char* src); | ||||||
| 	yycc_char8_t* ToUTF8(char* src); | 	yycc_char8_t* ToUTF8(char* src); | ||||||
| 	yycc_u8string ToUTF8(const std::string_view& src); | 	yycc_u8string ToUTF8(const std::string_view& src); | ||||||
| 	yycc_u8string_view ToUTF8View(const std::string_view& src); | 	yycc_u8string_view ToUTF8View(const std::string_view& src); | ||||||
|  |  | ||||||
| 	const char* ToNative(const yycc_char8_t* src); | 	const char* ToOrdinary(const yycc_char8_t* src); | ||||||
| 	char* ToNative(yycc_char8_t* src); | 	char* ToOrdinary(yycc_char8_t* src); | ||||||
| 	std::string ToNative(const yycc_u8string_view& src); | 	std::string ToOrdinary(const yycc_u8string_view& src); | ||||||
| 	std::string_view ToNativeView(const yycc_u8string_view& src); | 	std::string_view ToOrdinaryView(const yycc_u8string_view& src); | ||||||
|  |  | ||||||
| #if YYCC_OS == YYCC_OS_WINDOWS | #if YYCC_OS == YYCC_OS_WINDOWS | ||||||
|  |  | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ | |||||||
| #include <cstdarg> | #include <cstdarg> | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
| #include <cinttypes> | #include <cinttypes> | ||||||
|  | #include <mutex> | ||||||
|  |  | ||||||
| #include "WinImportPrefix.hpp" | #include "WinImportPrefix.hpp" | ||||||
| #include <Windows.h> | #include <Windows.h> | ||||||
| @ -19,38 +20,153 @@ | |||||||
|  |  | ||||||
| namespace YYCC::ExceptionHelper { | namespace YYCC::ExceptionHelper { | ||||||
|  |  | ||||||
| 	/** | 	static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS); | ||||||
| 	 * @brief True if the exception handler already registered, otherwise false. | 	class ExceptionRegister { | ||||||
| 	 * @details | 	public: | ||||||
| 	 * This variable is designed to prevent multiple register operation | 		ExceptionRegister() : | ||||||
| 	 * because unhandled exception handler should only be registered once. | 			m_CoreMutex(), | ||||||
| 	 * \n | 			m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr), | ||||||
| 	 * Register function should check whether this variable is false before registering, | 			m_SingletonMutex(NULL) {} | ||||||
| 	 * and set this variable to true after registing. | 		~ExceptionRegister() { | ||||||
| 	 * Unregister as well as should do the same check. | 			Unregister(); | ||||||
| 	*/ | 		} | ||||||
| 	static bool g_IsRegistered = false; |  | ||||||
| 	/** | 	public: | ||||||
| 	 * @brief True if a exception handler is running, otherwise false. | 		/** | ||||||
| 	 * @details | 		 * @brief Try to register unhandled exception handler. | ||||||
| 	 * This variable is served for blocking possible infinity recursive exception handling. | 		*/ | ||||||
| 	 * \n | 		void Register() { | ||||||
| 	 * When entering unhandled exception handler, we must check whether this variable is true. | 			std::lock_guard<std::mutex> locker(m_CoreMutex); | ||||||
| 	 * If it is true, it mean that there is another unhandled exception handler running. | 			// if we have registered, return | ||||||
| 	 * Then we should exit immediately. | 			if (m_IsRegistered) return; | ||||||
| 	 * Otherwise, this variable should be set to true indicating we are processing unhandled exception. |  | ||||||
| 	 * After processing exception, at the end of unhandled exception handler, | 			// check singleton | ||||||
| 	 * we should restore this value to false. | 			// build mutex string first | ||||||
| 	 * | 			yycc_u8string mutex_name; | ||||||
| 	*/ | 			if (!StringHelper::Printf(mutex_name, YYCC_U8("Global\\%" PRIu32 ".{61634294-d23c-43f9-8490-b5e09837eede}"), GetCurrentProcessId())) | ||||||
| 	static bool g_IsProcessing = false; | 				return; | ||||||
| 	/** | 			std::wstring mutex_wname; | ||||||
| 	 * @brief The backup of original exception handler. | 			if (!EncodingHelper::UTF8ToWchar(mutex_name, mutex_wname)) | ||||||
| 	 * @details | 				return; | ||||||
| 	 * This variable was set when registering unhandled exception handler. | 			// create mutex | ||||||
| 	 * And will be used when unregistering for restoring. | 			m_SingletonMutex = CreateMutexW(NULL, FALSE, mutex_wname.c_str()); | ||||||
| 	*/ | 			DWORD errcode = GetLastError(); | ||||||
| 	static LPTOP_LEVEL_EXCEPTION_FILTER g_ProcBackup; | 			// 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 | #pragma region Exception Handler Implementation | ||||||
|  |  | ||||||
| @ -122,7 +238,7 @@ namespace YYCC::ExceptionHelper { | |||||||
| 		if (fs != nullptr) { | 		if (fs != nullptr) { | ||||||
| 			va_list arg1; | 			va_list arg1; | ||||||
| 			va_start(arg1, fmt); | 			va_start(arg1, fmt); | ||||||
| 			std::vfprintf(fs, EncodingHelper::ToNative(fmt), arg1); | 			std::vfprintf(fs, EncodingHelper::ToOrdinary(fmt), arg1); | ||||||
| 			std::fputs("\n", fs); | 			std::fputs("\n", fs); | ||||||
| 			va_end(arg1); | 			va_end(arg1); | ||||||
| 		} | 		} | ||||||
| @ -145,7 +261,7 @@ namespace YYCC::ExceptionHelper { | |||||||
| 	static void UExceptionErrLogWriteLine(std::FILE* fs, const yycc_char8_t* strl) { | 	static void UExceptionErrLogWriteLine(std::FILE* fs, const yycc_char8_t* strl) { | ||||||
| 		// write to file | 		// write to file | ||||||
| 		if (fs != nullptr) { | 		if (fs != nullptr) { | ||||||
| 			std::fputs(EncodingHelper::ToNative(strl), fs); | 			std::fputs(EncodingHelper::ToOrdinary(strl), fs); | ||||||
| 			std::fputs("\n", fs); | 			std::fputs("\n", fs); | ||||||
| 		} | 		} | ||||||
| 		// write to stderr | 		// write to stderr | ||||||
| @ -316,28 +432,24 @@ namespace YYCC::ExceptionHelper { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	static bool UExceptionFetchRecordPath(yycc_u8string& log_path, yycc_u8string& coredump_path) { | 	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". | 		// build two file names like: "error.exe.1234.log" and "error.exe.1234.dmp". | ||||||
| 		// "module.dll" is the name of current module. "1234" is current process id. | 		// "error.exe" is the name of current process. "1234" is current process id. | ||||||
| 		// get self module name | 		// get process name | ||||||
| 		yycc_u8string u8_self_module_name; | 		yycc_u8string u8_process_name; | ||||||
| 		{ | 		{ | ||||||
| 			// get module handle | 			// get full path of process | ||||||
| 			HMODULE hSelfModule = YYCC::WinFctHelper::GetCurrentModule(); | 			yycc_u8string u8_process_path; | ||||||
| 			if (hSelfModule == nullptr) | 			if (!YYCC::WinFctHelper::GetModuleFileName(NULL, u8_process_path)) | ||||||
| 				return false; |  | ||||||
| 			// get full path of self module |  | ||||||
| 			yycc_u8string u8_self_module_path; |  | ||||||
| 			if (!YYCC::WinFctHelper::GetModuleFileName(hSelfModule, u8_self_module_path)) |  | ||||||
| 				return false; | 				return false; | ||||||
| 			// extract file name from full path by std::filesystem::path | 			// extract file name from full path by std::filesystem::path | ||||||
| 			std::filesystem::path self_module_path(FsPathPatch::FromUTF8Path(u8_self_module_path.c_str())); | 			std::filesystem::path process_path(FsPathPatch::FromUTF8Path(u8_process_path.c_str())); | ||||||
| 			u8_self_module_name = FsPathPatch::ToUTF8Path(self_module_path.filename()); | 			u8_process_name = FsPathPatch::ToUTF8Path(process_path.filename()); | ||||||
| 		} | 		} | ||||||
| 		// then get process id | 		// then get process id | ||||||
| 		DWORD process_id = GetCurrentProcessId(); | 		DWORD process_id = GetCurrentProcessId(); | ||||||
| 		// conbine them as a file name prefix | 		// conbine them as a file name prefix | ||||||
| 		yycc_u8string u8_filename_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; | 			return false; | ||||||
| 		// then get file name for log and minidump | 		// then get file name for log and minidump | ||||||
| 		yycc_u8string u8_log_filename = u8_filename_prefix + YYCC_U8(".log"); | 		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) { | 	static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) { | ||||||
| 		// detect loop calling | 		// try to start process current unhandled exception | ||||||
| 		if (g_IsProcessing) goto end_proc; | 		// to prevent any possible recursive calling. | ||||||
| 		// start process | 		if (!g_ExceptionRegister.StartProcessing()) goto end_proc; | ||||||
| 		g_IsProcessing = true; |  | ||||||
|  |  | ||||||
| 		// core implementation | 		// core implementation | ||||||
| 		{ | 		{ | ||||||
| @ -396,14 +507,15 @@ namespace YYCC::ExceptionHelper { | |||||||
|  |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// end process | 		// stop process | ||||||
| 	failed: | 		g_ExceptionRegister.StartProcessing(); | ||||||
| 		g_IsProcessing = false; |  | ||||||
|  | 	end_proc: | ||||||
| 		// if backup proc can be run, run it | 		// if backup proc can be run, run it | ||||||
| 		// otherwise directly return. | 		// otherwise directly return. | ||||||
| 	end_proc: | 		auto prev_proc = g_ExceptionRegister.GetPrevProcHandler(); | ||||||
| 		if (g_ProcBackup != nullptr) { | 		if (prev_proc != nullptr) { | ||||||
| 			return g_ProcBackup(info); | 			return prev_proc(info); | ||||||
| 		} else { | 		} else { | ||||||
| 			return EXCEPTION_CONTINUE_SEARCH; | 			return EXCEPTION_CONTINUE_SEARCH; | ||||||
| 		} | 		} | ||||||
| @ -412,15 +524,11 @@ namespace YYCC::ExceptionHelper { | |||||||
| #pragma endregion | #pragma endregion | ||||||
|  |  | ||||||
| 	void Register() { | 	void Register() { | ||||||
| 		if (g_IsRegistered) return; | 		g_ExceptionRegister.Register(); | ||||||
| 		g_ProcBackup = SetUnhandledExceptionFilter(UExceptionImpl); |  | ||||||
| 		g_IsRegistered = true; |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	void Unregister() { | 	void Unregister() { | ||||||
| 		if (!g_IsRegistered) return; | 		g_ExceptionRegister.Unregister(); | ||||||
| 		SetUnhandledExceptionFilter(g_ProcBackup); |  | ||||||
| 		g_IsRegistered = false; |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ namespace YYCC::FsPathPatch { | |||||||
| 		return std::filesystem::path(wpath); | 		return std::filesystem::path(wpath); | ||||||
| 		 | 		 | ||||||
| #else | #else | ||||||
| 		return std::filesystem::path(EncodingHelper::ToNative(u8_path)); | 		return std::filesystem::path(EncodingHelper::ToOrdinary(u8_path)); | ||||||
| #endif | #endif | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | |||||||
| @ -16,13 +16,13 @@ namespace YYCC::ParserHelper { | |||||||
| 	template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0> | 	template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0> | ||||||
| 	bool TryParse(const yycc_u8string_view& strl, _Ty& num) { | 	bool TryParse(const yycc_u8string_view& strl, _Ty& num) { | ||||||
| 		auto [ptr, ec] = std::from_chars( | 		auto [ptr, ec] = std::from_chars( | ||||||
| 			EncodingHelper::ToNative(strl.data()),  | 			EncodingHelper::ToOrdinary(strl.data()),  | ||||||
| 			EncodingHelper::ToNative(strl.data() + strl.size()),  | 			EncodingHelper::ToOrdinary(strl.data() + strl.size()),  | ||||||
| 			num, std::chars_format::general | 			num, std::chars_format::general | ||||||
| 		); | 		); | ||||||
| 		if (ec == std::errc()) { | 		if (ec == std::errc()) { | ||||||
| 			// check whether the full string is matched | 			// 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) { | 		} else if (ec == std::errc::invalid_argument) { | ||||||
| 			// given string is invalid | 			// given string is invalid | ||||||
| 			return false; | 			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> | 	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) { | 	bool TryParse(const yycc_u8string_view& strl, _Ty& num, int base = 10) { | ||||||
| 		auto [ptr, ec] = std::from_chars( | 		auto [ptr, ec] = std::from_chars( | ||||||
| 			EncodingHelper::ToNative(strl.data()),  | 			EncodingHelper::ToOrdinary(strl.data()),  | ||||||
| 			EncodingHelper::ToNative(strl.data() + strl.size()),  | 			EncodingHelper::ToOrdinary(strl.data() + strl.size()),  | ||||||
| 			num, base | 			num, base | ||||||
| 		); | 		); | ||||||
| 		if (ec == std::errc()) { | 		if (ec == std::errc()) { | ||||||
| 			// check whether the full string is matched | 			// 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) { | 		} else if (ec == std::errc::invalid_argument) { | ||||||
| 			// given string is invalid | 			// given string is invalid | ||||||
| 			return false; | 			return false; | ||||||
| @ -76,8 +76,8 @@ namespace YYCC::ParserHelper { | |||||||
| 	yycc_u8string ToString(_Ty num) { | 	yycc_u8string ToString(_Ty num) { | ||||||
| 		std::array<yycc_char8_t, 64> buffer; | 		std::array<yycc_char8_t, 64> buffer; | ||||||
| 		auto [ptr, ec] = std::to_chars( | 		auto [ptr, ec] = std::to_chars( | ||||||
| 			EncodingHelper::ToNative(buffer.data()),  | 			EncodingHelper::ToOrdinary(buffer.data()),  | ||||||
| 			EncodingHelper::ToNative(buffer.data() + buffer.size()),  | 			EncodingHelper::ToOrdinary(buffer.data() + buffer.size()),  | ||||||
| 			num | 			num | ||||||
| 		); | 		); | ||||||
| 		if (ec == std::errc()) { | 		if (ec == std::errc()) { | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ namespace YYCC::StringHelper { | |||||||
| 		int count = std::vsnprintf( | 		int count = std::vsnprintf( | ||||||
| 			nullptr,  | 			nullptr,  | ||||||
| 			0,  | 			0,  | ||||||
| 			EncodingHelper::ToNative(format),  | 			EncodingHelper::ToOrdinary(format),  | ||||||
| 			args1 | 			args1 | ||||||
| 		); | 		); | ||||||
| 		if (count < 0) { | 		if (count < 0) { | ||||||
| @ -40,9 +40,9 @@ namespace YYCC::StringHelper { | |||||||
| 		// however std::vsnprintf already have a trailing NULL, so we plus 1 for it. | 		// however std::vsnprintf already have a trailing NULL, so we plus 1 for it. | ||||||
| 		strl.resize(count); | 		strl.resize(count); | ||||||
| 		int write_result = std::vsnprintf( | 		int write_result = std::vsnprintf( | ||||||
| 			EncodingHelper::ToNative(strl.data()), | 			EncodingHelper::ToOrdinary(strl.data()), | ||||||
| 			strl.size() + 1,  | 			strl.size() + 1,  | ||||||
| 			EncodingHelper::ToNative(format),  | 			EncodingHelper::ToOrdinary(format),  | ||||||
| 			args2 | 			args2 | ||||||
| 		); | 		); | ||||||
| 		va_end(args2); | 		va_end(args2); | ||||||
|  | |||||||
| @ -134,7 +134,8 @@ namespace YYCCTestbench { | |||||||
| 			Console::Write(YYCC_U8("\t> ")); | 			Console::Write(YYCC_U8("\t> ")); | ||||||
|  |  | ||||||
| 			YYCC::yycc_u8string gotten(Console::ReadLine()); | 			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_FloatSetting(YYCC_U8("float-setting"), 0.0f), | ||||||
| 			m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")), | 			m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")), | ||||||
| 			m_BoolSetting(YYCC_U8("bool-setting"), false), | 			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_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1), | ||||||
| 			m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), { | 			m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), { | ||||||
| 				&m_IntSetting, &m_FloatSetting, &m_StringSetting, &m_BoolSetting, &m_ClampedFloatSetting, &m_EnumSetting | 				&m_IntSetting, &m_FloatSetting, &m_StringSetting, &m_BoolSetting, &m_ClampedFloatSetting, &m_EnumSetting | ||||||
| @ -436,7 +437,7 @@ namespace YYCCTestbench { | |||||||
| 		// init cfg manager | 		// init cfg manager | ||||||
| 		TestConfigManager test; | 		TestConfigManager test; | ||||||
|  |  | ||||||
| 		// test constrain works | 		// test constraint works | ||||||
| 		Assert(!test.m_ClampedFloatSetting.Set(2.0f), YYCC_U8("YYCC::ConfigManager::Constraint")); | 		Assert(!test.m_ClampedFloatSetting.Set(2.0f), YYCC_U8("YYCC::ConfigManager::Constraint")); | ||||||
| 		Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::Constraint")); | 		Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::Constraint")); | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user