A modern C++17 wrapper for Win32 Registry utilities.
- LocalMachine
- ClassesRoot
- Users
- CurrentUser
You start from a pre-opened key (listed above) to open a subkey or create a subkey.
Opening a subkey is as simple as using the familiar array indexing operator[L"subkey"]
.
For example, to open HKEY_CURRENT_USER\Control Panel
, you do
auto controlPanel = CurrentUser[L"Control Panel"];
The []
operator can be chanined to open subkeys at any level
(up to 512, the maximum supported depth in registry) in a single line.
auto currentVersion = CurrentUser[L"Software"][L"Microsoft"][L"Windows"][L"CurrentVersion"];
To create a subkey, you call .create(L"subkey")
on an opened key.
auto newKey = key.create(L"newKey");
You don't need to worry about closing the keys. It is handled automatically in the destructor.
If you need to explicitly write registry data to the hard disk, you call .flush()
key.flush();
Use the operator +=Value<ValueType>(...)
to add a value and
operator -= L"valueName"
to delete a value. For example,
auto run = CurrentUser[L"Software"][L"Microsoft"][L"Windows"][L"CurrentVersion"][L"Run"];
run -= L"LGHUB"; //Stop logitech g-hub auto launching when Windows start up
run += Value<Type::String>(L"My Application", L"...");
There are 2 ways to read a value.
- If you are sure about the type, use
key.valueOf(L"valueName").as<type>()
The.as<>()
call returns an instance ofValue<>
type (see below)auto binaryValue = CurrentUser[L"Microsoft"][L"Windows"][L"CurrentVersion"][L"Explorer"][L"StartupApproved"][L"Run"] .valueOf(L"OneDriveSetup") .as<Type::Binary>();
- If you are NOT sure about the type, first get the type, then use a
switch
statement to handle it according to your needauto unknownTypeValue = CurrentUser[L"subKey"].valueOf(L"valueName"); switch(unknownTypeValue.getType()) { case Type::Binary: { auto binaryValue = unknownTypeValue.as<Type::Binary>(); //process binary value... } //other cases... }
To enumerate a key, you can either use the iterator return from .begin()
and .end()
,
or a range-based for
loop, which uses the iterator underhood.
When the iterator is dereferenced with operator*
, it returns a std::variant
of all the
supported value-types (see below) as well as the key type.
Then you may use std::visit()
to call different functions
for different value types.
template<typename... Functions>
struct overloaded : Functions...
{
using Functions::operator()...;
};
template<typename... Functions>
overloaded(Functions...) -> overloaded<Functions...>;
for(auto child : key) //child is a std::variant
{
std::visit(
overloaded
{
[](Value<Type::Binary> const& value)
{
//process binary value
},
[](Value<Type::String> const& value)
{
//process string value
}
},
child
);
}
Since delete
is a keyword in C++, I chooose to use the word .remove()
instead.
If you can be sure that there are no subkeys and values in the current key, you use
.remove(L"key")
. Why not use operator-=
you ask?
Because there are no ways to distinguish whether to delete a key (fail if there are subkeys)
or to delete a key recursively using operator-=
with a name.
And in fact, operator -=
is for [deleting a value from a key](Deleting a value from a key).
See docs.
Values types are strongly-typed classes, with Type
as its non-type template parameters.
You usually get an instance of this class from key.valueOf(L"valueName").as<type>()
All value types are listed as below
Value<Type::Binary>
->REG_BINARY
Value<Type::Dword>
->REG_DWORD
Value<Type::UnexpandedString>
->REG_EXPAND_SZ
Value<Type::MultiString>
->REG_MULTI_SZ
Value<Type::Qword>
->REG_QWORD
Value<Type::String>
->REG_SZ
All Value<>
types support these methods:
get
that returns the data that best expressed in C++- Binary type returns
std::vector<BYTE>
- Dword type returns
DWORD
- Qword type returns
QWORD
- String types (including unexpanded string and multi-string) returns
std::wstring
- Binary type returns
getData()
returns a pointer to the underlying storage, which you should not modifygetName()
returns the name of the value
There are also additional methods that are supported in specific types, for example,
Value<Type::UnexpanedString>
has .expand()
that returns a string with the environment variables expanded.
You can find out those methods with the assistance of your IDE.
You usually construct a value when editing a key.
A value is constructed with a value name, and its corresponding data that is best expressed in C++ that already described above.
For example, a Value<Type::String>
can be constructed with a name and a string.
Value<Type::String>{L"name", L"value"};
This small header-only library is written with inter-op with Win32 APIs in mind for greater flexibility.
There is a getter function called .getXYZ()
on multiple wrapper classes. For example
HKEY keyHandle = key.getHandle();
I am too lazy to bother setting up CI for testing. After all, this would be a small convenient utility for my company's software.
You can checkout the test code in test.cpp
.
Here are the screenshots of its running.