feat: add some work

- add dialog parameter class for dialog helper. prepare to write real dialog popup function.
- refactor file filters class in dialog helper to make it no const syntax conflict.
- improve windows header import prefix and suffix header. remove the barrier limiting headers only can be imported once. fix define preprocessor usage bug.
- move crt warning and errors macro from project settings to internal headers.
- copy IronPad as ExceptionHandler but not adapted to this project.
This commit is contained in:
yyc12345 2024-05-23 09:37:41 +08:00
parent 359aff82ac
commit 84228b5f8c
10 changed files with 500 additions and 70 deletions

View File

@ -27,53 +27,52 @@ namespace YYCC::DialogHelper {
return true;
}
void FileFilters::Clear() {
m_Filters.clear();
}
bool FileFilters::Generate(WinFileFilters& win_result) const {
// clear Windows oriented data
win_result.m_WinDataStruct.reset();
win_result.m_WinFilters.clear();
bool FileFilters::Generate(UINT& filter_count, COMDLG_FILTERSPEC*& filter_specs) {
// init defualt value to prevent the scenario that caller do not check return value.
filter_count = 0u;
filter_specs = nullptr;
// clear win string vector and build new one
m_WinFilters.clear();
// build new Windows oriented string vector first
for (const auto& it : m_Filters) {
// convert name to wchar
WinFilterName name;
WinFileFilters::WinFilterName name;
if (!YYCC::EncodingHelper::UTF8ToWchar(it.first.c_str(), name))
return false;
// convert pattern and join them
std::string joined_modes(YYCC::StringHelper::Join(it.second, u8";"));
WinFilterModes modes;
WinFileFilters::WinFilterModes modes;
if (!YYCC::EncodingHelper::UTF8ToWchar(joined_modes.c_str(), modes))
return false;
// append new pair
m_WinFilters.emplace_back(std::make_pair(name, modes));
win_result.m_WinFilters.emplace_back(std::make_pair(name, modes));
}
// check filter size
// if it overflow the maximum value, return false
size_t count = m_WinFilters.size();
size_t count = win_result.m_WinFilters.size();
if (count > std::numeric_limits<UINT>::max())
return false;
// create new win data struct
// and assign string pointer from internal built win string vector.
m_WinDataStruct.reset(new COMDLG_FILTERSPEC[count]);
win_result.m_WinDataStruct.reset(new COMDLG_FILTERSPEC[count]);
for (size_t i = 0u; i < count; ++i) {
m_WinDataStruct[i].pszName = m_WinFilters[i].first.c_str();
m_WinDataStruct[i].pszSpec = m_WinFilters[i].second.c_str();
win_result.m_WinDataStruct[i].pszName = win_result.m_WinFilters[i].first.c_str();
win_result.m_WinDataStruct[i].pszSpec = win_result.m_WinFilters[i].second.c_str();
}
// set return value
filter_count = static_cast<UINT>(count);
filter_specs = m_WinDataStruct.get();
// everything is okey
return true;
}
#pragma endregion
#pragma region FileDialog
#pragma endregion

View File

@ -15,15 +15,39 @@
namespace YYCC::DialogHelper {
/**
* @brief The class represent the file types region in file dialog
* @details THis class is specific for Windows use, not user oriented.
*/
class WinFileFilters {
friend class FileFilters;
public:
WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {}
UINT GetFilterCount() const {
return static_cast<UINT>(m_WinFilters.size());
}
const COMDLG_FILTERSPEC* GetFilterSpecs() const {
return m_WinDataStruct.get();
}
protected:
using WinFilterModes = std::wstring;
using WinFilterName = std::wstring;
using WinFilterPair = std::pair<WinFilterName, WinFilterModes>;
std::vector<WinFilterPair> m_WinFilters;
std::unique_ptr<COMDLG_FILTERSPEC[]> m_WinDataStruct;
};
/**
* @brief The class represent the file types region in file dialog.
* @details THis class is user oriented. User can use function manipulate file types
* and final fialog function will produce Windows-understood data struct from this.
*/
class FileFilters {
public:
FileFilters() :
m_Filters(),
m_WinFilters(), m_WinDataStruct(nullptr)
{}
FileFilters() : m_Filters(){}
/**
* @brief Add a filter pair in file types list.
@ -40,16 +64,19 @@ namespace YYCC::DialogHelper {
/**
* @brief Clear filter pairs for following re-use.
*/
void Clear();
void Clear() { m_Filters.clear(); }
/**
* @brief Get the count of added filter pairs.
* @return The count of already added filter pairs.
*/
size_t Count() const { return m_Filters.size(); }
/**
* @brief Generate Windows dialog system used data struct.
* @param filter_count[out] The count of generated filter data struct.
* @param filter_specs[out] The pointer to generated filter data struct.
* @remarks User should not call this function. This function is used by internal functions.
* @param win_result[out] The class holding the generated filter data struct.
* @return True if generation is success, otherwise false.
*/
bool Generate(UINT& filter_count, COMDLG_FILTERSPEC*& filter_specs);
bool Generate(WinFileFilters& win_result) const;
protected:
using FilterModes = std::vector<std::string>;
@ -57,45 +84,70 @@ namespace YYCC::DialogHelper {
using FilterPair = std::pair<FilterName, FilterModes>;
std::vector<FilterPair> m_Filters;
using WinFilterModes = std::wstring;
using WinFilterName = std::wstring;
using WinFilterPair = std::pair<WinFilterName, WinFilterModes>;
std::vector<WinFilterPair> m_WinFilters;
std::unique_ptr<COMDLG_FILTERSPEC[]> m_WinDataStruct;
};
//struct FileDialogParameter {
// FileDialogParameter() :
// m_Owner(nullptr),
// m_Filter(), m_SelectedFilter(0),
// m_Title(),
// m_DefaultExtension(), m_InitialDirectory(), m_InitialFileName() {}
class FileDialog {
public:
FileDialog() :
m_Owner(NULL), m_Title(),
m_FileTypes(),
m_DefaultFileTypeIndex(0u),
m_InitFileName(), m_InitDirectory() {}
// HWND m_Owner;
// std::vector<std::pair<std::string, std::string>> m_Filter;
// size_t m_SelectedFilter;
// std::string m_Title;
// std::string m_DefaultExtension;
// std::string m_InitialFileName;
// std::string m_InitialDirectory;
//};
void SetOwner(HWND owner) { m_Owner = owner; }
HWND GetOwner() const { return m_Owner; }
//struct FolderDialogParameter {
// FolderDialogParameter() :
// m_Owner(nullptr),
// m_Title() {}
void SetTitle(const char* title) {
if (title == nullptr) m_Title.clear();
else m_Title = title;
}
const char* GetTitle() const {
if (m_Title.empty()) return nullptr;
else return m_Title.c_str();
}
// HWND m_Owner;
// std::string m_Title;
//};
FileFilters& GetFileTypes() {
return m_FileTypes;
}
const FileFilters& GetFileTypes() const {
return m_FileTypes;
}
//bool OpenFileDialog(const FileDialogParameter& params, std::string& ret);
//bool OpenMultipleFileDialog(const FileDialogParameter& params, std::vector<std::string>& ret);
//bool SaveFileDialog(const FileDialogParameter& params, std::string& ret);
void SetDefaultFileTypeIndex(UINT idx) { m_DefaultFileTypeIndex = idx; }
UINT GetDefaultFileTypeIndex() const { return m_DefaultFileTypeIndex; }
//bool OpenFolderDialog(const FolderDialogParameter& params, std::string& ret);
void SetInitFileName(const char* init_filename) {
if (init_filename == nullptr) m_InitFileName.clear();
else m_InitFileName = init_filename;
}
const char* GetInitFileName() const {
if (m_InitFileName.empty()) return nullptr;
else return m_InitFileName.c_str();
}
void SetInitDirectory(const char* init_dir) {
if (init_dir == nullptr) m_InitDirectory.clear();
else m_InitDirectory = init_dir;
}
const char* GetInitDirectory() const {
if (m_InitDirectory.empty()) return nullptr;
else return m_InitDirectory.c_str();
}
private:
HWND m_Owner;
std::string m_Title;
FileFilters m_FileTypes;
UINT m_DefaultFileTypeIndex;
std::string m_InitFileName;
std::string m_InitDirectory;
};
bool OpenFileDialog(const FileDialog& params, std::string& ret);
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<std::string>& ret);
bool SaveFileDialog(const FileDialog& params, std::string& ret);
bool OpenFolderDialog(const FileDialog& params, std::string& ret);
}

325
src/ExceptionHelper.cpp Normal file
View File

@ -0,0 +1,325 @@
#include "ExceptionHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#include <filesystem>
#include <cstdarg>
#include <cstdio>
#include <cinttypes>
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include <DbgHelp.h>
#include "WinImportSuffix.hpp"
namespace YYCC::ExceptionHelper {
/**
* @brief true if the exception handler already registered.
* This variable is served for singleton.
*/
static bool g_IsRegistered = false;
/**
* @brief true if a exception handler is running.
* This variable is served for blocking possible infinity recursive exception handling.
*/
static bool g_IsProcessing = false;
/**
* @brief The backup of original exception handler.
*/
LPTOP_LEVEL_EXCEPTION_FILTER g_ProcBackup;
#pragma region Exception Handler Detail
static HMODULE GetCurrentModule() {
// Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code
HMODULE hModule = NULL;
GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address and do not inc ref counter.
(LPCWSTR)GetCurrentModule,
&hModule);
return hModule;
}
static const char* UExceptionGetCodeName(DWORD code) {
switch (code) {
case EXCEPTION_ACCESS_VIOLATION:
return "access violation";
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
return "array index out of bound";
case EXCEPTION_BREAKPOINT:
return "breakpoint reached";
case EXCEPTION_DATATYPE_MISALIGNMENT:
return "misaligned data access";
case EXCEPTION_FLT_DENORMAL_OPERAND:
return "operand had denormal value";
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
return "floating-point division by zero";
case EXCEPTION_FLT_INEXACT_RESULT:
return "no decimal fraction representation for value";
case EXCEPTION_FLT_INVALID_OPERATION:
return "invalid floating-point operation";
case EXCEPTION_FLT_OVERFLOW:
return "floating-point overflow";
case EXCEPTION_FLT_STACK_CHECK:
return "floating-point stack corruption";
case EXCEPTION_FLT_UNDERFLOW:
return "floating-point underflow";
case EXCEPTION_ILLEGAL_INSTRUCTION:
return "illegal instruction";
case EXCEPTION_IN_PAGE_ERROR:
return "inaccessible page";
case EXCEPTION_INT_DIVIDE_BY_ZERO:
return "integer division by zero";
case EXCEPTION_INT_OVERFLOW:
return "integer overflow";
case EXCEPTION_INVALID_DISPOSITION:
return "documentation says this should never happen";
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
return "can't continue after a noncontinuable exception";
case EXCEPTION_PRIV_INSTRUCTION:
return "attempted to execute a privileged instruction";
case EXCEPTION_SINGLE_STEP:
return "one instruction has been executed";
case EXCEPTION_STACK_OVERFLOW:
return "stack overflow";
default:
return "unknown exception";
}
}
static void UExceptionFormat(std::FILE* fs, const char* fmt, ...) {
// write to file
va_list arg1;
va_start(arg1, fmt);
std::vfprintf(fs, fmt, arg1);
va_end(arg1);
// write to stdout
va_list arg2;
va_start(arg2, fmt);
std::vfprintf(stdout, fmt, arg2);
va_end(arg2);
}
static void UExceptionPrint(std::FILE* fs, const char* strl) {
// write to file
std::fputs(strl, fs);
// write to stdout
std::fputs(strl, stdout);
}
static void UExceptionBacktrace(FILE* fs, LPCONTEXT context, int maxdepth) {
// setup loading symbol options
SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); // lazy load symbol, and load line number.
// setup handle
HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
// init symbol
if (!SymInitialize(process, 0, TRUE)) {
// fail to load. return
UExceptionPrint(fs, "Lost symbol file!\n");
return;
}
// ========== CORE DUMP ==========
// prepare frame. setup correct fields
// references:
// https://github.com/rust-lang/backtrace-rs/blob/9ed25b581cfd2ee60e5a3b9054fd023bf6dced90/src/backtrace/dbghelp.rs
// https://sourceforge.net/p/predef/wiki/Architectures/
DWORD machine_type = 0;
STACKFRAME64 frame;
memset(&frame, 0, sizeof(frame));
#if defined(_M_IX86) || defined(__i386__)
// x86
machine_type = IMAGE_FILE_MACHINE_I386;
frame.AddrPC.Offset = context->Eip;
frame.AddrStack.Offset = context->Esp;
frame.AddrFrame.Offset = context->Ebp;
#elif defined(_M_AMD64) || defined(__amd64__)
// amd64
machine_type = IMAGE_FILE_MACHINE_AMD64;
frame.AddrPC.Offset = context->Rip;
frame.AddrStack.Offset = context->Rsp;
frame.AddrFrame.Offset = context->Rbp;
#elif defined(_M_ARM) || defined(__arm__)
// arm (32bit)
machine_type = IMAGE_FILE_MACHINE_ARMNT;
frame.AddrPC.Offset = context->Pc;
frame.AddrStack.Offset = context->Sp;
frame.AddrFrame.Offset = context->R11;
#elif defined(_M_ARM64) || defined(__aarch64__)
// arm64
machine_type = IMAGE_FILE_MACHINE_ARM64;
frame.AddrPC.Offset = context->Pc;
frame.AddrStack.Offset = context->Sp;
frame.AddrFrame.Offset = context->DUMMYUNIONNAME.DUMMYSTRUCTNAME.Fp;
#else
#error "Unsupported platform"
//IA-64 anybody?
#endif
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrStack.Mode = AddrModeFlat;
frame.AddrFrame.Mode = AddrModeFlat;
// other variables
char module_name_raw[MAX_PATH];
// stack walker
while (StackWalk64(machine_type, process, thread, &frame, context,
0, SymFunctionTableAccess64, SymGetModuleBase64, 0)) {
// depth breaker
--maxdepth;
if (maxdepth < 0) {
UExceptionPrint(fs, "...\n"); // indicate there are some frames not listed
break;
}
// get module name
DWORD64 module_base = SymGetModuleBase64(process, frame.AddrPC.Offset);
const char* module_name = "[unknown module]";
if (module_base && GetModuleFileNameA((HINSTANCE)module_base, module_name_raw, MAX_PATH)) {
module_name = module_name_raw;
}
// get source file and line
const char* source_file = "[unknow_source_file]";
DWORD64 source_file_line = 0;
DWORD dwDisplacement;
IMAGEHLP_LINE64 winline;
winline.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &dwDisplacement, &winline)) {
source_file = winline.FileName;
source_file_line = winline.LineNumber;
}
// write to file
UExceptionFormat(fs, "0x%016llx(rel: 0x%016llx)[%s]\t%s#%llu\n",
frame.AddrPC.Offset, frame.AddrPC.Offset - module_base, module_name,
source_file, source_file_line
);
}
// ========== END CORE DUMP ==========
// free symbol
SymCleanup(process);
}
static void UExceptionCoreDump(LPCWSTR filename, LPEXCEPTION_POINTERS info) {
// open file and write
HANDLE hFile = CreateFileW(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION exception_info;
exception_info.ThreadId = GetCurrentThreadId();
exception_info.ExceptionPointers = info;
exception_info.ClientPointers = TRUE;
MiniDumpWriteDump(
GetCurrentProcess(), GetCurrentProcessId(), hFile,
MiniDumpNormal,
&exception_info,
NULL, NULL
);
CloseHandle(hFile);
}
}
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) {
// detect loop calling
if (g_IsProcessing) {
goto end_proc;
}
// start process
g_IsProcessing = true;
{
// get main folder first
std::filesystem::path ironpad_path;
WCHAR module_path[MAX_PATH];
std::memset(module_path, 0, sizeof(module_path));
if (GetModuleFileNameW(GetCurrentModule(), module_path, MAX_PATH) == 0) {
goto failed;
}
ironpad_path = module_path;
ironpad_path = ironpad_path.parent_path();
// create 2 filename
auto logfilename = ironpad_path / "IronPad.log";
auto dmpfilename = ironpad_path / "IronPad.dmp";
std::fputc('\n', stdout);
std::fprintf(stdout, "Exception Log: %s\n", logfilename.string().c_str());
std::fprintf(stdout, "Exception Coredump: %s\n", dmpfilename.string().c_str());
// output log file
{
std::FILE* fs = _wfopen(logfilename.wstring().c_str(), L"w");
if (fs == nullptr) {
goto failed;
}
// record exception type first
PEXCEPTION_RECORD rec = info->ExceptionRecord;
fprintf(fs, "Unhandled exception occured at 0x%08p: %s (%lu).\n",
rec->ExceptionAddress,
UExceptionGetCodeName(rec->ExceptionCode),
rec->ExceptionCode
);
// special proc for 2 exceptions
if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) {
if (rec->NumberParameters >= 2) {
const char* op =
rec->ExceptionInformation[0] == 0 ? "read" :
rec->ExceptionInformation[0] == 1 ? "written" : "executed";
fprintf(fs, "The data at memory address 0x%016" PRIxPTR " could not be %s.\n",
rec->ExceptionInformation[1], op);
}
}
// output stacktrace
UExceptionBacktrace(fs, info->ContextRecord, 1024);
std::fclose(fs);
}
// output minidump
{
UExceptionCoreDump(dmpfilename.wstring().c_str(), info);
}
}
// end process
failed:
g_IsProcessing = false;
// if backup proc can be run, run it
// otherwise directly return.
end_proc:
if (g_ProcBackup != nullptr) {
return g_ProcBackup(info);
} else {
return EXCEPTION_CONTINUE_SEARCH;
}
}
#pragma endregion
void Register() {
if (g_IsRegistered) return;
g_ProcBackup = SetUnhandledExceptionFilter(UExceptionImpl);
g_IsRegistered = true;
}
void Unregister() {
if (!g_IsRegistered) return;
SetUnhandledExceptionFilter(g_ProcBackup);
g_IsRegistered = false;
}
}
#endif

