Files
render/tool/include/wintool.h
T
2026-08-17 06:31:43 +09:00

529 lines
21 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef ZCPPWIN_WINTOOL_H
#define ZCPPWIN_WINTOOL_H
// ============================================================================
// wintool.h
// ----------------------------------------------------------------------------
// MSVC / Windows / C++20 常用 Win32 工具库声明文件。
//
// 使用方式:
// 1. 将 wintool.h 与 wintool.cpp 加入你的 CLion + CMake 工程。
// 2. 推荐在 CMake 中启用 C++20,并定义 UNICODE、_UNICODE、NOMINMAX。
// 3. 所有接口都位于命名空间 Z 中,直接调用 Z::xxx 即可。
//
// 设计约定:
// - 默认优先使用宽字符版本,也就是 std::wstring / Win32 W 系列 API。
// - 失败时大多数函数会抛出 Z::Win32Error、Z::HResultError 或 std::runtime_error。
// - 资源对象采用 RAII,离开作用域后自动释放 HANDLE、HMODULE、HKEY 等资源。
// - 返回 std::optional 的函数表示“失败或不存在”不一定是异常场景。
//
// 简单示例:
// ZWINTOOL::SetConsoleUtf8();
// auto exe = ZWINTOOL::ModulePathW();
// ZWINTOOL::WriteTextFileUtf8(L"log.txt", "hello\n");
// auto text = ZWINTOOL::ReadTextFileUtf8(L"log.txt");
// ============================================================================
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <objbase.h>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include "ztool_lib_macro.h"
namespace ZWINTOOL {
// 通用字节数组类型,常用于二进制文件、注册表 REG_BINARY 等场景。
using Bytes = std::vector<std::byte>;
/* ---------------------------------------------------------------------------- */
//region 错误处理工具
/* ---------------------------------------------------------------------------- */
// Win32 错误异常。
// 作用:保存 GetLastError / Win32 API 返回的错误码,并生成可读错误文本。
// 用法:catch (const Z::Win32Error& e) 后可通过 e.code() 获取 DWORD 错误码。
class ZTOOL_API Win32Error final : public std::runtime_error {
public:
// error 为 Win32 错误码;context 用于标记失败位置,例如 "CreateFileW"。
explicit Win32Error(DWORD error, std::string_view context = {});
// 返回原始 Win32 错误码。
[[nodiscard]] DWORD code() const noexcept;
private:
DWORD code_{};
};
// HRESULT 错误异常。
// 作用:包装 COM、Shell、DirectX 等 API 常见的 HRESULT 失败值。
// 用法:catch (const Z::HResultError& e) 后可通过 e.hr() 获取 HRESULT。
class ZTOOL_API HResultError final : public std::runtime_error {
public:
// hr 为 HRESULTcontext 用于标记失败位置,例如 "CoCreateInstance"。
explicit HResultError(HRESULT hr, std::string_view context = {});
// 返回原始 HRESULT。
[[nodiscard]] HRESULT hr() const noexcept;
private:
HRESULT hr_{};
};
// 获取当前线程最近一次 Win32 错误码,相当于 GetLastError()。
[[nodiscard]] ZTOOL_API DWORD LastError() noexcept;
// 将 Win32 错误码格式化为宽字符串。
// 用法:std::wstring msg = Z::ErrorMessageW(ERROR_FILE_NOT_FOUND);
[[nodiscard]] ZTOOL_API std::wstring ErrorMessageW(DWORD error = ::GetLastError());
// 将 Win32 错误码格式化为 UTF-8 字符串。
[[nodiscard]] ZTOOL_API std::string ErrorMessageA(DWORD error = ::GetLastError());
// 将 HRESULT 格式化为宽字符串。
[[nodiscard]] ZTOOL_API std::wstring HResultMessageW(HRESULT hr);
// 将 HRESULT 格式化为 UTF-8 字符串。
[[nodiscard]] ZTOOL_API std::string HResultMessageA(HRESULT hr);
// 直接抛出当前 GetLastError() 对应的 Win32Error。
// 用法:if (!ok) Z::ThrowLastError("SomeWin32Api");
ZTOOL_API void ThrowLastError(std::string_view context = {});
// 检查 BOOL 返回值;为 FALSE 时抛出 Win32Error。
ZTOOL_API void ThrowIfFalse(BOOL ok, std::string_view context = {});
// 检查指针;为空时抛出 Win32Error。
ZTOOL_API void ThrowIfNull(const void *pointer, std::string_view context = {});
// 检查 HANDLEnullptr 或 INVALID_HANDLE_VALUE 时抛出 Win32Error。
ZTOOL_API void ThrowIfInvalidHandle(HANDLE handle, std::string_view context = {});
// 检查 HRESULTFAILED(hr) 时抛出 HResultError。
ZTOOL_API void CheckHResult(HRESULT hr, std::string_view context = {});
//endregion
/* ---------------------------------------------------------------------------- */
//region 字符串编码转换工具
/* ---------------------------------------------------------------------------- */
// UTF-8 转 UTF-16。
// 典型用途:把配置文件、网络文本、std::string 传给 Windows W 系列 API。
[[nodiscard]] ZTOOL_API std::wstring Utf8ToWide(std::string_view text);
// UTF-16 转 UTF-8。
// 典型用途:把 Win32 返回的 std::wstring 输出到日志、文件或控制台。
[[nodiscard]] ZTOOL_API std::string WideToUtf8(std::wstring_view text);
// 指定代码页的多字节字符串转 UTF-16。
// 用法:Z::MultiByteToWide(text, CP_ACP) 或 Z::MultiByteToWide(text, CP_UTF8)。
[[nodiscard]] ZTOOL_API std::wstring MultiByteToWide(std::string_view text, UINT codePage);
// UTF-16 转指定代码页多字节字符串。
[[nodiscard]] ZTOOL_API std::string WideToMultiByte(std::wstring_view text, UINT codePage);
//endregion
/* ---------------------------------------------------------------------------- */
//region RAII Win32 资源封装
/* ---------------------------------------------------------------------------- */
// HANDLE RAII 封装。
// 作用:自动调用 CloseHandle,避免文件、进程、线程、事件等句柄泄漏。
// 用法:Z::Handle file(::CreateFileW(...)); if (file) { ... }
class ZTOOL_API Handle final {
public:
constexpr Handle() noexcept = default;
// 接管一个已有 HANDLE;析构时自动 CloseHandle。
explicit Handle(HANDLE handle) noexcept;
~Handle() noexcept;
Handle(const Handle &) = delete;
Handle &operator=(const Handle &) = delete;
Handle(Handle &&other) noexcept;
Handle &operator=(Handle &&other) noexcept;
// 获取原始 HANDLE,不转移所有权。
[[nodiscard]] HANDLE get() const noexcept;
// 获取用于输出参数的地址;会先释放旧句柄。
// 用法:SomeApi(handle.put());
[[nodiscard]] HANDLE *put() noexcept;
// 判断是否持有有效句柄。
[[nodiscard]] bool valid() const noexcept;
explicit operator bool() const noexcept;
// 放弃所有权并返回原始 HANDLE;调用者之后需要自行 CloseHandle。
[[nodiscard]] HANDLE release() noexcept;
// 释放旧句柄并接管新句柄。
void reset(HANDLE handle = nullptr) noexcept;
private:
HANDLE handle_ = nullptr;
};
// HMODULE RAII 封装。
// 作用:LoadLibraryW / FreeLibrary 的安全封装,适合动态加载 DLL。
// 用法:Z::Library dll(L"user32.dll"); auto p = dll.proc<Fn*>("MessageBoxW");
class ZTOOL_API Library final {
public:
Library() noexcept = default;
explicit Library(std::wstring_view path);
~Library() noexcept;
Library(const Library &) = delete;
Library &operator=(const Library &) = delete;
Library(Library &&other) noexcept;
Library &operator=(Library &&other) noexcept;
[[nodiscard]] HMODULE get() const noexcept;
[[nodiscard]] bool valid() const noexcept;
explicit operator bool() const noexcept;
// 加载指定 DLL;已加载其他模块时会先释放。
void load(std::wstring_view path);
// 放弃所有权,调用者之后需要自行 FreeLibrary。
[[nodiscard]] HMODULE release() noexcept;
// 释放旧模块并接管新模块。
void reset(HMODULE module = nullptr) noexcept;
// 获取 DLL 导出函数地址,并转换为指定函数指针类型。
// 用法:using Fn = int (WINAPI*)(HWND,LPCWSTR,LPCWSTR,UINT);
// auto MessageBoxWFn = dll.proc<Fn>("MessageBoxW");
template<class T>
[[nodiscard]] T proc(std::string_view name) const {
if (!module_) {
throw std::logic_error("Z::Library::proc called on empty library");
}
std::string procName{name};
FARPROC p = ::GetProcAddress(module_, procName.c_str());
if (!p) {
ThrowLastError("GetProcAddress");
}
return reinterpret_cast<T>(p);
}
private:
HMODULE module_ = nullptr;
};
// COM 初始化 RAII 封装。
// 作用:构造时 CoInitializeEx,析构时 CoUninitialize。
// 用法:Z::ComApartment com; 然后安全调用 COM / Shell API。
class ZTOOL_API ComApartment final {
public:
// coinit 可传 COINIT_APARTMENTTHREADED 或 COINIT_MULTITHREADED。
explicit ComApartment(DWORD coinit = COINIT_APARTMENTTHREADED);
~ComApartment() noexcept;
ComApartment(const ComApartment &) = delete;
ComApartment &operator=(const ComApartment &) = delete;
// 返回 CoInitializeEx 的结果,通常 S_OK 或 S_FALSE 表示可用。
[[nodiscard]] HRESULT hr() const noexcept;
private:
HRESULT hr_ = S_OK;
bool initialized_ = false;
};
//endregion
/* ---------------------------------------------------------------------------- */
//region 路径与环境变量工具
/* ---------------------------------------------------------------------------- */
// 获取当前 EXE 或指定模块的完整路径。
[[nodiscard]] ZTOOL_API std::wstring ModulePathW(HMODULE module = nullptr);
// 获取当前 EXE 或指定模块所在目录。
[[nodiscard]] ZTOOL_API std::wstring ModuleDirW(HMODULE module = nullptr);
// 获取当前工作目录。
[[nodiscard]] ZTOOL_API std::wstring CurrentDirectoryW();
// 获取系统临时目录。
[[nodiscard]] ZTOOL_API std::wstring TempDirectoryW();
// 获取父目录路径。示例:C:\\a\\b.txt -> C:\\a。
[[nodiscard]] ZTOOL_API std::wstring ParentPathW(std::wstring_view path);
// 获取文件名部分。示例:C:\\a\\b.txt -> b.txt。
[[nodiscard]] ZTOOL_API std::wstring FileNameW(std::wstring_view path);
// 拼接两个路径片段,会自动处理反斜杠。
[[nodiscard]] ZTOOL_API std::wstring JoinPathW(std::wstring_view left, std::wstring_view right);
// 转换为 Windows 长路径形式。绝对路径会加 \\?\ 前缀,便于突破 MAX_PATH 限制。
[[nodiscard]] ZTOOL_API std::wstring ToLongPathW(std::wstring_view path);
// 读取环境变量;不存在时返回 std::nullopt。
[[nodiscard]] ZTOOL_API std::optional<std::wstring> EnvironmentVariableW(std::wstring_view name);
// 设置环境变量;value 为 nullopt 时删除环境变量。
ZTOOL_API void SetEnvironmentVariableWZ(std::wstring_view name,
std::optional<std::wstring_view> value);
// 展开字符串中的环境变量。示例:%TEMP%\\a.txt。
[[nodiscard]] ZTOOL_API std::wstring ExpandEnvironmentStringsWZ(std::wstring_view text);
//endregion
/* ---------------------------------------------------------------------------- */
//region 文件系统工具
/* ---------------------------------------------------------------------------- */
// 判断路径是否存在,文件或目录均可。
[[nodiscard]] ZTOOL_API bool PathExistsW(std::wstring_view path);
// 判断路径是否存在且为普通文件。
[[nodiscard]] ZTOOL_API bool FileExistsW(std::wstring_view path);
// 判断路径是否存在且为目录。
[[nodiscard]] ZTOOL_API bool DirectoryExistsW(std::wstring_view path);
// 递归创建目录。目录已存在时返回 true。
[[nodiscard]] ZTOOL_API bool CreateDirectoryTreeW(std::wstring_view path);
// 读取整个文件为字节数组。失败时抛出 Win32Error。
[[nodiscard]] ZTOOL_API Bytes ReadFileBytes(std::wstring_view path);
// 写入字节数组到文件;append=true 时追加写入,否则覆盖。
ZTOOL_API void WriteFileBytes(std::wstring_view path, std::span<const std::byte> data,
bool append = false);
// 按 UTF-8 文本读取文件。不会自动去除 BOM。
[[nodiscard]] ZTOOL_API std::string ReadTextFileUtf8(std::wstring_view path);
// 按 UTF-8 文本写入文件;writeBom=true 时写入 UTF-8 BOM。
ZTOOL_API void WriteTextFileUtf8(std::wstring_view path, std::string_view text, bool append = false,
bool writeBom = false);
//endregion
/* ---------------------------------------------------------------------------- */
//region 进程与命令行工具
/* ---------------------------------------------------------------------------- */
// 启动进程参数。
// application:可执行文件路径,可为空。
// commandLine:完整命令行;如果 application 为空,应包含可执行文件。
// workingDirectory:工作目录,可为空。
// creationFlagsCreateProcessW 标志。
// inheritHandles:是否继承句柄。
// showWindow/showCommand:控制窗口显示状态。
struct ZTOOL_API ProcessStartInfo {
std::wstring application;
std::wstring commandLine;
std::wstring workingDirectory;
DWORD creationFlags = 0;
bool inheritHandles = false;
bool showWindow = true;
WORD showCommand = SW_SHOWNORMAL;
};
// 启动进程后的结果。
// process/thread 使用 RAII 管理,离开作用域自动 CloseHandle。
struct ZTOOL_API ProcessInfo {
Handle process;
Handle thread;
DWORD processId = 0;
DWORD threadId = 0;
};
// 获取当前进程命令行参数,等价于 main/wmain 的 argv。
[[nodiscard]] ZTOOL_API std::vector<std::wstring> CommandLineArgsW();
// 将单个参数转义为 Windows 命令行安全格式。
[[nodiscard]] ZTOOL_API std::wstring QuoteCommandLineArgW(std::wstring_view arg);
// 把多个参数拼接成安全命令行。
[[nodiscard]] ZTOOL_API std::wstring BuildCommandLineW(std::span<const std::wstring> args);
// 启动进程。失败时抛出 Win32Error。
[[nodiscard]] ZTOOL_API ProcessInfo StartProcess(const ProcessStartInfo &info);
// 等待进程结束。
// 返回:进程退出码;如果超时返回 std::nullopt。
[[nodiscard]] ZTOOL_API std::optional<DWORD> WaitForProcess(HANDLE process,
DWORD milliseconds = INFINITE);
// 判断当前进程是否以管理员权限运行。
[[nodiscard]] ZTOOL_API bool IsProcessElevated();
// 获取当前 Windows 用户名。
[[nodiscard]] ZTOOL_API std::wstring UserNameW();
// 获取计算机名。
[[nodiscard]] ZTOOL_API std::wstring ComputerNameW();
// 获取当前进程 ID。
[[nodiscard]] ZTOOL_API DWORD CurrentProcessId() noexcept;
// 获取当前线程 ID。
[[nodiscard]] ZTOOL_API DWORD CurrentThreadId() noexcept;
//endregion
/* ---------------------------------------------------------------------------- */
//region 注册表工具
/* ---------------------------------------------------------------------------- */
// HKEY RAII 封装。
// 作用:自动 RegCloseKey,提供常用注册表读写和枚举功能。
// 用法:
// auto key = Z::RegKey::Open(HKEY_CURRENT_USER, L"Software\\MyApp");
// auto name = key.GetStringValue(L"Name");
class ZTOOL_API RegKey final {
public:
RegKey() noexcept = default;
// 接管已有 HKEY;析构时自动 RegCloseKey。
explicit RegKey(HKEY key) noexcept;
~RegKey() noexcept;
RegKey(const RegKey &) = delete;
RegKey &operator=(const RegKey &) = delete;
RegKey(RegKey &&other) noexcept;
RegKey &operator=(RegKey &&other) noexcept;
// 打开注册表键;失败时抛出 Win32Error。
static RegKey Open(HKEY root, std::wstring_view subKey, REGSAM access = KEY_READ);
// 尝试打开注册表键;不存在时返回 std::nullopt,其他错误抛异常。
static std::optional<RegKey> TryOpen(HKEY root, std::wstring_view subKey,
REGSAM access = KEY_READ);
// 创建或打开注册表键。
static RegKey Create(HKEY root, std::wstring_view subKey,
REGSAM access = KEY_READ | KEY_WRITE);
[[nodiscard]] HKEY get() const noexcept;
[[nodiscard]] HKEY *put() noexcept;
[[nodiscard]] bool valid() const noexcept;
explicit operator bool() const noexcept;
[[nodiscard]] HKEY release() noexcept;
void reset(HKEY key = nullptr) noexcept;
// 读取字符串值。支持 REG_SZ / REG_EXPAND_SZ;不存在返回 nullopt。
[[nodiscard]] std::optional<std::wstring> GetStringValue(
std::wstring_view valueName = {}) const;
// 写入字符串值。type 通常为 REG_SZ 或 REG_EXPAND_SZ。
void SetStringValue(std::wstring_view valueName, std::wstring_view value,
DWORD type = REG_SZ) const;
// 读取 DWORD 值;不存在返回 nullopt。
[[nodiscard]] std::optional<DWORD> GetDwordValue(
std::wstring_view valueName = {}) const;
// 写入 DWORD 值。
void SetDwordValue(std::wstring_view valueName, DWORD value) const;
// 读取 QWORD 值;不存在返回 nullopt。
[[nodiscard]] std::optional<unsigned long long> GetQwordValue(
std::wstring_view valueName = {}) const;
// 写入 QWORD 值。
void SetQwordValue(std::wstring_view valueName, unsigned long long value) const;
// 读取二进制值;不存在返回 nullopt。
[[nodiscard]] std::optional<Bytes> GetBinaryValue(
std::wstring_view valueName = {}) const;
// 写入二进制值。
void SetBinaryValue(std::wstring_view valueName,
std::span<const std::byte> data) const;
// 枚举子键名。
[[nodiscard]] std::vector<std::wstring> EnumSubKeys() const;
// 枚举值名。
[[nodiscard]] std::vector<std::wstring> EnumValueNames() const;
private:
HKEY key_ = nullptr;
};
//endregion
/* ---------------------------------------------------------------------------- */
//region 窗口工具
/* ---------------------------------------------------------------------------- */
// 获取窗口标题文本。
[[nodiscard]] ZTOOL_API std::wstring WindowTextW(HWND hwnd);
// 获取窗口标题文本并转换为 UTF-8。
[[nodiscard]] ZTOOL_API std::string WindowTextUtf8(HWND hwnd);
// 使用 UTF-8 文本设置窗口标题。
ZTOOL_API void SetWindowTextUtf8(HWND hwnd, std::string_view text);
// 获取窗口类名。
[[nodiscard]] ZTOOL_API std::wstring ClassNameW(HWND hwnd);
// 获取窗口屏幕坐标矩形。
[[nodiscard]] ZTOOL_API RECT WindowRect(HWND hwnd);
// 获取窗口客户区矩形。
[[nodiscard]] ZTOOL_API RECT ClientRect(HWND hwnd);
// 将窗口移动到父窗口或屏幕中央。
ZTOOL_API void CenterWindow(HWND hwnd, HWND parent = nullptr);
//endregion
/* ---------------------------------------------------------------------------- */
//region 剪贴板与控制台工具
/* ---------------------------------------------------------------------------- */
// 设置剪贴板 Unicode 文本。
ZTOOL_API void SetClipboardTextW(std::wstring_view text, HWND owner = nullptr);
// 读取剪贴板 Unicode 文本;没有文本时返回 nullopt。
[[nodiscard]] ZTOOL_API std::optional<std::wstring> GetClipboardTextW(HWND owner = nullptr);
// 将控制台输入/输出代码页设置为 UTF-8。
// 常用于 main 开头,配合中文日志输出。
ZTOOL_API void SetConsoleUtf8();
// 判断当前进程是否已经拥有控制台。
[[nodiscard]] ZTOOL_API bool HasConsole() noexcept;
// 尝试附加父进程控制台;失败则分配一个新控制台。
[[nodiscard]] ZTOOL_API bool AttachOrAllocConsole();
//endregion
} // namespace Z
#endif //ZCPPWIN_WINTOOL_H