From ab90abbae42e4d9b2e9bbc03859a3debdc6dc69e Mon Sep 17 00:00:00 2001 From: Court Cutting Date: Fri, 14 Jun 2024 13:16:58 -0700 Subject: [PATCH] Updated README.md --- Build/msvc_2022/SkinFlaps.sln | 66 + Build/msvc_2022/SkinFlaps.vcxproj.filters | 39 + Build/msvc_2022/imgui.ini | 139 +- README.md | 5 +- SkinFlaps/src/FacialFlapsGui.h | 94 +- SkinFlaps/src/WinReg.hpp | 2933 +++++++++++++++++++++ SkinFlaps/src/surgicalActions.cpp | 4 + SkinFlaps/src/vnBccTetCutter_tbb.cpp | 166 +- 8 files changed, 3347 insertions(+), 99 deletions(-) create mode 100644 Build/msvc_2022/SkinFlaps.vcxproj.filters create mode 100644 SkinFlaps/src/WinReg.hpp diff --git a/Build/msvc_2022/SkinFlaps.sln b/Build/msvc_2022/SkinFlaps.sln index 3189a77..3176995 100644 --- a/Build/msvc_2022/SkinFlaps.sln +++ b/Build/msvc_2022/SkinFlaps.sln @@ -27,48 +27,114 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SkinFlaps", "SkinFlaps.vcxp EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Debug|Any CPU.ActiveCfg = Debug|x64 + {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Debug|Any CPU.Build.0 = Debug|x64 + {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Debug|ARM.ActiveCfg = Debug|x64 + {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Debug|ARM.Build.0 = Debug|x64 + {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Debug|ARM64.ActiveCfg = Debug|x64 + {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Debug|ARM64.Build.0 = Debug|x64 {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Debug|x64.ActiveCfg = Debug|x64 {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Debug|x64.Build.0 = Debug|x64 {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Debug|x86.ActiveCfg = Debug|Win32 {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Debug|x86.Build.0 = Debug|Win32 + {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Release|Any CPU.ActiveCfg = Release|x64 + {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Release|Any CPU.Build.0 = Release|x64 + {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Release|ARM.ActiveCfg = Release|x64 + {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Release|ARM.Build.0 = Release|x64 + {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Release|ARM64.ActiveCfg = Release|x64 + {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Release|ARM64.Build.0 = Release|x64 {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Release|x64.ActiveCfg = Release|x64 {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Release|x64.Build.0 = Release|x64 {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Release|x86.ActiveCfg = Release|Win32 {DEB9C50E-C72F-42AF-A881-66ED5D9654A9}.Release|x86.Build.0 = Release|Win32 + {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Debug|Any CPU.ActiveCfg = Debug|x64 + {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Debug|Any CPU.Build.0 = Debug|x64 + {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Debug|ARM.ActiveCfg = Debug|x64 + {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Debug|ARM.Build.0 = Debug|x64 + {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Debug|ARM64.ActiveCfg = Debug|x64 + {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Debug|ARM64.Build.0 = Debug|x64 {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Debug|x64.ActiveCfg = Debug|x64 {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Debug|x64.Build.0 = Debug|x64 {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Debug|x86.ActiveCfg = Debug|Win32 {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Debug|x86.Build.0 = Debug|Win32 + {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Release|Any CPU.ActiveCfg = Release|x64 + {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Release|Any CPU.Build.0 = Release|x64 + {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Release|ARM.ActiveCfg = Release|x64 + {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Release|ARM.Build.0 = Release|x64 + {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Release|ARM64.ActiveCfg = Release|x64 + {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Release|ARM64.Build.0 = Release|x64 {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Release|x64.ActiveCfg = Release|x64 {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Release|x64.Build.0 = Release|x64 {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Release|x86.ActiveCfg = Release|Win32 {93FD1C29-F122-48B5-B86A-6BFA7379E503}.Release|x86.Build.0 = Release|Win32 + {7F05671F-7805-4541-9EC9-31ED72931CFE}.Debug|Any CPU.ActiveCfg = Debug|x64 + {7F05671F-7805-4541-9EC9-31ED72931CFE}.Debug|Any CPU.Build.0 = Debug|x64 + {7F05671F-7805-4541-9EC9-31ED72931CFE}.Debug|ARM.ActiveCfg = Debug|x64 + {7F05671F-7805-4541-9EC9-31ED72931CFE}.Debug|ARM.Build.0 = Debug|x64 + {7F05671F-7805-4541-9EC9-31ED72931CFE}.Debug|ARM64.ActiveCfg = Debug|x64 + {7F05671F-7805-4541-9EC9-31ED72931CFE}.Debug|ARM64.Build.0 = Debug|x64 {7F05671F-7805-4541-9EC9-31ED72931CFE}.Debug|x64.ActiveCfg = Debug|x64 {7F05671F-7805-4541-9EC9-31ED72931CFE}.Debug|x64.Build.0 = Debug|x64 {7F05671F-7805-4541-9EC9-31ED72931CFE}.Debug|x86.ActiveCfg = Debug|Win32 {7F05671F-7805-4541-9EC9-31ED72931CFE}.Debug|x86.Build.0 = Debug|Win32 + {7F05671F-7805-4541-9EC9-31ED72931CFE}.Release|Any CPU.ActiveCfg = Release|x64 + {7F05671F-7805-4541-9EC9-31ED72931CFE}.Release|Any CPU.Build.0 = Release|x64 + {7F05671F-7805-4541-9EC9-31ED72931CFE}.Release|ARM.ActiveCfg = Release|x64 + {7F05671F-7805-4541-9EC9-31ED72931CFE}.Release|ARM.Build.0 = Release|x64 + {7F05671F-7805-4541-9EC9-31ED72931CFE}.Release|ARM64.ActiveCfg = Release|x64 + {7F05671F-7805-4541-9EC9-31ED72931CFE}.Release|ARM64.Build.0 = Release|x64 {7F05671F-7805-4541-9EC9-31ED72931CFE}.Release|x64.ActiveCfg = Release|x64 {7F05671F-7805-4541-9EC9-31ED72931CFE}.Release|x64.Build.0 = Release|x64 {7F05671F-7805-4541-9EC9-31ED72931CFE}.Release|x86.ActiveCfg = Release|Win32 {7F05671F-7805-4541-9EC9-31ED72931CFE}.Release|x86.Build.0 = Release|Win32 + {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Debug|Any CPU.ActiveCfg = Debug|x64 + {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Debug|Any CPU.Build.0 = Debug|x64 + {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Debug|ARM.ActiveCfg = Debug|x64 + {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Debug|ARM.Build.0 = Debug|x64 + {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Debug|ARM64.ActiveCfg = Debug|x64 + {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Debug|ARM64.Build.0 = Debug|x64 {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Debug|x64.ActiveCfg = Debug|x64 {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Debug|x64.Build.0 = Debug|x64 {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Debug|x86.ActiveCfg = Debug|x64 {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Debug|x86.Build.0 = Debug|x64 + {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Release|Any CPU.ActiveCfg = Release|x64 + {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Release|Any CPU.Build.0 = Release|x64 + {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Release|ARM.ActiveCfg = Release|x64 + {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Release|ARM.Build.0 = Release|x64 + {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Release|ARM64.ActiveCfg = Release|x64 + {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Release|ARM64.Build.0 = Release|x64 {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Release|x64.ActiveCfg = Release|x64 {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Release|x64.Build.0 = Release|x64 {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Release|x86.ActiveCfg = Release|x64 {93364FFE-71F2-4B8F-A597-5F2A1C5FDDDB}.Release|x86.Build.0 = Release|x64 + {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Debug|Any CPU.ActiveCfg = Debug|x64 + {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Debug|Any CPU.Build.0 = Debug|x64 + {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Debug|ARM.ActiveCfg = Debug|x64 + {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Debug|ARM.Build.0 = Debug|x64 + {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Debug|ARM64.ActiveCfg = Debug|x64 + {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Debug|ARM64.Build.0 = Debug|x64 {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Debug|x64.ActiveCfg = Debug|x64 {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Debug|x64.Build.0 = Debug|x64 {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Debug|x86.ActiveCfg = Debug|Win32 {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Debug|x86.Build.0 = Debug|Win32 + {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Release|Any CPU.ActiveCfg = Release|x64 + {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Release|Any CPU.Build.0 = Release|x64 + {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Release|ARM.ActiveCfg = Release|x64 + {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Release|ARM.Build.0 = Release|x64 + {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Release|ARM64.ActiveCfg = Release|x64 + {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Release|ARM64.Build.0 = Release|x64 {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Release|x64.ActiveCfg = Release|x64 {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Release|x64.Build.0 = Release|x64 {50DC4134-5B3C-4FA7-8E82-F5A44422A1C5}.Release|x86.ActiveCfg = Release|Win32 diff --git a/Build/msvc_2022/SkinFlaps.vcxproj.filters b/Build/msvc_2022/SkinFlaps.vcxproj.filters new file mode 100644 index 0000000..82a9959 --- /dev/null +++ b/Build/msvc_2022/SkinFlaps.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Build/msvc_2022/imgui.ini b/Build/msvc_2022/imgui.ini index 56727c7..714f43c 100644 --- a/Build/msvc_2022/imgui.ini +++ b/Build/msvc_2022/imgui.ini @@ -155,7 +155,7 @@ Collapsed=0 [Window][Program error] Pos=138,41 -Size=240,71 +Size=276,71 Collapsed=0 [Window][Usage error] @@ -243,6 +243,11 @@ Pos=60,60 Size=32,35 Collapsed=0 +[Window][History Save Error] +Pos=60,60 +Size=367,97 +Collapsed=0 + [Table][0xC62877A3,4] RefScale=13 Column 0 Sort=0v @@ -8255,3 +8260,135 @@ Column 0 Sort=0v RefScale=13 Column 0 Sort=0v +[Table][0x12F03CF4,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x750D44D1,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x730858BD,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x93ACB5E1,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x0AD9E112,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0xDA9301BE,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x0E782EEE,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x81D67DE4,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x553D52B4,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0xD2D03ABE,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x063B15EE,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x94C2362F,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x4029197F,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0xE65EC846,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x32B5E716,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x60B83EA3,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x287C9ED5,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x81357E24,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x86C0FC90,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x3514AF2A,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x9C5D4FDB,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0xB977E7C2,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x6A59F341,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x18DFA68E,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0xB985A0D9,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0xC4276F78,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0xB568CC2B,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0xE8A27683,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x055B9F75,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0xD1B0B025,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0x7846B7A5,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0xA946A911,4] +RefScale=13 +Column 0 Sort=0v + +[Table][0xF786DC74,4] +RefScale=13 +Column 0 Sort=0v + diff --git a/README.md b/README.md index 0bd8ae0..6975869 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,9 @@ A YouTube video by Dr. Cutting will demonstrate how to use the program in *[User ##### Surgical physics library - Qisi Wang and Eftychios Sifakis -##### Surgical interface, tools an graphics - @ccuttingmd +##### Surgical interface, tools and graphics - @ccuttingmd +#### The authors wish to thank the team at AdvancedInstaller Inc. for the use of their excellent Windows software installer. ### **License** @@ -61,7 +62,7 @@ A YouTube video by Dr. Cutting will demonstrate how to use the program in *[User - Copyright 2014-2022 Qisi Wang, Court Cutting, Eftychios Sifakis + Copyright 2014-2024 Qisi Wang, Court Cutting, Eftychios Sifakis Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met (BSD-2-Clause license): diff --git a/SkinFlaps/src/FacialFlapsGui.h b/SkinFlaps/src/FacialFlapsGui.h index 22fa3ba..a9cce3d 100644 --- a/SkinFlaps/src/FacialFlapsGui.h +++ b/SkinFlaps/src/FacialFlapsGui.h @@ -494,24 +494,90 @@ class FacialFlapsGui { modelFile = modelFileName; } + static std::wstring RegGetString(HKEY hKey, const std::wstring& subKey, const std::wstring& value) + { + DWORD dataSize{}; + // First call to get dataSize + LONG retCode = ::RegGetValue( + hKey, + subKey.c_str(), + value.c_str(), + RRF_RT_REG_SZ, + nullptr, + nullptr, + &dataSize + ); + + if (retCode < ERROR_SUCCESS) + { + return std::wstring(); + } + + std::wstring data; + data.resize(dataSize / sizeof(wchar_t)); + + retCode = ::RegGetValue( + hKey, + subKey.c_str(), + value.c_str(), + RRF_RT_REG_SZ, + nullptr, + &data[0], + &dataSize + ); + + if (retCode != ERROR_SUCCESS) + { + return std::wstring(); + } + + DWORD stringLengthInWchars = dataSize / sizeof(wchar_t); + stringLengthInWchars--; // Exclude the NUL written by the Win32 API + data.resize(stringLengthInWchars); + + return data; + } + + static void setDefaultDirectories() { if (historyDirectory.empty() || modelDirectory.empty()) { - char buff[400]; - GetCurrentDir(buff, 400); - modelDirectory.assign(buff); - size_t pos = modelDirectory.rfind("Build"); - if (pos == std::string::npos) { // not part of program build. Use install dir. - historyDirectory = "C:\\Users\\SkinFlaps"; - modelDirectory = "C:\\Users\\SkinFlaps"; + char buff[200]; + HKEY hKey = HKEY_LOCAL_MACHINE; + std::wstring ret, subKey = L"SOFTWARE\\SkinFlaps", value = L"ModelDir"; + ret = RegGetString(hKey, subKey, value); + if (!ret.empty()) { + size_t i; + wcstombs_s(&i, buff, (size_t)200, ret.c_str(), (size_t)199); // -1 so the appended NULL doesn't fall outside the allocated buffer + modelDirectory = buff; } - else { // doing program building and testing - std::string projectFolder = "SkinFlaps"; - pos = modelDirectory.rfind(projectFolder); - modelDirectory.erase(modelDirectory.begin() + pos + projectFolder.size(), modelDirectory.end()); - historyDirectory = modelDirectory; + else + modelDirectory.clear(); + subKey = L"SOFTWARE\\SkinFlaps", value = L"HistoryDir"; + ret = RegGetString(hKey, subKey, value); + if (!ret.empty()) { + size_t i; + wcstombs_s(&i, buff, (size_t)200, ret.c_str(), (size_t)199); // -1 so the appended NULL doesn't fall outside the allocated buffer + historyDirectory = buff; + } + else + historyDirectory.clear(); + if (modelDirectory.empty() || historyDirectory.empty()) { + GetCurrentDir(buff, 200); + modelDirectory.assign(buff); + size_t pos = modelDirectory.rfind("Build"); + if (pos == std::string::npos) { // not part of program build. Use install dir. + historyDirectory = "C:\\Users\\SkinFlaps"; + modelDirectory = "C:\\ProgramData\\SkinFlaps"; + } + else { // doing program building and testing + std::string projectFolder = "SkinFlaps"; + pos = modelDirectory.rfind(projectFolder); + modelDirectory.erase(modelDirectory.begin() + pos + projectFolder.size(), modelDirectory.end()); + historyDirectory = modelDirectory; + } + modelDirectory.append("\\Model\\"); + historyDirectory.append("\\History\\"); } - modelDirectory.append("\\Model\\"); - historyDirectory.append("\\History\\"); igSurgAct.setModelDirectory(modelDirectory.c_str()); igSurgAct.setHistoryDirectory(historyDirectory.c_str()); } diff --git a/SkinFlaps/src/WinReg.hpp b/SkinFlaps/src/WinReg.hpp new file mode 100644 index 0000000..26905b6 --- /dev/null +++ b/SkinFlaps/src/WinReg.hpp @@ -0,0 +1,2933 @@ +#ifndef GIOVANNI_DICANIO_WINREG_HPP_INCLUDED +#define GIOVANNI_DICANIO_WINREG_HPP_INCLUDED + + +//////////////////////////////////////////////////////////////////////////////// +// +// *** Modern C++ Wrappers Around Windows Registry C API *** +// +// Copyright (C) by Giovanni Dicanio +// +// First version: 2017, January 22nd +// Last update: 2024, February 22nd +// +// E-mail: . AT REMOVE_THIS gmail.com +// +// Registry key handles are safely and conveniently wrapped +// in the RegKey resource manager C++ class. +// +// Many methods are available in two forms: +// +// - One form that signals errors throwing exceptions +// of class RegException (e.g. RegKey::Open) +// +// - Another form that returns RegResult objects (e.g. RegKey::TryOpen) +// +// In addition, there are also some methods named like TryGet...Value +// (e.g. TryGetDwordValue), that _try_ to perform the given query, +// and return a RegExpected object. On success, that object contains +// the value read from the registry. On failure, the returned RegExpected object +// contains a RegResult storing the return code from the Windows Registry API call. +// +// Unicode UTF-16 strings are represented using the std::wstring class; +// ATL's CString is not used, to avoid dependencies from ATL or MFC. +// +// Compiler: Visual Studio 2019 +// C++ Language Standard: C++17 (/std:c++17) +// Code compiles cleanly at warning level 4 (/W4) on both 32-bit and 64-bit builds. +// +// Requires building in Unicode mode (which has been the default since VS2005). +// +// =========================================================================== +// +// The MIT License(MIT) +// +// Copyright(c) 2017-2024 by Giovanni Dicanio +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//////////////////////////////////////////////////////////////////////////////// + + +#include // Windows Platform SDK +#include // _ASSERTE + +#include // std::unique_ptr, std::make_unique +#include // std::wstring +#include // std::system_error +#include // std::swap, std::pair, std::move +#include // std::variant +#include // std::vector + + +namespace winreg +{ + +// +// Forward Class Declarations +// + +class RegException; +class RegResult; + +template +class RegExpected; + + +// +// Class Declarations +// + +//------------------------------------------------------------------------------ +// +// Safe, efficient and convenient C++ wrapper around HKEY registry key handles. +// +// This class is movable but not copyable. +// +// This class is designed to be very *efficient* and low-overhead, for example: +// non-throwing operations are carefully marked as noexcept, so the C++ compiler +// can emit optimized code. +// +// Moreover, this class just wraps a raw HKEY handle, without any +// shared-ownership overhead like in std::shared_ptr; you can think of this +// class kind of like a std::unique_ptr for HKEYs. +// +// The class is also swappable (defines a custom non-member swap); +// relational operators are properly overloaded as well. +// +//------------------------------------------------------------------------------ +class RegKey +{ +public: + + // + // Construction/Destruction + // + + // Initialize as an empty key handle + RegKey() noexcept = default; + + // Take ownership of the input key handle + explicit RegKey(HKEY hKey) noexcept; + + // Open the given registry key if it exists, else create a new key. + // Uses default KEY_READ|KEY_WRITE|KEY_WOW64_64KEY access. + // For finer grained control, call the Create() method overloads. + // Throw RegException on failure. + RegKey(HKEY hKeyParent, const std::wstring& subKey); + + // Open the given registry key if it exists, else create a new key. + // Allow the caller to specify the desired access to the key + // (e.g. KEY_READ|KEY_WOW64_64KEY for read-only access). + // For finer grained control, call the Create() method overloads. + // Throw RegException on failure. + RegKey(HKEY hKeyParent, const std::wstring& subKey, REGSAM desiredAccess); + + + // Take ownership of the input key handle. + // The input key handle wrapper is reset to an empty state. + RegKey(RegKey&& other) noexcept; + + // Move-assign from the input key handle. + // Properly check against self-move-assign (which is safe and does nothing). + RegKey& operator=(RegKey&& other) noexcept; + + // Ban copy + RegKey(const RegKey&) = delete; + RegKey& operator=(const RegKey&) = delete; + + // Safely close the wrapped key handle (if any) + ~RegKey() noexcept; + + + // + // Properties + // + + // Access the wrapped raw HKEY handle + [[nodiscard]] HKEY Get() const noexcept; + + // Is the wrapped HKEY handle valid? + [[nodiscard]] bool IsValid() const noexcept; + + // Same as IsValid(), but allow a short "if (regKey)" syntax + [[nodiscard]] explicit operator bool() const noexcept; + + // Is the wrapped handle a predefined handle (e.g.HKEY_CURRENT_USER) ? + [[nodiscard]] bool IsPredefined() const noexcept; + + + // + // Operations + // + + // Close current HKEY handle. + // If there's no valid handle, do nothing. + // This method doesn't close predefined HKEY handles (e.g. HKEY_CURRENT_USER). + void Close() noexcept; + + // Transfer ownership of current HKEY to the caller. + // Note that the caller is responsible for closing the key handle! + [[nodiscard]] HKEY Detach() noexcept; + + // Take ownership of the input HKEY handle. + // Safely close any previously open handle. + // Input key handle can be nullptr. + void Attach(HKEY hKey) noexcept; + + // Non-throwing swap; + // Note: There's also a non-member swap overload + void SwapWith(RegKey& other) noexcept; + + + // + // Wrappers around Windows Registry APIs. + // See the official MSDN documentation for these APIs for detailed explanations + // of the wrapper method parameters. + // + + // + // NOTE on the KEY_WOW64_64KEY flag + // ================================ + // + // By default, a 32-bit application running on 64-bit Windows accesses the 32-bit registry view + // and a 64-bit application accesses the 64-bit registry view. + // Using this KEY_WOW64_64KEY flag, both 32-bit or 64-bit applications access the 64-bit + // registry view. + // + // MSDN documentation: + // https://docs.microsoft.com/en-us/windows/win32/winprog64/accessing-an-alternate-registry-view + // + // If you want to use the default Windows API behavior, don't OR (|) the KEY_WOW64_64KEY flag + // when specifying the desired access (e.g. just pass KEY_READ | KEY_WRITE as the desired + // access parameter). + // + + // Wrapper around RegCreateKeyEx, that allows you to specify desired access + void Create( + HKEY hKeyParent, + const std::wstring& subKey, + REGSAM desiredAccess = KEY_READ | KEY_WRITE | KEY_WOW64_64KEY + ); + + // Wrapper around RegCreateKeyEx + void Create( + HKEY hKeyParent, + const std::wstring& subKey, + REGSAM desiredAccess, + DWORD options, + SECURITY_ATTRIBUTES* securityAttributes, + DWORD* disposition + ); + + // Wrapper around RegOpenKeyEx + void Open( + HKEY hKeyParent, + const std::wstring& subKey, + REGSAM desiredAccess = KEY_READ | KEY_WRITE | KEY_WOW64_64KEY + ); + + // Wrapper around RegCreateKeyEx, that allows you to specify desired access + [[nodiscard]] RegResult TryCreate( + HKEY hKeyParent, + const std::wstring& subKey, + REGSAM desiredAccess = KEY_READ | KEY_WRITE | KEY_WOW64_64KEY + ) noexcept; + + // Wrapper around RegCreateKeyEx + [[nodiscard]] RegResult TryCreate( + HKEY hKeyParent, + const std::wstring& subKey, + REGSAM desiredAccess, + DWORD options, + SECURITY_ATTRIBUTES* securityAttributes, + DWORD* disposition + ) noexcept; + + // Wrapper around RegOpenKeyEx + [[nodiscard]] RegResult TryOpen( + HKEY hKeyParent, + const std::wstring& subKey, + REGSAM desiredAccess = KEY_READ | KEY_WRITE | KEY_WOW64_64KEY + ) noexcept; + + + // + // Registry Value Setters + // + + void SetDwordValue(const std::wstring& valueName, DWORD data); + void SetQwordValue(const std::wstring& valueName, const ULONGLONG& data); + void SetStringValue(const std::wstring& valueName, const std::wstring& data); + void SetExpandStringValue(const std::wstring& valueName, const std::wstring& data); + void SetMultiStringValue(const std::wstring& valueName, const std::vector& data); + void SetBinaryValue(const std::wstring& valueName, const std::vector& data); + void SetBinaryValue(const std::wstring& valueName, const void* data, DWORD dataSize); + + + // + // Registry Value Setters Returning RegResult + // (instead of throwing RegException on error) + // + + [[nodiscard]] RegResult TrySetDwordValue(const std::wstring& valueName, DWORD data) noexcept; + + [[nodiscard]] RegResult TrySetQwordValue(const std::wstring& valueName, + const ULONGLONG& data) noexcept; + + [[nodiscard]] RegResult TrySetStringValue(const std::wstring& valueName, + const std::wstring& data) noexcept; + + [[nodiscard]] RegResult TrySetExpandStringValue(const std::wstring& valueName, + const std::wstring& data) noexcept; + + [[nodiscard]] RegResult TrySetMultiStringValue(const std::wstring& valueName, + const std::vector& data); + // Note: The TrySetMultiStringValue method CANNOT be marked noexcept, + // because internally the method *dynamically allocates memory* for creating the multi-string + // that will be stored in the Registry. + + [[nodiscard]] RegResult TrySetBinaryValue(const std::wstring& valueName, + const std::vector& data) noexcept; + + [[nodiscard]] RegResult TrySetBinaryValue(const std::wstring& valueName, + const void* data, + DWORD dataSize) noexcept; + + + // + // Registry Value Getters + // + + [[nodiscard]] DWORD GetDwordValue(const std::wstring& valueName) const; + [[nodiscard]] ULONGLONG GetQwordValue(const std::wstring& valueName) const; + [[nodiscard]] std::wstring GetStringValue(const std::wstring& valueName) const; + + enum class ExpandStringOption + { + DontExpand, + Expand + }; + + [[nodiscard]] std::wstring GetExpandStringValue( + const std::wstring& valueName, + ExpandStringOption expandOption = ExpandStringOption::DontExpand + ) const; + + [[nodiscard]] std::vector GetMultiStringValue(const std::wstring& valueName) const; + [[nodiscard]] std::vector GetBinaryValue(const std::wstring& valueName) const; + + + // + // Registry Value Getters Returning RegExpected + // (instead of throwing RegException on error) + // + + [[nodiscard]] RegExpected TryGetDwordValue(const std::wstring& valueName) const; + [[nodiscard]] RegExpected TryGetQwordValue(const std::wstring& valueName) const; + [[nodiscard]] RegExpected TryGetStringValue(const std::wstring& valueName) const; + + [[nodiscard]] RegExpected TryGetExpandStringValue( + const std::wstring& valueName, + ExpandStringOption expandOption = ExpandStringOption::DontExpand + ) const; + + [[nodiscard]] RegExpected> + TryGetMultiStringValue(const std::wstring& valueName) const; + + [[nodiscard]] RegExpected> + TryGetBinaryValue(const std::wstring& valueName) const; + + + // + // Query Operations + // + + // Information about a registry key (retrieved by QueryInfoKey) + struct InfoKey + { + DWORD NumberOfSubKeys; + DWORD NumberOfValues; + FILETIME LastWriteTime; + + // Clear the structure fields + InfoKey() noexcept + : NumberOfSubKeys{0} + , NumberOfValues{0} + { + LastWriteTime.dwHighDateTime = LastWriteTime.dwLowDateTime = 0; + } + + InfoKey(DWORD numberOfSubKeys, DWORD numberOfValues, FILETIME lastWriteTime) noexcept + : NumberOfSubKeys{ numberOfSubKeys } + , NumberOfValues{ numberOfValues } + , LastWriteTime{ lastWriteTime } + { + } + }; + + // Retrieve information about the registry key + [[nodiscard]] InfoKey QueryInfoKey() const; + + // Return the DWORD type ID for the input registry value + [[nodiscard]] DWORD QueryValueType(const std::wstring& valueName) const; + + + enum class KeyReflection + { + ReflectionEnabled, + ReflectionDisabled + }; + + // Determines whether reflection has been disabled or enabled for the specified key + [[nodiscard]] KeyReflection QueryReflectionKey() const; + + // Enumerate the subkeys of the registry key, using RegEnumKeyEx + [[nodiscard]] std::vector EnumSubKeys() const; + + // Enumerate the values under the registry key, using RegEnumValue. + // Returns a vector of pairs: In each pair, the wstring is the value name, + // the DWORD is the value type. + [[nodiscard]] std::vector> EnumValues() const; + + + // + // Query Operations Returning RegExpected + // (instead of throwing RegException on error) + // + + // Retrieve information about the registry key + [[nodiscard]] RegExpected TryQueryInfoKey() const; + + // Return the DWORD type ID for the input registry value + [[nodiscard]] RegExpected TryQueryValueType(const std::wstring& valueName) const; + + + // Determines whether reflection has been disabled or enabled for the specified key + [[nodiscard]] RegExpected TryQueryReflectionKey() const; + + // Enumerate the subkeys of the registry key, using RegEnumKeyEx + [[nodiscard]] RegExpected> TryEnumSubKeys() const; + + // Enumerate the values under the registry key, using RegEnumValue. + // Returns a vector of pairs: In each pair, the wstring is the value name, + // the DWORD is the value type. + [[nodiscard]] RegExpected>> TryEnumValues() const; + + + // + // Misc Registry API Wrappers + // + + void DeleteValue(const std::wstring& valueName); + void DeleteKey(const std::wstring& subKey, REGSAM desiredAccess); + void DeleteTree(const std::wstring& subKey); + void CopyTree(const std::wstring& sourceSubKey, const RegKey& destKey); + void FlushKey(); + void LoadKey(const std::wstring& subKey, const std::wstring& filename); + void SaveKey(const std::wstring& filename, SECURITY_ATTRIBUTES* securityAttributes) const; + void EnableReflectionKey(); + void DisableReflectionKey(); + void ConnectRegistry(const std::wstring& machineName, HKEY hKeyPredefined); + + + // + // Misc Registry API Wrappers Returning RegResult Status + // (instead of throwing RegException on error) + // + + [[nodiscard]] RegResult TryDeleteValue(const std::wstring& valueName) noexcept; + [[nodiscard]] RegResult TryDeleteKey(const std::wstring& subKey, REGSAM desiredAccess) noexcept; + [[nodiscard]] RegResult TryDeleteTree(const std::wstring& subKey) noexcept; + + [[nodiscard]] RegResult TryCopyTree(const std::wstring& sourceSubKey, + const RegKey& destKey) noexcept; + + [[nodiscard]] RegResult TryFlushKey() noexcept; + + [[nodiscard]] RegResult TryLoadKey(const std::wstring& subKey, + const std::wstring& filename) noexcept; + + [[nodiscard]] RegResult TrySaveKey(const std::wstring& filename, + SECURITY_ATTRIBUTES* securityAttributes) const noexcept; + + [[nodiscard]] RegResult TryEnableReflectionKey() noexcept; + [[nodiscard]] RegResult TryDisableReflectionKey() noexcept; + + [[nodiscard]] RegResult TryConnectRegistry(const std::wstring& machineName, + HKEY hKeyPredefined) noexcept; + + + // Return a string representation of Windows registry types + [[nodiscard]] static std::wstring RegTypeToString(DWORD regType); + + + // + // Relational comparison operators are overloaded as non-members + // ==, !=, <, <=, >, >= + // + + + // + // Private Implementation + // + +private: + // The wrapped registry key handle + HKEY m_hKey{ nullptr }; +}; + + +//------------------------------------------------------------------------------ +// An exception representing an error with the registry operations +//------------------------------------------------------------------------------ +class RegException + : public std::system_error +{ +public: + RegException(LSTATUS errorCode, const char* message); + RegException(LSTATUS errorCode, const std::string& message); +}; + + +//------------------------------------------------------------------------------ +// A tiny wrapper around LSTATUS return codes used by the Windows Registry API. +//------------------------------------------------------------------------------ +class RegResult +{ +public: + + // Initialize to success code (ERROR_SUCCESS) + RegResult() noexcept = default; + + // Initialize with specific Windows Registry API LSTATUS return code + explicit RegResult(LSTATUS result) noexcept; + + // Is the wrapped code a success code? + [[nodiscard]] bool IsOk() const noexcept; + + // Is the wrapped error code a failure code? + [[nodiscard]] bool Failed() const noexcept; + + // Is the wrapped code a success code? + [[nodiscard]] explicit operator bool() const noexcept; + + // Get the wrapped Win32 code + [[nodiscard]] LSTATUS Code() const noexcept; + + // Return the system error message associated to the current error code + [[nodiscard]] std::wstring ErrorMessage() const; + + // Return the system error message associated to the current error code, + // using the given input language identifier + [[nodiscard]] std::wstring ErrorMessage(DWORD languageId) const; + +private: + // Error code returned by Windows Registry C API; + // default initialized to success code. + LSTATUS m_result{ ERROR_SUCCESS }; +}; + + +//------------------------------------------------------------------------------ +// A class template that stores a value of type T (e.g. DWORD, std::wstring) +// on success, or a RegResult on error. +// +// Used as the return value of some Registry RegKey::TryGetXxxValue() methods +// as an alternative to exception-throwing methods. +//------------------------------------------------------------------------------ +template +class RegExpected +{ +public: + // Initialize the object with an error code + explicit RegExpected(const RegResult& errorCode) noexcept; + + // Initialize the object with a value (the success case) + explicit RegExpected(const T& value); + + // Initialize the object with a value (the success case), + // optimized for move semantics + explicit RegExpected(T&& value); + + // Does this object contain a valid value? + [[nodiscard]] explicit operator bool() const noexcept; + + // Does this object contain a valid value? + [[nodiscard]] bool IsValid() const noexcept; + + // Access the value (if the object contains a valid value). + // Throws an exception if the object is in invalid state. + [[nodiscard]] const T& GetValue() const; + + // Access the error code (if the object contains an error status) + // Throws an exception if the object is in valid state. + [[nodiscard]] RegResult GetError() const; + + +private: + // Stores a value of type T on success, + // or RegResult on error + std::variant m_var; +}; + + +//------------------------------------------------------------------------------ +// Overloads of relational comparison operators for RegKey +//------------------------------------------------------------------------------ + +inline bool operator==(const RegKey& a, const RegKey& b) noexcept +{ + return a.Get() == b.Get(); +} + +inline bool operator!=(const RegKey& a, const RegKey& b) noexcept +{ + return a.Get() != b.Get(); +} + +inline bool operator<(const RegKey& a, const RegKey& b) noexcept +{ + return a.Get() < b.Get(); +} + +inline bool operator<=(const RegKey& a, const RegKey& b) noexcept +{ + return a.Get() <= b.Get(); +} + +inline bool operator>(const RegKey& a, const RegKey& b) noexcept +{ + return a.Get() > b.Get(); +} + +inline bool operator>=(const RegKey& a, const RegKey& b) noexcept +{ + return a.Get() >= b.Get(); +} + + +//------------------------------------------------------------------------------ +// Private Helper Classes and Functions +//------------------------------------------------------------------------------ + +// +// Note: Naming this private namespace `winreg_internal` instead of just +// `internal` (or `detail`) helps protecting against client code +// that does something like: +// +// using namespace winreg; +// +// In such cases, WinReg's internal private helper code is still *protected* +// under the `winreg_internal` namespace, and will not collide against +// other libraries' internal/detail namespaces. +// + +namespace winreg_internal +{ + +//------------------------------------------------------------------------------ +// Simple scoped-based RAII wrapper that *automatically* invokes LocalFree() +// in its destructor. +//------------------------------------------------------------------------------ +template +class ScopedLocalFree +{ +public: + + typedef T Type; + typedef T* TypePtr; + + + // Init wrapped pointer to nullptr + ScopedLocalFree() noexcept = default; + + // Automatically and safely invoke ::LocalFree() + ~ScopedLocalFree() noexcept + { + Free(); + } + + // + // Ban copy and move operations + // + ScopedLocalFree(const ScopedLocalFree&) = delete; + ScopedLocalFree(ScopedLocalFree&&) = delete; + ScopedLocalFree& operator=(const ScopedLocalFree&) = delete; + ScopedLocalFree& operator=(ScopedLocalFree&&) = delete; + + + // Read-only access to the wrapped pointer + [[nodiscard]] T* Get() const noexcept + { + return m_ptr; + } + + // Writable access to the wrapped pointer + [[nodiscard]] T** AddressOf() noexcept + { + return &m_ptr; + } + + // Explicit pointer conversion to bool + explicit operator bool() const noexcept + { + return (m_ptr != nullptr); + } + + // Safely invoke ::LocalFree() on the wrapped pointer + void Free() noexcept + { + if (m_ptr != nullptr) + { + ::LocalFree(m_ptr); + m_ptr = nullptr; + } + } + + + // + // IMPLEMENTATION + // +private: + T* m_ptr{ nullptr }; +}; + + +//------------------------------------------------------------------------------ +// Helper function to build a multi-string from a vector. +// +// A multi-string is a sequence of contiguous NUL-terminated strings, +// that terminates with an additional NUL. +// Basically, considered as a whole, the sequence is terminated by two NULs. +// E.g.: +// Hello\0World\0\0 +//------------------------------------------------------------------------------ +[[nodiscard]] inline std::vector BuildMultiString(const std::vector& data) +{ + // Special case of the empty multi-string + if (data.empty()) + { + // Build a vector containing just two NULs + return std::vector(2, L'\0'); + } + + // Get the total length in wchar_ts of the multi-string + size_t totalLen = 0; + for (const auto& s : data) + { + // Add one to current string's length for the terminating NUL + totalLen += (s.length() + 1); + } + + // Add one for the last NUL terminator (making the whole structure double-NUL terminated) + totalLen++; + + // Allocate a buffer to store the multi-string + std::vector multiString; + + // Reserve room in the vector to speed up the following insertion loop + multiString.reserve(totalLen); + + // Copy the single strings into the multi-string + for (const auto& s : data) + { + if (!s.empty()) + { + // Copy current string's content + multiString.insert(multiString.end(), s.begin(), s.end()); + } + + // Don't forget to NUL-terminate the current string + // (or just insert L'\0' for empty strings) + multiString.emplace_back(L'\0'); + } + + // Add the last NUL-terminator + multiString.emplace_back(L'\0'); + + return multiString; +} + + +//------------------------------------------------------------------------------ +// Return true if the wchar_t sequence stored in 'data' terminates +// with two null (L'\0') wchar_t's +//------------------------------------------------------------------------------ +[[nodiscard]] inline bool IsDoubleNullTerminated(const std::vector& data) +{ + // First check that there's enough room for at least two nulls + if (data.size() < 2) + { + return false; + } + + // Check that the sequence terminates with two nulls (L'\0', L'\0') + const size_t lastPosition = data.size() - 1; + return ((data[lastPosition] == L'\0') && + (data[lastPosition - 1] == L'\0')) ? true : false; +} + + +//------------------------------------------------------------------------------ +// Given a sequence of wchar_ts representing a double-null-terminated string, +// returns a vector of wstrings that represent the single strings. +// +// Also supports embedded empty strings in the sequence. +//------------------------------------------------------------------------------ +[[nodiscard]] inline std::vector ParseMultiString(const std::vector& data) +{ + // Make sure that there are two terminating L'\0's at the end of the sequence + if (!IsDoubleNullTerminated(data)) + { + throw RegException{ ERROR_INVALID_DATA, "Not a double-null terminated string." }; + } + + // Parse the double-NUL-terminated string into a vector, + // which will be returned to the caller + std::vector result; + + // + // Note on Embedded Empty Strings + // ============================== + // + // Below commented-out there is the previous parsing code, + // that assumes that an empty string *terminates* the sequence. + // + // In fact, according to the official Microsoft MSDN documentation, + // an empty string is treated as a sequence terminator, + // so you can't have empty strings inside the sequence. + // + // Source: https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types + // "A REG_MULTI_SZ string ends with a string of length 0. + // Therefore, it is not possible to include a zero-length string + // in the sequence. An empty sequence would be defined as follows: \0." + // + // Unfortunately, it seems that Microsoft violates its own rule, for example + // in the PendingFileRenameOperations value under the + // "SYSTEM\CurrentControlSet\Control\Session Manager" key. + // This is a REG_MULTI_SZ value that does contain embedded empty strings. + // + // So, I changed the previous parsing code to support also embedded empty strings. + // + // ------------------------------------------------------------------------- + //// *** Previous parsing code - Assumes an empty string terminates the sequence *** + // + //const wchar_t* currStringPtr = data.data(); + //while (*currStringPtr != L'\0') + //{ + // // Current string is NUL-terminated, so get its length calling wcslen + // const size_t currStringLength = wcslen(currStringPtr); + // + // // Add current string to the result vector + // result.emplace_back(currStringPtr, currStringLength); + // + // // Move to the next string + // currStringPtr += currStringLength + 1; + //} + // ------------------------------------------------------------------------- + // + + const wchar_t* currStringPtr = data.data(); + const wchar_t* const endPtr = data.data() + data.size() - 1; + + while (currStringPtr < endPtr) + { + // Current string is NUL-terminated, so get its length calling wcslen + const size_t currStringLength = wcslen(currStringPtr); + + // Add current string to the result vector + if (currStringLength > 0) + { + result.emplace_back(currStringPtr, currStringLength); + } + else + { + // Insert empty strings, as well + result.emplace_back(std::wstring{}); + } + + // Move to the next string, skipping the terminating NUL + currStringPtr += currStringLength + 1; + } + + return result; +} + + +//------------------------------------------------------------------------------ +// Builds a RegExpected object that stores an error code +//------------------------------------------------------------------------------ +template +[[nodiscard]] inline RegExpected MakeRegExpectedWithError(const LSTATUS retCode) +{ + return RegExpected{ RegResult{ retCode } }; +} + + +} // namespace winreg_internal + + +//------------------------------------------------------------------------------ +// RegKey Inline Methods +//------------------------------------------------------------------------------ + +inline RegKey::RegKey(const HKEY hKey) noexcept + : m_hKey{ hKey } +{ +} + + +inline RegKey::RegKey(const HKEY hKeyParent, const std::wstring& subKey) +{ + Create(hKeyParent, subKey); +} + + +inline RegKey::RegKey(const HKEY hKeyParent, const std::wstring& subKey, const REGSAM desiredAccess) +{ + Create(hKeyParent, subKey, desiredAccess); +} + + +inline RegKey::RegKey(RegKey&& other) noexcept + : m_hKey{ other.m_hKey } +{ + // Other doesn't own the handle anymore + other.m_hKey = nullptr; +} + + +inline RegKey& RegKey::operator=(RegKey&& other) noexcept +{ + // Prevent self-move-assign + if ((this != &other) && (m_hKey != other.m_hKey)) + { + // Close current + Close(); + + // Move from other (i.e. take ownership of other's raw handle) + m_hKey = other.m_hKey; + other.m_hKey = nullptr; + } + return *this; +} + + +inline RegKey::~RegKey() noexcept +{ + // Release the owned handle (if any) + Close(); +} + + +inline HKEY RegKey::Get() const noexcept +{ + return m_hKey; +} + + +inline void RegKey::Close() noexcept +{ + if (IsValid()) + { + // Do not call RegCloseKey on predefined keys + if (! IsPredefined()) + { + ::RegCloseKey(m_hKey); + } + + // Avoid dangling references + m_hKey = nullptr; + } +} + + +inline bool RegKey::IsValid() const noexcept +{ + return m_hKey != nullptr; +} + + +inline RegKey::operator bool() const noexcept +{ + return IsValid(); +} + + +inline bool RegKey::IsPredefined() const noexcept +{ + // Predefined keys + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724836(v=vs.85).aspx + + if ( (m_hKey == HKEY_CURRENT_USER) + || (m_hKey == HKEY_LOCAL_MACHINE) + || (m_hKey == HKEY_CLASSES_ROOT) + || (m_hKey == HKEY_CURRENT_CONFIG) + || (m_hKey == HKEY_CURRENT_USER_LOCAL_SETTINGS) + || (m_hKey == HKEY_PERFORMANCE_DATA) + || (m_hKey == HKEY_PERFORMANCE_NLSTEXT) + || (m_hKey == HKEY_PERFORMANCE_TEXT) + || (m_hKey == HKEY_USERS)) + { + return true; + } + + return false; +} + + +inline HKEY RegKey::Detach() noexcept +{ + HKEY hKey = m_hKey; + + // We don't own the HKEY handle anymore + m_hKey = nullptr; + + // Transfer ownership to the caller + return hKey; +} + + +inline void RegKey::Attach(const HKEY hKey) noexcept +{ + // Prevent self-attach + if (m_hKey != hKey) + { + // Close any open registry handle + Close(); + + // Take ownership of the input hKey + m_hKey = hKey; + } +} + + +inline void RegKey::SwapWith(RegKey& other) noexcept +{ + // Enable ADL (not necessary in this case, but good practice) + using std::swap; + + // Swap the raw handle members + swap(m_hKey, other.m_hKey); +} + + +inline void swap(RegKey& a, RegKey& b) noexcept +{ + a.SwapWith(b); +} + + +inline void RegKey::Create( + const HKEY hKeyParent, + const std::wstring& subKey, + const REGSAM desiredAccess +) +{ + constexpr DWORD kDefaultOptions = REG_OPTION_NON_VOLATILE; + + Create(hKeyParent, subKey, desiredAccess, kDefaultOptions, + nullptr, // no security attributes, + nullptr // no disposition + ); +} + + +inline void RegKey::Create( + const HKEY hKeyParent, + const std::wstring& subKey, + const REGSAM desiredAccess, + const DWORD options, + SECURITY_ATTRIBUTES* const securityAttributes, + DWORD* const disposition +) +{ + HKEY hKey = nullptr; + LSTATUS retCode = ::RegCreateKeyExW( + hKeyParent, + subKey.c_str(), + 0, // reserved + REG_NONE, // user-defined class type parameter not supported + options, + desiredAccess, + securityAttributes, + &hKey, + disposition + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegCreateKeyExW failed." }; + } + + // Safely close any previously opened key + Close(); + + // Take ownership of the newly created key + m_hKey = hKey; +} + + +inline void RegKey::Open( + const HKEY hKeyParent, + const std::wstring& subKey, + const REGSAM desiredAccess +) +{ + HKEY hKey = nullptr; + LSTATUS retCode = ::RegOpenKeyExW( + hKeyParent, + subKey.c_str(), + REG_NONE, // default options + desiredAccess, + &hKey + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegOpenKeyExW failed." }; + } + + // Safely close any previously opened key + Close(); + + // Take ownership of the newly created key + m_hKey = hKey; +} + + +inline RegResult RegKey::TryCreate( + const HKEY hKeyParent, + const std::wstring& subKey, + const REGSAM desiredAccess +) noexcept +{ + constexpr DWORD kDefaultOptions = REG_OPTION_NON_VOLATILE; + + return TryCreate(hKeyParent, subKey, desiredAccess, kDefaultOptions, + nullptr, // no security attributes, + nullptr // no disposition + ); +} + + +inline RegResult RegKey::TryCreate( + const HKEY hKeyParent, + const std::wstring& subKey, + const REGSAM desiredAccess, + const DWORD options, + SECURITY_ATTRIBUTES* const securityAttributes, + DWORD* const disposition +) noexcept +{ + HKEY hKey = nullptr; + RegResult retCode{ ::RegCreateKeyExW( + hKeyParent, + subKey.c_str(), + 0, // reserved + REG_NONE, // user-defined class type parameter not supported + options, + desiredAccess, + securityAttributes, + &hKey, + disposition + ) }; + if (retCode.Failed()) + { + return retCode; + } + + // Safely close any previously opened key + Close(); + + // Take ownership of the newly created key + m_hKey = hKey; + + _ASSERTE(retCode.IsOk()); + return retCode; +} + + +inline RegResult RegKey::TryOpen( + const HKEY hKeyParent, + const std::wstring& subKey, + const REGSAM desiredAccess +) noexcept +{ + HKEY hKey = nullptr; + RegResult retCode{ ::RegOpenKeyExW( + hKeyParent, + subKey.c_str(), + REG_NONE, // default options + desiredAccess, + &hKey + ) }; + if (retCode.Failed()) + { + return retCode; + } + + // Safely close any previously opened key + Close(); + + // Take ownership of the newly created key + m_hKey = hKey; + + _ASSERTE(retCode.IsOk()); + return retCode; +} + + +inline void RegKey::SetDwordValue(const std::wstring& valueName, const DWORD data) +{ + _ASSERTE(IsValid()); + + LSTATUS retCode = ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_DWORD, + reinterpret_cast(&data), + sizeof(data) + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot write DWORD value: RegSetValueExW failed." }; + } +} + + +inline void RegKey::SetQwordValue(const std::wstring& valueName, const ULONGLONG& data) +{ + _ASSERTE(IsValid()); + + LSTATUS retCode = ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_QWORD, + reinterpret_cast(&data), + sizeof(data) + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot write QWORD value: RegSetValueExW failed." }; + } +} + + +inline void RegKey::SetStringValue(const std::wstring& valueName, const std::wstring& data) +{ + _ASSERTE(IsValid()); + + // String size including the terminating NUL, in bytes + const DWORD dataSize = static_cast((data.length() + 1) * sizeof(wchar_t)); + + LSTATUS retCode = ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_SZ, + reinterpret_cast(data.c_str()), + dataSize + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot write string value: RegSetValueExW failed." }; + } +} + + +inline void RegKey::SetExpandStringValue(const std::wstring& valueName, const std::wstring& data) +{ + _ASSERTE(IsValid()); + + // String size including the terminating NUL, in bytes + const DWORD dataSize = static_cast((data.length() + 1) * sizeof(wchar_t)); + + LSTATUS retCode = ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_EXPAND_SZ, + reinterpret_cast(data.c_str()), + dataSize + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot write expand string value: RegSetValueExW failed." }; + } +} + + +inline void RegKey::SetMultiStringValue( + const std::wstring& valueName, + const std::vector& data +) +{ + _ASSERTE(IsValid()); + + // First, we have to build a double-NUL-terminated multi-string from the input data + const std::vector multiString = winreg_internal::BuildMultiString(data); + + // Total size, in bytes, of the whole multi-string structure + const DWORD dataSize = static_cast(multiString.size() * sizeof(wchar_t)); + + LSTATUS retCode = ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_MULTI_SZ, + reinterpret_cast(multiString.data()), + dataSize + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot write multi-string value: RegSetValueExW failed." }; + } +} + + +inline void RegKey::SetBinaryValue(const std::wstring& valueName, const std::vector& data) +{ + _ASSERTE(IsValid()); + + // Total data size, in bytes + const DWORD dataSize = static_cast(data.size()); + + LSTATUS retCode = ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_BINARY, + data.data(), + dataSize + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot write binary data value: RegSetValueExW failed." }; + } +} + + +inline void RegKey::SetBinaryValue( + const std::wstring& valueName, + const void* const data, + const DWORD dataSize +) +{ + _ASSERTE(IsValid()); + + LSTATUS retCode = ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_BINARY, + static_cast(data), + dataSize + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot write binary data value: RegSetValueExW failed." }; + } +} + + +inline RegResult RegKey::TrySetDwordValue(const std::wstring& valueName, const DWORD data) noexcept +{ + _ASSERTE(IsValid()); + + return RegResult{ ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_DWORD, + reinterpret_cast(&data), + sizeof(data) + ) }; +} + + +inline RegResult RegKey::TrySetQwordValue(const std::wstring& valueName, + const ULONGLONG& data) noexcept +{ + _ASSERTE(IsValid()); + + return RegResult{ ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_QWORD, + reinterpret_cast(&data), + sizeof(data) + ) }; +} + + +inline RegResult RegKey::TrySetStringValue(const std::wstring& valueName, + const std::wstring& data) noexcept +{ + _ASSERTE(IsValid()); + + // String size including the terminating NUL, in bytes + const DWORD dataSize = static_cast((data.length() + 1) * sizeof(wchar_t)); + + return RegResult{ ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_SZ, + reinterpret_cast(data.c_str()), + dataSize + ) }; +} + + +inline RegResult RegKey::TrySetExpandStringValue(const std::wstring& valueName, + const std::wstring& data) noexcept +{ + _ASSERTE(IsValid()); + + // String size including the terminating NUL, in bytes + const DWORD dataSize = static_cast((data.length() + 1) * sizeof(wchar_t)); + + return RegResult{ ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_EXPAND_SZ, + reinterpret_cast(data.c_str()), + dataSize + ) }; +} + + +inline RegResult RegKey::TrySetMultiStringValue(const std::wstring& valueName, + const std::vector& data) +{ + _ASSERTE(IsValid()); + + // First, we have to build a double-NUL-terminated multi-string from the input data. + // + // NOTE: This is the reason why I *cannot* mark this method noexcept, + // since a *dynamic allocation* happens for creating the std::vector in BuildMultiString. + // And, if dynamic memory allocations fail, an exception is thrown. + // + const std::vector multiString = winreg_internal::BuildMultiString(data); + + // Total size, in bytes, of the whole multi-string structure + const DWORD dataSize = static_cast(multiString.size() * sizeof(wchar_t)); + + return RegResult{ ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_MULTI_SZ, + reinterpret_cast(multiString.data()), + dataSize + ) }; +} + + +inline RegResult RegKey::TrySetBinaryValue(const std::wstring& valueName, + const std::vector& data) noexcept +{ + _ASSERTE(IsValid()); + + // Total data size, in bytes + const DWORD dataSize = static_cast(data.size()); + + return RegResult{ ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_BINARY, + data.data(), + dataSize + ) }; +} + + +inline RegResult RegKey::TrySetBinaryValue(const std::wstring& valueName, + const void* const data, + const DWORD dataSize) noexcept +{ + _ASSERTE(IsValid()); + + return RegResult{ ::RegSetValueExW( + m_hKey, + valueName.c_str(), + 0, // reserved + REG_BINARY, + static_cast(data), + dataSize + ) }; +} + + +inline DWORD RegKey::GetDwordValue(const std::wstring& valueName) const +{ + _ASSERTE(IsValid()); + + DWORD data = 0; // to be read from the registry + DWORD dataSize = sizeof(data); // size of data, in bytes + + constexpr DWORD flags = RRF_RT_REG_DWORD; + LSTATUS retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + &data, + &dataSize + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot get DWORD value: RegGetValueW failed." }; + } + + return data; +} + + +inline ULONGLONG RegKey::GetQwordValue(const std::wstring& valueName) const +{ + _ASSERTE(IsValid()); + + ULONGLONG data = 0; // to be read from the registry + DWORD dataSize = sizeof(data); // size of data, in bytes + + constexpr DWORD flags = RRF_RT_REG_QWORD; + LSTATUS retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + &data, + &dataSize + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot get QWORD value: RegGetValueW failed." }; + } + + return data; +} + + +inline std::wstring RegKey::GetStringValue(const std::wstring& valueName) const +{ + _ASSERTE(IsValid()); + + std::wstring result; // to be read from the registry + DWORD dataSize = 0; // size of the string data, in bytes + + constexpr DWORD flags = RRF_RT_REG_SZ; + + LSTATUS retCode = ERROR_MORE_DATA; + + while (retCode == ERROR_MORE_DATA) + { + // Get the size of the result string + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + nullptr, // output buffer not needed now + &dataSize + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot get the size of the string value: RegGetValueW failed." }; + } + + // Allocate a string of proper size. + // Note that dataSize is in bytes and includes the terminating NUL; + // we have to convert the size from bytes to wchar_ts for wstring::resize. + result.resize(dataSize / sizeof(wchar_t)); + + // Call RegGetValue for the second time to read the string's content + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + result.data(), // output buffer + &dataSize + ); + } + + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot get the string value: RegGetValueW failed." }; + } + + // Remove the NUL terminator scribbled by RegGetValue from the wstring + result.resize((dataSize / sizeof(wchar_t)) - 1); + + return result; +} + + +inline std::wstring RegKey::GetExpandStringValue( + const std::wstring& valueName, + const ExpandStringOption expandOption +) const +{ + _ASSERTE(IsValid()); + + std::wstring result; // to be read from the registry + DWORD dataSize = 0; // size of the expand string data, in bytes + + + DWORD flags = RRF_RT_REG_EXPAND_SZ; + + // Adjust the flag for RegGetValue considering the expand string option specified by the caller + if (expandOption == ExpandStringOption::DontExpand) + { + flags |= RRF_NOEXPAND; + } + + + LSTATUS retCode = ERROR_MORE_DATA; + + while (retCode == ERROR_MORE_DATA) + { + // Get the size of the result string + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + nullptr, // output buffer not needed now + &dataSize + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, + "Cannot get the size of the expand string value: RegGetValueW failed." }; + } + + // Allocate a string of proper size. + // Note that dataSize is in bytes and includes the terminating NUL; + // we have to convert the size from bytes to wchar_ts for wstring::resize. + result.resize(dataSize / sizeof(wchar_t)); + + // Call RegGetValue for the second time to read the string's content + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + result.data(), // output buffer + &dataSize + ); + } + + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot get the expand string value: RegGetValueW failed." }; + } + + // Remove the NUL terminator scribbled by RegGetValue from the wstring + result.resize((dataSize / sizeof(wchar_t)) - 1); + + return result; +} + + +inline std::vector RegKey::GetMultiStringValue(const std::wstring& valueName) const +{ + _ASSERTE(IsValid()); + + // Room for the result multi-string, to be read from the registry + std::vector multiString; + + // Size of the multi-string, in bytes + DWORD dataSize = 0; + + constexpr DWORD flags = RRF_RT_REG_MULTI_SZ; + + LSTATUS retCode = ERROR_MORE_DATA; + + while (retCode == ERROR_MORE_DATA) + { + // Request the size of the multi-string, in bytes + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + nullptr, // output buffer not needed now + &dataSize + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, + "Cannot get the size of the multi-string value: RegGetValueW failed." }; + } + + // Allocate room for the result multi-string. + // Note that dataSize is in bytes, but our vector::resize method requires size + // to be expressed in wchar_ts. + multiString.resize(dataSize / sizeof(wchar_t)); + + // Call RegGetValue for the second time to read the multi-string's content into the vector + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + multiString.data(), // output buffer + &dataSize + ); + } + + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot get the multi-string value: RegGetValueW failed." }; + } + + // Resize vector to the actual size returned by the last call to RegGetValue. + // Note that the vector is a vector of wchar_ts, instead the size returned by RegGetValue + // is in bytes, so we have to scale from bytes to wchar_t count. + multiString.resize(dataSize / sizeof(wchar_t)); + + // Convert the double-null-terminated string structure to a vector, + // and return that back to the caller + return winreg_internal::ParseMultiString(multiString); +} + + +inline std::vector RegKey::GetBinaryValue(const std::wstring& valueName) const +{ + _ASSERTE(IsValid()); + + // Room for the binary data, to be read from the registry + std::vector binaryData; + + // Size of binary data, in bytes + DWORD dataSize = 0; + + constexpr DWORD flags = RRF_RT_REG_BINARY; + + LSTATUS retCode = ERROR_MORE_DATA; + + while (retCode == ERROR_MORE_DATA) + { + // Request the size of the binary data, in bytes + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + nullptr, // output buffer not needed now + &dataSize + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, + "Cannot get the size of the binary data: RegGetValueW failed." }; + } + + // Allocate a buffer of proper size to store the binary data + binaryData.resize(dataSize); + + // Handle the special case of zero-length binary data: + // If the binary data value in the registry is empty, just return an empty vector. + if (dataSize == 0) + { + _ASSERTE(binaryData.empty()); + return binaryData; + } + + // Call RegGetValue for the second time to read the binary data content into the vector + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + binaryData.data(), // output buffer + &dataSize + ); + } + + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot get the binary data: RegGetValueW failed." }; + } + + // Resize vector to the actual size returned by the last call to RegGetValue + binaryData.resize(dataSize); + + return binaryData; +} + + +inline RegExpected RegKey::TryGetDwordValue(const std::wstring& valueName) const +{ + _ASSERTE(IsValid()); + + using RegValueType = DWORD; + + DWORD data = 0; // to be read from the registry + DWORD dataSize = sizeof(data); // size of data, in bytes + + constexpr DWORD flags = RRF_RT_REG_DWORD; + LSTATUS retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + &data, + &dataSize + ); + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + return RegExpected{ data }; +} + + +inline RegExpected RegKey::TryGetQwordValue(const std::wstring& valueName) const +{ + _ASSERTE(IsValid()); + + using RegValueType = ULONGLONG; + + ULONGLONG data = 0; // to be read from the registry + DWORD dataSize = sizeof(data); // size of data, in bytes + + constexpr DWORD flags = RRF_RT_REG_QWORD; + LSTATUS retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + &data, + &dataSize + ); + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + return RegExpected{ data }; +} + + +inline RegExpected RegKey::TryGetStringValue(const std::wstring& valueName) const +{ + _ASSERTE(IsValid()); + + using RegValueType = std::wstring; + + constexpr DWORD flags = RRF_RT_REG_SZ; + + std::wstring result; + + DWORD dataSize = 0; // size of the string data, in bytes + + LSTATUS retCode = ERROR_MORE_DATA; + + while (retCode == ERROR_MORE_DATA) + { + // Get the size of the result string + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + nullptr, // output buffer not needed now + &dataSize + ); + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + // Allocate a string of proper size. + // Note that dataSize is in bytes and includes the terminating NUL; + // we have to convert the size from bytes to wchar_ts for wstring::resize. + result.resize(dataSize / sizeof(wchar_t)); + + // Call RegGetValue for the second time to read the string's content + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + result.data(), // output buffer + &dataSize + ); + } + + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + // Remove the NUL terminator scribbled by RegGetValue from the wstring + result.resize((dataSize / sizeof(wchar_t)) - 1); + + return RegExpected{ result }; +} + + +inline RegExpected RegKey::TryGetExpandStringValue( + const std::wstring& valueName, + const ExpandStringOption expandOption +) const +{ + _ASSERTE(IsValid()); + + using RegValueType = std::wstring; + + DWORD flags = RRF_RT_REG_EXPAND_SZ; + + // Adjust the flag for RegGetValue considering the expand string option specified by the caller + if (expandOption == ExpandStringOption::DontExpand) + { + flags |= RRF_NOEXPAND; + } + + std::wstring result; + DWORD dataSize = 0; // size of the expand string data, in bytes + LSTATUS retCode = ERROR_MORE_DATA; + + while (retCode == ERROR_MORE_DATA) + { + // Get the size of the result string + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + nullptr, // output buffer not needed now + &dataSize + ); + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + // Allocate a string of proper size. + // Note that dataSize is in bytes and includes the terminating NUL; + // we have to convert the size from bytes to wchar_ts for wstring::resize. + result.resize(dataSize / sizeof(wchar_t)); + + // Call RegGetValue for the second time to read the string's content + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + result.data(), // output buffer + &dataSize + ); + } + + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + // Remove the NUL terminator scribbled by RegGetValue from the wstring + result.resize((dataSize / sizeof(wchar_t)) - 1); + + return RegExpected{ result }; +} + + +inline RegExpected> + RegKey::TryGetMultiStringValue(const std::wstring& valueName) const +{ + _ASSERTE(IsValid()); + + using RegValueType = std::vector; + + constexpr DWORD flags = RRF_RT_REG_MULTI_SZ; + + // Room for the result multi-string + std::vector data; + + // Size of the multi-string, in bytes + DWORD dataSize = 0; + + LSTATUS retCode = ERROR_MORE_DATA; + + while (retCode == ERROR_MORE_DATA) + { + // Request the size of the multi-string, in bytes + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + nullptr, // output buffer not needed now + &dataSize + ); + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + // Allocate room for the result multi-string. + // Note that dataSize is in bytes, but our vector::resize method requires size + // to be expressed in wchar_ts. + data.resize(dataSize / sizeof(wchar_t)); + + // Call RegGetValue for the second time to read the multi-string's content into the vector + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + data.data(), // output buffer + &dataSize + ); + } + + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + // Resize vector to the actual size returned by the last call to RegGetValue. + // Note that the vector is a vector of wchar_ts, instead the size returned by RegGetValue + // is in bytes, so we have to scale from bytes to wchar_t count. + data.resize(dataSize / sizeof(wchar_t)); + + // Convert the double-null-terminated string structure to a vector, + // and return that back to the caller + return RegExpected{ winreg_internal::ParseMultiString(data) }; +} + + +inline RegExpected> + RegKey::TryGetBinaryValue(const std::wstring& valueName) const +{ + _ASSERTE(IsValid()); + + using RegValueType = std::vector; + + constexpr DWORD flags = RRF_RT_REG_BINARY; + + // Room for the binary data + std::vector data; + + DWORD dataSize = 0; // size of binary data, in bytes + + LSTATUS retCode = ERROR_MORE_DATA; + + while (retCode == ERROR_MORE_DATA) + { + // Request the size of the binary data, in bytes + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + nullptr, // output buffer not needed now + &dataSize + ); + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + // Allocate a buffer of proper size to store the binary data + data.resize(dataSize); + + // Handle the special case of zero-length binary data: + // If the binary data value in the registry is empty, just return + if (dataSize == 0) + { + _ASSERTE(data.empty()); + return RegExpected{ data }; + } + + // Call RegGetValue for the second time to read the binary data content into the vector + retCode = ::RegGetValueW( + m_hKey, + nullptr, // no subkey + valueName.c_str(), + flags, + nullptr, // type not required + data.data(), // output buffer + &dataSize + ); + } + + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + // Resize vector to the actual size returned by the last call to RegGetValue + data.resize(dataSize); + + return RegExpected{ data }; +} + + +inline std::vector RegKey::EnumSubKeys() const +{ + _ASSERTE(IsValid()); + + // Get some useful enumeration info, like the total number of subkeys + // and the maximum length of the subkey names + DWORD subKeyCount = 0; + DWORD maxSubKeyNameLen = 0; + LSTATUS retCode = ::RegQueryInfoKeyW( + m_hKey, + nullptr, // no user-defined class + nullptr, // no user-defined class size + nullptr, // reserved + &subKeyCount, + &maxSubKeyNameLen, + nullptr, // no subkey class length + nullptr, // no value count + nullptr, // no value name max length + nullptr, // no max value length + nullptr, // no security descriptor + nullptr // no last write time + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ + retCode, + "RegQueryInfoKeyW failed while preparing for subkey enumeration." + }; + } + + // NOTE: According to the MSDN documentation, the size returned for subkey name max length + // does *not* include the terminating NUL, so let's add +1 to take it into account + // when I allocate the buffer for reading subkey names. + maxSubKeyNameLen++; + + // Preallocate a buffer for the subkey names + auto nameBuffer = std::make_unique(maxSubKeyNameLen); + + // The result subkey names will be stored here + std::vector subkeyNames; + + // Reserve room in the vector to speed up the following insertion loop + subkeyNames.reserve(subKeyCount); + + // Enumerate all the subkeys + for (DWORD index = 0; index < subKeyCount; index++) + { + // Get the name of the current subkey + DWORD subKeyNameLen = maxSubKeyNameLen; + retCode = ::RegEnumKeyExW( + m_hKey, + index, + nameBuffer.get(), + &subKeyNameLen, + nullptr, // reserved + nullptr, // no class + nullptr, // no class + nullptr // no last write time + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot enumerate subkeys: RegEnumKeyExW failed." }; + } + + // On success, the ::RegEnumKeyEx API writes the length of the + // subkey name in the subKeyNameLen output parameter + // (not including the terminating NUL). + // So I can build a wstring based on that length. + subkeyNames.emplace_back(nameBuffer.get(), subKeyNameLen); + } + + return subkeyNames; +} + + +inline std::vector> RegKey::EnumValues() const +{ + _ASSERTE(IsValid()); + + // Get useful enumeration info, like the total number of values + // and the maximum length of the value names + DWORD valueCount = 0; + DWORD maxValueNameLen = 0; + LSTATUS retCode = ::RegQueryInfoKeyW( + m_hKey, + nullptr, // no user-defined class + nullptr, // no user-defined class size + nullptr, // reserved + nullptr, // no subkey count + nullptr, // no subkey max length + nullptr, // no subkey class length + &valueCount, + &maxValueNameLen, + nullptr, // no max value length + nullptr, // no security descriptor + nullptr // no last write time + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ + retCode, + "RegQueryInfoKeyW failed while preparing for value enumeration." + }; + } + + // NOTE: According to the MSDN documentation, the size returned for value name max length + // does *not* include the terminating NUL, so let's add +1 to take it into account + // when I allocate the buffer for reading value names. + maxValueNameLen++; + + // Preallocate a buffer for the value names + auto nameBuffer = std::make_unique(maxValueNameLen); + + // The value names and types will be stored here + std::vector> valueInfo; + + // Reserve room in the vector to speed up the following insertion loop + valueInfo.reserve(valueCount); + + // Enumerate all the values + for (DWORD index = 0; index < valueCount; index++) + { + // Get the name and the type of the current value + DWORD valueNameLen = maxValueNameLen; + DWORD valueType = 0; + retCode = ::RegEnumValueW( + m_hKey, + index, + nameBuffer.get(), + &valueNameLen, + nullptr, // reserved + &valueType, + nullptr, // no data + nullptr // no data size + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot enumerate values: RegEnumValueW failed." }; + } + + // On success, the RegEnumValue API writes the length of the + // value name in the valueNameLen output parameter + // (not including the terminating NUL). + // So we can build a wstring based on that. + valueInfo.emplace_back( + std::wstring{ nameBuffer.get(), valueNameLen }, + valueType + ); + } + + return valueInfo; +} + + +inline RegExpected> RegKey::TryEnumSubKeys() const +{ + _ASSERTE(IsValid()); + + using ReturnType = std::vector; + + // Get some useful enumeration info, like the total number of subkeys + // and the maximum length of the subkey names + DWORD subKeyCount = 0; + DWORD maxSubKeyNameLen = 0; + LSTATUS retCode = ::RegQueryInfoKeyW( + m_hKey, + nullptr, // no user-defined class + nullptr, // no user-defined class size + nullptr, // reserved + &subKeyCount, + &maxSubKeyNameLen, + nullptr, // no subkey class length + nullptr, // no value count + nullptr, // no value name max length + nullptr, // no max value length + nullptr, // no security descriptor + nullptr // no last write time + ); + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + // NOTE: According to the MSDN documentation, the size returned for subkey name max length + // does *not* include the terminating NUL, so let's add +1 to take it into account + // when I allocate the buffer for reading subkey names. + maxSubKeyNameLen++; + + // Preallocate a buffer for the subkey names + auto nameBuffer = std::make_unique(maxSubKeyNameLen); + + // The result subkey names will be stored here + std::vector subkeyNames; + + // Reserve room in the vector to speed up the following insertion loop + subkeyNames.reserve(subKeyCount); + + // Enumerate all the subkeys + for (DWORD index = 0; index < subKeyCount; index++) + { + // Get the name of the current subkey + DWORD subKeyNameLen = maxSubKeyNameLen; + retCode = ::RegEnumKeyExW( + m_hKey, + index, + nameBuffer.get(), + &subKeyNameLen, + nullptr, // reserved + nullptr, // no class + nullptr, // no class + nullptr // no last write time + ); + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + // On success, the ::RegEnumKeyEx API writes the length of the + // subkey name in the subKeyNameLen output parameter + // (not including the terminating NUL). + // So I can build a wstring based on that length. + subkeyNames.emplace_back(nameBuffer.get(), subKeyNameLen); + } + + return RegExpected{ subkeyNames }; +} + + +inline RegExpected>> RegKey::TryEnumValues() const +{ + _ASSERTE(IsValid()); + + using ReturnType = std::vector>; + + // Get useful enumeration info, like the total number of values + // and the maximum length of the value names + DWORD valueCount = 0; + DWORD maxValueNameLen = 0; + LSTATUS retCode = ::RegQueryInfoKeyW( + m_hKey, + nullptr, // no user-defined class + nullptr, // no user-defined class size + nullptr, // reserved + nullptr, // no subkey count + nullptr, // no subkey max length + nullptr, // no subkey class length + &valueCount, + &maxValueNameLen, + nullptr, // no max value length + nullptr, // no security descriptor + nullptr // no last write time + ); + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + // NOTE: According to the MSDN documentation, the size returned for value name max length + // does *not* include the terminating NUL, so let's add +1 to take it into account + // when I allocate the buffer for reading value names. + maxValueNameLen++; + + // Preallocate a buffer for the value names + auto nameBuffer = std::make_unique(maxValueNameLen); + + // The value names and types will be stored here + std::vector> valueInfo; + + // Reserve room in the vector to speed up the following insertion loop + valueInfo.reserve(valueCount); + + // Enumerate all the values + for (DWORD index = 0; index < valueCount; index++) + { + // Get the name and the type of the current value + DWORD valueNameLen = maxValueNameLen; + DWORD valueType = 0; + retCode = ::RegEnumValueW( + m_hKey, + index, + nameBuffer.get(), + &valueNameLen, + nullptr, // reserved + &valueType, + nullptr, // no data + nullptr // no data size + ); + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + // On success, the RegEnumValue API writes the length of the + // value name in the valueNameLen output parameter + // (not including the terminating NUL). + // So we can build a wstring based on that. + valueInfo.emplace_back( + std::wstring{ nameBuffer.get(), valueNameLen }, + valueType + ); + } + + return RegExpected{ valueInfo }; +} + + +inline DWORD RegKey::QueryValueType(const std::wstring& valueName) const +{ + _ASSERTE(IsValid()); + + DWORD typeId = 0; // will be returned by RegQueryValueEx + + LSTATUS retCode = ::RegQueryValueExW( + m_hKey, + valueName.c_str(), + nullptr, // reserved + &typeId, + nullptr, // not interested + nullptr // not interested + ); + + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "Cannot get the value type: RegQueryValueExW failed." }; + } + + return typeId; +} + + +inline RegExpected RegKey::TryQueryValueType(const std::wstring& valueName) const +{ + _ASSERTE(IsValid()); + + using ReturnType = DWORD; + + DWORD typeId = 0; // will be returned by RegQueryValueEx + + LSTATUS retCode = ::RegQueryValueExW( + m_hKey, + valueName.c_str(), + nullptr, // reserved + &typeId, + nullptr, // not interested + nullptr // not interested + ); + + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + return RegExpected{ typeId }; +} + + +inline RegKey::InfoKey RegKey::QueryInfoKey() const +{ + _ASSERTE(IsValid()); + + InfoKey infoKey{}; + LSTATUS retCode = ::RegQueryInfoKeyW( + m_hKey, + nullptr, + nullptr, + nullptr, + &(infoKey.NumberOfSubKeys), + nullptr, + nullptr, + &(infoKey.NumberOfValues), + nullptr, + nullptr, + nullptr, + &(infoKey.LastWriteTime) + ); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegQueryInfoKeyW failed." }; + } + + return infoKey; +} + + +inline RegExpected RegKey::TryQueryInfoKey() const +{ + _ASSERTE(IsValid()); + + using ReturnType = RegKey::InfoKey; + + InfoKey infoKey{}; + LSTATUS retCode = ::RegQueryInfoKeyW( + m_hKey, + nullptr, + nullptr, + nullptr, + &(infoKey.NumberOfSubKeys), + nullptr, + nullptr, + &(infoKey.NumberOfValues), + nullptr, + nullptr, + nullptr, + &(infoKey.LastWriteTime) + ); + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + return RegExpected{ infoKey }; +} + + +inline RegKey::KeyReflection RegKey::QueryReflectionKey() const +{ + BOOL isReflectionDisabled = FALSE; + LSTATUS retCode = ::RegQueryReflectionKey(m_hKey, &isReflectionDisabled); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegQueryReflectionKey failed." }; + } + + return (isReflectionDisabled ? KeyReflection::ReflectionDisabled + : KeyReflection::ReflectionEnabled); +} + + +inline RegExpected RegKey::TryQueryReflectionKey() const +{ + using ReturnType = RegKey::KeyReflection; + + BOOL isReflectionDisabled = FALSE; + LSTATUS retCode = ::RegQueryReflectionKey(m_hKey, &isReflectionDisabled); + if (retCode != ERROR_SUCCESS) + { + return winreg_internal::MakeRegExpectedWithError(retCode); + } + + KeyReflection keyReflection = isReflectionDisabled ? KeyReflection::ReflectionDisabled + : KeyReflection::ReflectionEnabled; + return RegExpected{ keyReflection }; +} + + +inline void RegKey::DeleteValue(const std::wstring& valueName) +{ + _ASSERTE(IsValid()); + + LSTATUS retCode = ::RegDeleteValueW(m_hKey, valueName.c_str()); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegDeleteValueW failed." }; + } +} + + +inline RegResult RegKey::TryDeleteValue(const std::wstring& valueName) noexcept +{ + _ASSERTE(IsValid()); + + return RegResult{ ::RegDeleteValueW(m_hKey, valueName.c_str()) }; +} + + +inline void RegKey::DeleteKey(const std::wstring& subKey, const REGSAM desiredAccess) +{ + _ASSERTE(IsValid()); + + LSTATUS retCode = ::RegDeleteKeyExW(m_hKey, subKey.c_str(), desiredAccess, 0); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegDeleteKeyExW failed." }; + } +} + + +inline RegResult RegKey::TryDeleteKey(const std::wstring& subKey, + const REGSAM desiredAccess) noexcept +{ + _ASSERTE(IsValid()); + + return RegResult{ ::RegDeleteKeyExW(m_hKey, subKey.c_str(), desiredAccess, 0) }; +} + + +inline void RegKey::DeleteTree(const std::wstring& subKey) +{ + _ASSERTE(IsValid()); + + LSTATUS retCode = ::RegDeleteTreeW(m_hKey, subKey.c_str()); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegDeleteTreeW failed." }; + } +} + + +inline RegResult RegKey::TryDeleteTree(const std::wstring& subKey) noexcept +{ + _ASSERTE(IsValid()); + + return RegResult{ ::RegDeleteTreeW(m_hKey, subKey.c_str()) }; +} + + +inline void RegKey::CopyTree(const std::wstring& sourceSubKey, const RegKey& destKey) +{ + _ASSERTE(IsValid()); + + LSTATUS retCode = ::RegCopyTreeW(m_hKey, sourceSubKey.c_str(), destKey.Get()); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegCopyTreeW failed." }; + } +} + + +inline RegResult RegKey::TryCopyTree(const std::wstring& sourceSubKey, + const RegKey& destKey) noexcept +{ + _ASSERTE(IsValid()); + + return RegResult{ ::RegCopyTreeW(m_hKey, sourceSubKey.c_str(), destKey.Get()) }; +} + + +inline void RegKey::FlushKey() +{ + _ASSERTE(IsValid()); + + LSTATUS retCode = ::RegFlushKey(m_hKey); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegFlushKey failed." }; + } +} + + +inline RegResult RegKey::TryFlushKey() noexcept +{ + _ASSERTE(IsValid()); + + return RegResult{ ::RegFlushKey(m_hKey) }; +} + + +inline void RegKey::LoadKey(const std::wstring& subKey, const std::wstring& filename) +{ + Close(); + + LSTATUS retCode = ::RegLoadKeyW(m_hKey, subKey.c_str(), filename.c_str()); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegLoadKeyW failed." }; + } +} + + +inline RegResult RegKey::TryLoadKey(const std::wstring& subKey, + const std::wstring& filename) noexcept +{ + Close(); + + return RegResult{ ::RegLoadKeyW(m_hKey, subKey.c_str(), filename.c_str()) }; +} + + +inline void RegKey::SaveKey( + const std::wstring& filename, + SECURITY_ATTRIBUTES* const securityAttributes +) const +{ + _ASSERTE(IsValid()); + + LSTATUS retCode = ::RegSaveKeyW(m_hKey, filename.c_str(), securityAttributes); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegSaveKeyW failed." }; + } +} + + +inline RegResult RegKey::TrySaveKey( + const std::wstring& filename, + SECURITY_ATTRIBUTES* const securityAttributes +) const noexcept +{ + _ASSERTE(IsValid()); + + return RegResult{ ::RegSaveKeyW(m_hKey, filename.c_str(), securityAttributes) }; +} + + +inline void RegKey::EnableReflectionKey() +{ + LSTATUS retCode = ::RegEnableReflectionKey(m_hKey); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegEnableReflectionKey failed." }; + } +} + + +inline RegResult RegKey::TryEnableReflectionKey() noexcept +{ + return RegResult{ ::RegEnableReflectionKey(m_hKey) }; +} + + +inline void RegKey::DisableReflectionKey() +{ + LSTATUS retCode = ::RegDisableReflectionKey(m_hKey); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegDisableReflectionKey failed." }; + } +} + + +inline RegResult RegKey::TryDisableReflectionKey() noexcept +{ + return RegResult{ ::RegDisableReflectionKey(m_hKey) }; +} + + +inline void RegKey::ConnectRegistry(const std::wstring& machineName, const HKEY hKeyPredefined) +{ + // Safely close any previously opened key + Close(); + + HKEY hKeyResult = nullptr; + LSTATUS retCode = ::RegConnectRegistryW(machineName.c_str(), hKeyPredefined, &hKeyResult); + if (retCode != ERROR_SUCCESS) + { + throw RegException{ retCode, "RegConnectRegistryW failed." }; + } + + // Take ownership of the result key + m_hKey = hKeyResult; +} + + +inline RegResult RegKey::TryConnectRegistry(const std::wstring& machineName, + const HKEY hKeyPredefined) noexcept +{ + // Safely close any previously opened key + Close(); + + HKEY hKeyResult = nullptr; + RegResult retCode{ ::RegConnectRegistryW(machineName.c_str(), hKeyPredefined, &hKeyResult) }; + if (retCode.Failed()) + { + return retCode; + } + + // Take ownership of the result key + m_hKey = hKeyResult; + + _ASSERTE(retCode.IsOk()); + return retCode; +} + + +inline std::wstring RegKey::RegTypeToString(const DWORD regType) +{ + switch (regType) + { + case REG_SZ: return L"REG_SZ"; + case REG_EXPAND_SZ: return L"REG_EXPAND_SZ"; + case REG_MULTI_SZ: return L"REG_MULTI_SZ"; + case REG_DWORD: return L"REG_DWORD"; + case REG_QWORD: return L"REG_QWORD"; + case REG_BINARY: return L"REG_BINARY"; + + default: return L"Unknown/unsupported registry type"; + } +} + + +//------------------------------------------------------------------------------ +// RegException Inline Methods +//------------------------------------------------------------------------------ + +inline RegException::RegException(const LSTATUS errorCode, const char* const message) + : std::system_error{ errorCode, std::system_category(), message } +{} + + +inline RegException::RegException(const LSTATUS errorCode, const std::string& message) + : std::system_error{ errorCode, std::system_category(), message } +{} + + +//------------------------------------------------------------------------------ +// RegResult Inline Methods +//------------------------------------------------------------------------------ + +inline RegResult::RegResult(const LSTATUS result) noexcept + : m_result{ result } +{} + + +inline bool RegResult::IsOk() const noexcept +{ + return m_result == ERROR_SUCCESS; +} + + +inline bool RegResult::Failed() const noexcept +{ + return m_result != ERROR_SUCCESS; +} + + +inline RegResult::operator bool() const noexcept +{ + return IsOk(); +} + + +inline LSTATUS RegResult::Code() const noexcept +{ + return m_result; +} + + +inline std::wstring RegResult::ErrorMessage() const +{ + return ErrorMessage(MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)); +} + + +inline std::wstring RegResult::ErrorMessage(const DWORD languageId) const +{ + // Invoke FormatMessage() to retrieve the error message from Windows + winreg_internal::ScopedLocalFree messagePtr; + DWORD retCode = ::FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + m_result, + languageId, + reinterpret_cast(messagePtr.AddressOf()), + 0, + nullptr + ); + if (retCode == 0) + { + // FormatMessage failed: return an empty string + return std::wstring{}; + } + + // Safely copy the C-string returned by FormatMessage() into a std::wstring object, + // and return it back to the caller. + return std::wstring{ messagePtr.Get() }; +} + + +//------------------------------------------------------------------------------ +// RegExpected Inline Methods +//------------------------------------------------------------------------------ + +template +inline RegExpected::RegExpected(const RegResult& errorCode) noexcept + : m_var{ errorCode } +{} + + +template +inline RegExpected::RegExpected(const T& value) + : m_var{ value } +{} + + +template +inline RegExpected::RegExpected(T&& value) + : m_var{ std::move(value) } +{} + + +template +inline RegExpected::operator bool() const noexcept +{ + return IsValid(); +} + + +template +inline bool RegExpected::IsValid() const noexcept +{ + return std::holds_alternative(m_var); +} + + +template +inline const T& RegExpected::GetValue() const +{ + // Check that the object stores a valid value + _ASSERTE(IsValid()); + + // If the object is in a valid state, the variant stores an instance of T + return std::get(m_var); +} + + +template +inline RegResult RegExpected::GetError() const +{ + // Check that the object is in an invalid state + _ASSERTE(!IsValid()); + + // If the object is in an invalid state, the variant stores a RegResult + // that represents an error code from the Windows Registry API + return std::get(m_var); +} + + +} // namespace winreg + + +#endif // GIOVANNI_DICANIO_WINREG_HPP_INCLUDED diff --git a/SkinFlaps/src/surgicalActions.cpp b/SkinFlaps/src/surgicalActions.cpp index b4fa47b..7b929cf 100644 --- a/SkinFlaps/src/surgicalActions.cpp +++ b/SkinFlaps/src/surgicalActions.cpp @@ -46,6 +46,10 @@ bool surgicalActions::saveSurgicalHistory(const char *fullFilePath) else hstStr = Serialize(_historyArray); std::ofstream outf(fullFilePath); + if (!outf.is_open()) { + _ffg->sendUserMessage("Can't save to this filename (demos are read only).\n\nPlease create another name for your history file-\n", "History Save Error"); + return false; + } prettyPrintJSON pp; pp.convert(hstStr.c_str(), ppStr); outf.write(ppStr.c_str(), ppStr.size()); diff --git a/SkinFlaps/src/vnBccTetCutter_tbb.cpp b/SkinFlaps/src/vnBccTetCutter_tbb.cpp index 5ab7f26..c520a7c 100644 --- a/SkinFlaps/src/vnBccTetCutter_tbb.cpp +++ b/SkinFlaps/src/vnBccTetCutter_tbb.cpp @@ -47,17 +47,19 @@ void vnBccTetCutter_tbb::addNewMultiresIncision() { _centTris.clear(); #if defined( _DEBUG ) for (int i = _lastTriangleSize; i < _mt->numberOfTriangles(); ++i) { + if (_mt->triangleMaterial(i) < 0) // signals a deleted triangle + continue; + inputTriangleTetsTbb(i, _centTris); + } #else tbb::parallel_for( tbb::blocked_range(_lastTriangleSize, _mt->numberOfTriangles()), [&](tbb::blocked_range r) { for (size_t i = r.begin(); i != r.end(); ++i) { -#endif if (_mt->triangleMaterial(i) < 0) // signals a deleted triangle continue; inputTriangleTetsTbb(i, _centTris); } -#if !defined( _DEBUG ) }); #endif // Look for and delete any new megatets which have new incision triangles penetrating them that will need to be recut. @@ -98,17 +100,19 @@ void vnBccTetCutter_tbb::addNewMultiresIncision() { } #if defined( _DEBUG ) for (int i = 0; i < borderTris.size(); ++i) { + if (_mt->triangleMaterial(borderTris[i]) < 0) // signals a deleted triangle + continue; + inputTriangleTetsTbb(borderTris[i], _centTris); + } #else tbb::parallel_for( tbb::blocked_range(0, borderTris.size()), [&](tbb::blocked_range r) { for (size_t i = r.begin(); i != r.end(); ++i) { -#endif - if (_mt->triangleMaterial(borderTris[i]) < 0) // signals a deleted triangle - continue; - inputTriangleTetsTbb(borderTris[i], _centTris); - } -#if !defined(_DEBUG ) + if (_mt->triangleMaterial(borderTris[i]) < 0) // signals a deleted triangle + continue; + inputTriangleTetsTbb(borderTris[i], _centTris); + } }); #endif @@ -239,17 +243,19 @@ void vnBccTetCutter_tbb::macrotetRecutCore() { _nSurfaceTets.store(_vbt->_tetNodes.size()); // this atomic must not step on any megatets that have already been created. Atomic used to multithread next section #if defined( _DEBUG ) for (int i = 0; i(0, tetTriVec.size()), [&](tbb::blocked_range r) { for (size_t i = r.begin(); i != r.end(); ++i) { -#endif if (tetTriVec[i].tc[0] == USHRT_MAX) continue; getConnectedComponents(tetTriVec[i], _newTets, _ntsHash); // for this centroid split its triangles into solid connected components } -#if !defined( _DEBUG ) }); #endif tetTriVec.clear(); @@ -296,16 +302,14 @@ void vnBccTetCutter_tbb::macrotetRecutCore() { _firstNewExteriorNode = _vbt->_nodeGridLoci.size(); oneapi::tbb::concurrent_vector eNodes; #if defined( _DEBUG ) - for (int i = 0; i(0, nts_vec.size()), [&](tbb::blocked_range r) { - for (size_t i = r.begin(); i != r.end(); ++i) { -#endif + for (size_t i = r.begin(); i != r.end(); ++i) assignExteriorTetNodes(nts_locs[i], nts_vec[i], eNodes); - } -#if !defined( _DEBUG ) }); #endif for (auto& en : eNodes) { @@ -470,16 +474,15 @@ void vnBccTetCutter_tbb::createFirstMacroTets(materialTriangles* mt, vnBccTetrah vtVerts.clear(); // COURT 4x faster than single thread using tbb hash container requiring no reduction. tbb version ~30% faster than omp before reduction and reduction using critical section. tbb hash container very helpful. #if defined( _DEBUG ) - for (int n = vnTriVec.size(), i = 0; i(0, vnTriVec.size()), [&](tbb::blocked_range r) { for (size_t i = r.begin(); i != r.end(); ++i) { -#endif inputTriangleTetsTbb(vnTriVec[i], _centTris); } -#if !defined( _DEBUG ) }); #endif vnTriVec.clear(); @@ -521,24 +524,25 @@ bool vnBccTetCutter_tbb::makeFirstVnTets(materialTriangles* mt, vnBccTetrahedra* return false; _vbt->_tetSubdivisionLevels = 1; // Not creating multiresolution tets. // COURT 4x faster than single thread using tbb hash container requiring no reduction. tbb version ~30% faster than omp before reduction and reduction using critical section. tbb hash container very helpful. + auto procTri = [&](size_t i) { + if (_mt->triangleMaterial(i) < 0) + return; + inputTriangleTetsTbb(i, _centTris); + Vec3d triVec[3]; + int* tr = _mt->triangleVertices(i); + for (int j = 0; j < 3; ++j) + triVec[j] = _vMatCoords[tr[j]]; + zIntersectTriangleTbb(triVec, true, _zIntr); + }; #ifdef _DEBUG - for (int i = 0; i < _mt->numberOfTriangles(); ++i) { + for (size_t i = 0; i < _mt->numberOfTriangles(); ++i) + procTri(i); #else tbb::parallel_for( tbb::blocked_range(0, _mt->numberOfTriangles()), [&](tbb::blocked_range r) { - for (size_t i = r.begin(); i != r.end(); ++i) { -#endif - if (_mt->triangleMaterial(i) < 0) - continue; - inputTriangleTetsTbb(i, _centTris); - Vec3d triVec[3]; - int* tr = _mt->triangleVertices(i); - for (int j = 0; j < 3; ++j) - triVec[j] = _vMatCoords[tr[j]]; - zIntersectTriangleTbb(triVec, true, _zIntr); - } -#ifndef _DEBUG + for (size_t i = r.begin(); i != r.end(); ++i) + procTri(i); }); #endif std::vector tetTriVec; @@ -560,15 +564,15 @@ bool vnBccTetCutter_tbb::makeFirstVnTets(materialTriangles* mt, vnBccTetrahedra* } _zIntr.clear(); - /* end = std::chrono::system_clock::now(); - std::chrono::duration elapsed_seconds = end - start; - std::time_t end_time = std::chrono::system_clock::to_time_t(end); - std::string message("Inputting triangles took "); - message += std::to_string(elapsed_seconds.count()); - message += " seconds for "; - message += std::to_string(_vbt->_tetNodes.size()); - message += " tets."; - std::cout << message << "\n"; */ + // end = std::chrono::system_clock::now(); + // std::chrono::duration elapsed_seconds = end - start; + // std::time_t end_time = std::chrono::system_clock::to_time_t(end); + // std::string message("Inputting triangles took "); + // message += std::to_string(elapsed_seconds.count()); + // message += " seconds for "; + // message += std::to_string(_vbt->_tetNodes.size()); + // message += " tets."; + // std::cout << message << "\n"; // create and hash all interior nodes. Very fast (< 0.002 sec) so don't bother multithreading createInteriorNodes(); @@ -586,28 +590,26 @@ bool vnBccTetCutter_tbb::makeFirstVnTets(materialTriangles* mt, vnBccTetrahedra* _nSurfaceTets.store(_vbt->_tetNodes.size()); // this atomic must not step on any megatets that have already been created. Atomic used to multithread next section #ifdef _DEBUG - for (int i = 0; i < tetTriVec.size(); ++i) { + for (int i = 0; i < tetTriVec.size(); ++i) + getConnectedComponents(tetTriVec[i], _newTets, _ntsHash); // for this centroid split its triangles into solid connected components #else tbb::parallel_for( tbb::blocked_range(0, tetTriVec.size()), [&](tbb::blocked_range r) { - for (size_t i = r.begin(); i != r.end(); ++i) { -#endif + for (size_t i = r.begin(); i != r.end(); ++i) getConnectedComponents(tetTriVec[i], _newTets, _ntsHash); // for this centroid split its triangles into solid connected components - } -#ifndef _DEBUG }); #endif - /* end = std::chrono::system_clock::now(); - elapsed_seconds = end - start; - end_time = std::chrono::system_clock::to_time_t(end); - message.assign("Getting connected components took "); - message += std::to_string(elapsed_seconds.count()); - message += " seconds for "; - message += std::to_string(_vbt->_tetNodes.size()); - message += " tets."; - std::cout << message << "\n"; */ + // end = std::chrono::system_clock::now(); + // elapsed_seconds = end - start; + // end_time = std::chrono::system_clock::to_time_t(end); + // message.assign("Getting connected components took "); + // message += std::to_string(elapsed_seconds.count()); + // message += " seconds for "; + // message += std::to_string(_vbt->_tetNodes.size()); + // message += " tets."; + // std::cout << message << "\n"; _vbt->_tetCentroids.assign(_nSurfaceTets, bccTetCentroid()); _vbt->_tetNodes.assign(_nSurfaceTets, std::array()); @@ -680,28 +682,26 @@ bool vnBccTetCutter_tbb::makeFirstVnTets(materialTriangles* mt, vnBccTetrahedra* oneapi::tbb::concurrent_vector eNodes; #ifdef _DEBUG - for (int i = 0; i < nts_vec.size(); ++i) { + for (int i = 0; i < nts_vec.size(); ++i) + assignExteriorTetNodes(nts_locs[i], nts_vec[i], eNodes); #else tbb::parallel_for( tbb::blocked_range(0, nts_vec.size()), [&](tbb::blocked_range r) { - for (size_t i = r.begin(); i != r.end(); ++i) { -#endif + for (size_t i = r.begin(); i != r.end(); ++i) assignExteriorTetNodes(nts_locs[i], nts_vec[i], eNodes); - } -#ifndef _DEBUG }); #endif - /* end = std::chrono::system_clock::now(); - elapsed_seconds = end - start; - end_time = std::chrono::system_clock::to_time_t(end); - message.assign("Assigning exterior nodes took "); - message += std::to_string(elapsed_seconds.count()); - message += " seconds for "; - message += std::to_string(_vbt->_tetNodes.size()); - message += " tets."; - std::cout << message << "\n"; */ + // end = std::chrono::system_clock::now(); + // elapsed_seconds = end - start; + // end_time = std::chrono::system_clock::to_time_t(end); + // message.assign("Assigning exterior nodes took "); + // message += std::to_string(elapsed_seconds.count()); + // message += " seconds for "; + // message += std::to_string(_vbt->_tetNodes.size()); + // message += " tets."; + // std::cout << message << "\n"; for (auto& en : eNodes) { int eNode = _vbt->_nodeGridLoci.size(); @@ -711,15 +711,15 @@ bool vnBccTetCutter_tbb::makeFirstVnTets(materialTriangles* mt, vnBccTetrahedra* } eNodes.clear(); - /* end = std::chrono::system_clock::now(); - elapsed_seconds = end - start; - end_time = std::chrono::system_clock::to_time_t(end); - message.assign("Assigning exterior nodes took "); - message += std::to_string(elapsed_seconds.count()); - message += " seconds for "; - message += std::to_string(_vbt->_tetNodes.size()); - message += " tets."; - std::cout << message << "\n"; */ + // end = std::chrono::system_clock::now(); + // elapsed_seconds = end - start; + // end_time = std::chrono::system_clock::to_time_t(end); + // message.assign("Assigning exterior nodes took "); + // message += std::to_string(elapsed_seconds.count()); + // message += " seconds for "; + // message += std::to_string(_vbt->_tetNodes.size()); + // message += " tets."; + // std::cout << message << "\n"; _vbt->_firstInteriorTet = _vbt->_tetNodes.size(); fillNonVnTetCenter(); // fast. Don't bother multithreading @@ -794,7 +794,7 @@ void vnBccTetCutter_tbb::decimateInteriorMicroTets(int firstInteriorMicroTet, st // tets were created in nested subdiv order, making hierarchical decimation easier _decimatedNodes.clear(); int n = _vbt->_tetCentroids.size(), i = firstInteriorMicroTet, nDec = 0; - for (int level = 2; level < _vbt->_tetSubdivisionLevels; ++level) { // decimate to one below megatet level +/* for (int level = 2; level < _vbt->_tetSubdivisionLevels; ++level) { // decimate to one below megatet level while (i < n) { bccTetCentroid subTc[8], tcUp = _vbt->centroidUpOneLevel(_vbt->_tetCentroids[i]); _vbt->subtetCentroids(tcUp, subTc); // invalid subtet outside positive octant labelled as all USHRT_MAX @@ -824,7 +824,7 @@ void vnBccTetCutter_tbb::decimateInteriorMicroTets(int firstInteriorMicroTet, st ++i; } i = n; - n = _vbt->_tetCentroids.size(); + n = _vbt->_tetCentroids.size(); */ // do one more level of decimation the hard way to validate algorithm - DONE /* auto makeMacrotet = [&](bccTetCentroid& tcUp, int(&subtets)[8]) { @@ -870,8 +870,10 @@ void vnBccTetCutter_tbb::decimateInteriorMicroTets(int firstInteriorMicroTet, st } ++i; } */ - } - // pack decimated tets amd nodes +// } + + + // pack decimated tets and nodes std::vector nodeMap; nodeMap.assign(_vbt->_nodeGridLoci.size(), -1); int offset = _vbt->_nMegatets; @@ -1044,7 +1046,7 @@ void vnBccTetCutter_tbb::decimateInteriorMicroTets(int firstInteriorMicroTet, st return false; }; int newT = 0; - for (int i = _meganodeSize; i < _firstNewExteriorNode; ++i) { // can't use exterior nodes since a virtual node could link across to wrong side + for (int i = _meganodeSize; i < _vbt->_nodeGridLoci.size(); ++i) { // _firstNewExteriorNode can't use exterior nodes since a virtual node could link across to wrong side? COURT check this if (possibleTJunction[i]) { int tri; Vec2f bary;