20
src/ExceptionHelper.hpp Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
namespace YYCC::ExceptionHelper {
/**
* @brief Register unhandled exception handler
* @detail This function frequently called at the start of program.
*/
void Register();
/**
* @brief Unregiister unhandled exception handler
* @detail This function frequently called at the end of program.
*/
void Unregister();
}
#endif

View File

@ -1,11 +1,19 @@
#pragma once
// It is by design that no pragma once or #if to prevent deplicated including.
// Because this header is the part of wrapper, not a real header.
// #pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
// Define 2 macros to disallow Windows generate MIN and MAX macros
// which cause std::min and std::max can not function as normal.
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#if !defined(NOMINMAX)
#define NOMINMAX
#endif
#endif

View File

@ -1,4 +1,7 @@
#pragma once
// It is by design that no pragma once or #if to prevent deplicated including.
// Because this header is the part of wrapper, not a real header.
// #pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
@ -6,6 +9,8 @@
// Windows also will generate following macros
// which may cause the function sign is different in Windows and other platforms.
// So we simply remove them.
// Because #undef will not throw error if there are no matched macro,
// so we simply #undef them directly.
#undef GetObject
#undef GetClassName
#undef LoadImage

View File

@ -10,6 +10,20 @@
#define YYCC_OS YYCC_OS_LINUX
#endif
// If we are in Windows,
// we need add 2 macros to disable Windows shitty warnings and errors of
// depracted functions and not secure functions.
#if YYCC_OS == YYCC_OS_WINDOWS
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#if !defined(_CRT_NONSTDC_NO_DEPRECATE)
#define _CRT_NONSTDC_NO_DEPRECATE
#endif
#endif
//// Decide the char type we used
//#include <string>
//namespace YYCC {

