I was tired of using outdated raw C Windows API in my modern projects, so I decided to write my own C++20 API wrapper, which is focusing on safety and extensive use of modern C++20 features such as templates, constexpr and operator overloading. Also, flooding the global namespace with Windows API types, functions and especially macros (which breaks existing STL code btw) is not a good practice for me.
This library provides partial wrapper for Windows API functions and types. Feel free to extend it and submit PRs.
Remark: UNICODE
macro is automatically defined when compiling this library. Idk if this is a good idea, but docs said ANSI versions are deprecated, so it's not a big deal.
- Wraps some Win32API functions and types into nice classes.
- Replaces
GetLastError()
-based error handling withwinapi::windows_exception
, which provides string error message out-of-the-box and can be used intry
blocks. - No more
void*
orwchar_t*
-ptr based type erasing. All fields and arguments are wrapped into STL (if possible) or my own newtypes (so paths becomesstd::filesystem::path
instead ofLPCWSTR
). - All Win32API headers are hidden in implementation. No more polluting global namespace with strange stuff.
- Also, working with
wchar_t
strings is a pain, so all strings in this library is good oldstd::string
, which is internally converted to correctwchar_t
representation if needed. - See Examples below for other cool features!
- C++20 compiler. I tested it with MinGW, feel free to sumbit issues if you want for me to add support for other compilers.
- CMake 3.16 at least.
- fmt. You can install it via
conan
,vcpkg
ormsys2
(see Installation).
If you want to contribute, please see CONTRIBUTING.md.
I recommend using conan to build this library. Run:
conan install . -b "missing" -s "build_type=Release"
cmake --preset "conan-release" .
cmake --build build/Release
cmake --install build/Release --prefix "where-you-want-the-library-to-be-installed"
Alternatively, you can build it yourself with CMake, passing prefix path:
cmake -S . -B target -DCMAKE_PREFIX_PATH="path-to-fmt" -DCMAKE_BUILD_TYPE=Release
cmake --build target
cmake --install target --prefix "<where-you-want-the-library-to-be-installed>"
If you want to build tests and examples, use following commands:
For conan:
conan install . -b missing -s "build_type=Release" -o "winapi20/*:test=True" -o "winapi20/*:examples=True"
cmake --preset "conan-release" .
cmake --build build/Release
For cmake:
cmake -S . -B target -DCMAKE_PREFIX_PATH="<path-to-fmt>" -DCMAKE_BUILD_TYPE=Release -DTESTS=ON -DEXAMPLES=ON
cmake --build target --target all
Finds process by name, retrieves dll with given name from it and calls function with given name in it, returning result.
// wrap this in try block if you want to handle errors properly.
// make snapshot of system processes
auto snapshot = Snapshot(Snapshot::IncludeFlags::Process, PID::current());
// find process by name
auto process = snapshot.find_first_by_name<ProcessEntry>("explorer.exe");
if(not process)
return 1;
fmt::print("found explorer.exe: {}\n", *process);
// open view-only handle to library
auto lib = dll::Library(View, "kernel32.dll", process->pid);
fmt::print("library path: {}\n", lib.file_path().string());
auto fn = lib["MulDiv"]; // aka `lib.exported_function_address("MulDiv")`;
if(not fn)
return 1;
auto const res = fn->call<int>(10, 2, 5);
fmt::print("calling function MulDiv(10, 2, 5) = {}\n", *fn);
fmt::print("10 * 2 / 5 = {}\n", res);
Possible output:
found explorer.exe: ProcessEntry { pid: PID(585656), parent_pid: PID(76060), thread_count: 182...
library path: C:\Windows\System32\KERNEL32.DLL
calling function MulDiv(10, 2, 5) = Function(0x7ffd429e0930)
10 * 2 / 5 = 4
Finds process by name and retrieve module with given name from it.
auto snapshot = Snapshot(Snapshot::IncludeFlags::Process, PID::current());
// find process by name
auto game = snapshot.find_first_by_name<ProcessEntry>("dota2.exe");
if(not game)
return 1;
fmt::print("found dota2.exe: {}\n", *game);
// make snapshot of modules of the process
auto module_snapshot = Snapshot(Snapshot::IncludeFlags::Module | Snapshot::IncludeFlags::Process, game->pid);
// get vector of all modules
auto modules = module_snapshot.modules();
// exclude all non-game modules (not containing "dota 2 beta" in path)
modules.erase(std::remove_if(
modules.begin(),
modules.end(),
[](auto& m) { return m.path.string().find("dota 2 beta") == std::string::npos; }), modules.end()
);
fmt::print("listing game modules:\n");
fmt::print("\t{}", fmt::join(modules, "\n\t"));
// find module by name in process
auto client_dll = module_snapshot.find_first_by_name<ModuleEntry>("client.dll");
if(not client_dll)
return 1;
fmt::print("\nfound client.dll: {}\n", *client_dll);
// get handle to module
fmt::print("client.dll handle: {}\n", client_dll->handle);
fmt::print("client.dll base address: {}\n", client_dll->base_address);
Possible output:
found dota2.exe: ProcessEntry { pid: 346156, parent_pid: 517408, thread_count: 82, thread_base_priority: 8, name: dota2.exe }
listing game modules:
ModuleEntry { pid: 346156, base_address: 140699576827904, size: 2416640, handle: Handle(0x7ff72c4c0000), name: dota2.exe, path: C:\Program Files (x86)\Steam\steamapps\common\dota 2 beta\game\bin\win64\dota2.exe }
...
ModuleEntry { pid: 346156, base_address: ...
found client.dll: ModuleEntry { pid: 346156, base_address: 140722899255296, size: 85884928, handle: Handle(0x7ffc9a6c0000), name: client.dll, path: C:\Program Files (x86)\Steam\steamapps\common\dota 2 beta\game\dota\bin\win64\client.dll }
client.dll handle: Handle(0x7ffc9a6c0000)
client.dll base address: 140722899255296