View File

@ -94,7 +94,7 @@
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
@ -110,7 +110,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
@ -126,7 +126,7 @@
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
@ -142,7 +142,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
@ -157,6 +157,7 @@
<ItemGroup>
<ClInclude Include="DialogHelper.hpp" />
<ClInclude Include="EncodingHelper.hpp" />
<ClInclude Include="ExceptionHelper.hpp" />
<ClInclude Include="IOHelper.hpp" />
<ClInclude Include="ParserHelper.hpp" />
<ClInclude Include="StringHelper.hpp" />
@ -169,6 +170,7 @@
<ItemGroup>
<ClCompile Include="DialogHelper.cpp" />
<ClCompile Include="EncodingHelper.cpp" />
<ClCompile Include="ExceptionHelper.cpp" />
<ClCompile Include="IOHelper.cpp" />
<ClCompile Include="ParserHelper.cpp" />
<ClCompile Include="StringHelper.cpp" />

View File

@ -45,6 +45,9 @@
<ClInclude Include="DialogHelper.hpp">
<Filter>Headers</Filter>
</ClInclude>
<ClInclude Include="ExceptionHelper.hpp">
<Filter>Headers</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="StringHelper.cpp">
@ -65,5 +68,8 @@
<ClCompile Include="DialogHelper.cpp">
<Filter>Sources</Filter>
</ClCompile>
<ClCompile Include="ExceptionHelper.cpp">
<Filter>Sources</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -60,9 +60,8 @@ namespace Testbench {
test.Add("Text File (*.*)", {"*.txt"});
test.Add("All Files (*.*)", {"*.*"});
UINT count;
COMDLG_FILTERSPEC* specs;
bool ret = test.Generate(count, specs);
YYCC::DialogHelper::WinFileFilters win_file_filters;
bool ret = test.Generate(win_file_filters);
}
}