Files
CPP_Core/Core/system/win.cpp
T
2026-06-16 10:56:40 +08:00

654 lines
23 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.
#if defined(_WIN32)
#include "windows_include.h"
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <cwchar>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <system_error>
#include <thread>
#include <type_traits>
#include <vector>
#include <dbghelp.h>
#include <objbase.h>
#include <oleauto.h>
#include <psapi.h>
#include <wbemidl.h>
#include "../Base/global_include.h"
#include "../system/export.h"
#include "Core/Base/JSON.h"
namespace Psc {
namespace {
std::wstring utf8_to_wide_impl(const std::string& input) {
if (input.empty()) {
return L"";
}
const int size = MultiByteToWideChar(CP_UTF8, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
if (size <= 0) {
return L"";
}
std::wstring result(static_cast<size_t>(size), L'\0');
MultiByteToWideChar(CP_UTF8, 0, input.data(), static_cast<int>(input.size()), result.data(), size);
return result;
}
std::string wide_to_utf8_impl(const wchar_t* input, int input_len = -1) {
if (!input) {
return "";
}
const int size = WideCharToMultiByte(CP_UTF8, 0, input, input_len, nullptr, 0, nullptr, nullptr);
if (size <= 0) {
return "";
}
std::string result(static_cast<size_t>(size), '\0');
WideCharToMultiByte(CP_UTF8, 0, input, input_len, result.data(), size, nullptr, nullptr);
if (input_len == -1 && !result.empty() && result.back() == '\0') {
result.pop_back();
}
return result;
}
std::string wide_to_utf8_impl(const std::wstring& input) {
return wide_to_utf8_impl(input.c_str(), static_cast<int>(input.size()));
}
std::string codepage_to_utf8(const std::string& input, UINT codepage) {
if (input.empty()) {
return "";
}
const int wide_len = MultiByteToWideChar(codepage, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
if (wide_len <= 0) {
return "";
}
std::wstring wide(static_cast<size_t>(wide_len), L'\0');
MultiByteToWideChar(codepage, 0, input.data(), static_cast<int>(input.size()), wide.data(), wide_len);
return wide_to_utf8_impl(wide);
}
std::string utf8_to_codepage(const std::string& input, UINT codepage) {
if (input.empty()) {
return "";
}
const std::wstring wide = utf8_to_wide_impl(input);
if (wide.empty()) {
return "";
}
const int len = WideCharToMultiByte(codepage, 0, wide.data(), static_cast<int>(wide.size()), nullptr, 0, nullptr, nullptr);
if (len <= 0) {
return "";
}
std::string result(static_cast<size_t>(len), '\0');
WideCharToMultiByte(codepage, 0, wide.data(), static_cast<int>(wide.size()), result.data(), len, nullptr, nullptr);
return result;
}
std::wstring read_env_w(const wchar_t* name) {
DWORD required = GetEnvironmentVariableW(name, nullptr, 0);
if (required == 0) {
return L"";
}
std::wstring result(required, L'\0');
DWORD written = GetEnvironmentVariableW(name, result.data(), required);
if (written == 0) {
return L"";
}
result.resize(written);
return result;
}
HANDLE thread_to_win32_handle(std::thread* thread) noexcept {
if (!thread) {
return nullptr;
}
using Native = std::thread::native_handle_type;
if constexpr (std::is_pointer<Native>::value) {
return reinterpret_cast<HANDLE>(thread->native_handle());
} else if constexpr (std::is_integral<Native>::value) {
return reinterpret_cast<HANDLE>(thread->native_handle());
} else {
// MinGW 的 libstdc++ 在某些配置下使用 winpthreadsnative_handle_type 不是 Win32 HANDLE。
return nullptr;
}
}
struct Bstr_Guard {
explicit Bstr_Guard(const wchar_t* value) : value(SysAllocString(value)) {}
~Bstr_Guard() { if (value) SysFreeString(value); }
Bstr_Guard(const Bstr_Guard&) = delete;
Bstr_Guard& operator=(const Bstr_Guard&) = delete;
BSTR value{};
};
template <typename T>
void release_com(T*& ptr) noexcept {
if (ptr) {
ptr->Release();
ptr = nullptr;
}
}
std::string fallback_stack_trace_without_dbghelp(void* const* stack, USHORT frames) {
std::ostringstream oss;
oss << "Stack trace addresses only; DbgHelp unavailable.\n";
for (USHORT i = 0; i < frames; ++i) {
oss << "Frame " << std::setw(2) << i << ": " << stack[i] << '\n';
}
return oss.str();
}
}
std::string get_stack_trace() {
constexpr int max_frames = 64;
void* stack[max_frames]{};
const USHORT frames = CaptureStackBackTrace(0, max_frames, stack, nullptr);
HMODULE dbghelp = LoadLibraryW(L"DbgHelp.dll");
if (!dbghelp) {
return fallback_stack_trace_without_dbghelp(stack, frames);
}
using SymInitializeT = BOOL(WINAPI*)(HANDLE, PCSTR, BOOL);
using SymSetOptionsT = DWORD(WINAPI*)(DWORD);
using SymFromAddrT = BOOL(WINAPI*)(HANDLE, DWORD64, PDWORD64, PSYMBOL_INFO);
using SymGetLineFromAddr64T = BOOL(WINAPI*)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64);
using SymCleanupT = BOOL(WINAPI*)(HANDLE);
auto pSymInitialize = reinterpret_cast<SymInitializeT>(GetProcAddress(dbghelp, "SymInitialize"));
auto pSymSetOptions = reinterpret_cast<SymSetOptionsT>(GetProcAddress(dbghelp, "SymSetOptions"));
auto pSymFromAddr = reinterpret_cast<SymFromAddrT>(GetProcAddress(dbghelp, "SymFromAddr"));
auto pSymGetLineFromAddr64 = reinterpret_cast<SymGetLineFromAddr64T>(GetProcAddress(dbghelp, "SymGetLineFromAddr64"));
auto pSymCleanup = reinterpret_cast<SymCleanupT>(GetProcAddress(dbghelp, "SymCleanup"));
if (!pSymInitialize || !pSymSetOptions || !pSymFromAddr || !pSymGetLineFromAddr64) {
FreeLibrary(dbghelp);
return fallback_stack_trace_without_dbghelp(stack, frames);
}
HANDLE process = GetCurrentProcess();
std::ostringstream oss;
if (!pSymInitialize(process, nullptr, TRUE)) {
FreeLibrary(dbghelp);
return fallback_stack_trace_without_dbghelp(stack, frames);
}
pSymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
constexpr DWORD max_name_len = MAX_SYM_NAME;
std::vector<unsigned char> symbol_buffer(sizeof(SYMBOL_INFO) + max_name_len * sizeof(char));
auto* symbol = reinterpret_cast<SYMBOL_INFO*>(symbol_buffer.data());
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = max_name_len;
for (USHORT i = 0; i < frames; ++i) {
DWORD64 address = reinterpret_cast<DWORD64>(stack[i]);
DWORD64 displacement64 = 0;
oss << "Frame " << std::right << std::setw(2) << i << ": ";
if (pSymFromAddr(process, address, &displacement64, symbol)) {
oss << std::left << std::setw(30) << symbol->Name << " ";
} else {
oss << std::left << std::setw(30) << "<unknown>" << " ";
}
IMAGEHLP_LINE64 line{};
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
DWORD displacement = 0;
if (pSymGetLineFromAddr64(process, address, &displacement, &line)) {
oss << line.FileName << ':' << line.LineNumber << " at 0x" << std::hex << address << std::dec << '\n';
} else {
oss << "at 0x" << std::hex << address << std::dec << '\n';
}
}
if (pSymCleanup) {
pSymCleanup(process);
}
FreeLibrary(dbghelp);
return oss.str();
}
void set_current_thread_name(const std::string& name) {
const std::wstring wname = utf8_to_wide_impl(name);
if (wname.empty()) {
return;
}
using SetThreadDescriptionT = HRESULT(WINAPI*)(HANDLE, PCWSTR);
HMODULE kernel32 = GetModuleHandleW(L"Kernel32.dll");
auto pSetThreadDescription = kernel32
? reinterpret_cast<SetThreadDescriptionT>(GetProcAddress(kernel32, "SetThreadDescription"))
: nullptr;
if (pSetThreadDescription) {
(void)pSetThreadDescription(GetCurrentThread(), wname.c_str());
}
}
void set_console_utf8() {
(void)SetConsoleOutputCP(CP_UTF8);
(void)SetConsoleCP(CP_UTF8);
}
PSC_DEFINE_TRIPLE_API_FROM_BOOL(void*, load_library, PSC_LOAD_LIBRARY_PARAMS)
bool load_library_ec(const std::string& library_path, void*& out, std::error_code& ec) noexcept {
out = nullptr;
ec.clear();
if (library_path.empty()) {
ec = std::make_error_code(std::errc::invalid_argument);
return false;
}
const std::wstring wpath = utf8_to_wide_impl(library_path);
HMODULE h = LoadLibraryW(wpath.c_str());
if (!h) {
ec = std::error_code(static_cast<int>(GetLastError()), std::system_category());
std::cerr << "加载库失败:【" << library_path << ""
<< ec.message() << " (code=" << ec.value() << ")\n";
return false;
}
out = reinterpret_cast<void*>(h);
return true;
}
void* load_library_fail_fast(const std::string& library_path) {
auto ret = try_load_library(library_path);
if (!ret) {
Psc::fail_fast();
}
return ret.value();
}
void free_library(void* library) {
if (library) {
FreeLibrary(reinterpret_cast<HMODULE>(library));
}
}
PSC_DEFINE_TRIPLE_API_FROM_BOOL(void*, load_function, PSC_LOAD_FUNCTION_PARAMS)
bool load_function_ec(void* library, const std::string& function_name, void*& out, std::error_code& ec) noexcept {
out = nullptr;
ec.clear();
if (!library) {
ec = std::make_error_code(std::errc::invalid_argument);
std::cerr << "load_function: " << function_name << " but library == nullptr\n";
return false;
}
FARPROC p = GetProcAddress(reinterpret_cast<HMODULE>(library), function_name.c_str());
if (!p) {
ec = std::error_code(static_cast<int>(GetLastError()), std::system_category());
std::cerr << "Failed to get function pointer for: " << function_name
<< " : " << ec.message() << " (code=" << ec.value() << ")\n";
return false;
}
out = reinterpret_cast<void*>(p);
return true;
}
void* load_function_fail_fast(void* library, const std::string& function_name) {
auto ret = try_load_function(library, function_name);
if (!ret) {
Psc::fail_fast();
}
return ret.value();
}
std::string get_computer_serial_number() {
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
const bool need_uninit = SUCCEEDED(hr);
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) {
return "";
}
hr = CoInitializeSecurity(nullptr, -1, nullptr, nullptr,
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE,
nullptr, EOAC_NONE, nullptr);
if (FAILED(hr) && hr != RPC_E_TOO_LATE) {
if (need_uninit) {
CoUninitialize();
}
return "";
}
IWbemLocator* locator = nullptr;
IWbemServices* services = nullptr;
IEnumWbemClassObject* enumerator = nullptr;
IWbemClassObject* object = nullptr;
std::string result;
hr = CoCreateInstance(CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER,
IID_IWbemLocator, reinterpret_cast<void**>(&locator));
if (FAILED(hr) || !locator) {
if (need_uninit) CoUninitialize();
return "";
}
Bstr_Guard ns(L"ROOT\\CIMV2");
hr = locator->ConnectServer(ns.value, nullptr, nullptr, nullptr, 0, nullptr, nullptr, &services);
if (SUCCEEDED(hr) && services) {
hr = CoSetProxyBlanket(services,
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
nullptr,
RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE,
nullptr,
EOAC_NONE);
}
if (SUCCEEDED(hr) && services) {
Bstr_Guard lang(L"WQL");
Bstr_Guard query(L"SELECT SerialNumber FROM Win32_BIOS");
hr = services->ExecQuery(lang.value, query.value,
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
nullptr, &enumerator);
}
if (SUCCEEDED(hr) && enumerator) {
ULONG returned = 0;
hr = enumerator->Next(WBEM_INFINITE, 1, &object, &returned);
if (SUCCEEDED(hr) && returned > 0 && object) {
VARIANT vtSerial;
VariantInit(&vtSerial);
hr = object->Get(L"SerialNumber", 0, &vtSerial, nullptr, nullptr);
if (SUCCEEDED(hr) && vtSerial.vt == VT_BSTR && vtSerial.bstrVal) {
result = wide_to_utf8_impl(vtSerial.bstrVal);
}
VariantClear(&vtSerial);
}
}
release_com(object);
release_com(enumerator);
release_com(services);
release_com(locator);
if (need_uninit) {
CoUninitialize();
}
return result;
}
std::string error_code_to_string(DWORD error_code) {
wchar_t* message = nullptr;
DWORD size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&message),
0,
nullptr);
std::string result = "错误码:" + std::to_string(error_code) + ": ";
if (size > 0 && message) {
result += wide_to_utf8_impl(message, static_cast<int>(size));
LocalFree(message);
} else {
result += "获取错误信息失败";
}
return result;
}
std::string get_error_message() {
return error_code_to_string(GetLastError());
}
std::string get_error_message(ERROR_CODE_TYPE code) {
return error_code_to_string(static_cast<DWORD>(code));
}
ERROR_CODE_TYPE get_error_code() {
return static_cast<ERROR_CODE_TYPE>(GetLastError());
}
void set_error_code(ERROR_CODE_TYPE code) {
SetLastError(static_cast<DWORD>(code));
}
std::string to_upper_case(const std::string& input) {
std::string result = input;
for (char& ch : result) {
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
}
return result;
}
std::string get_home_dir() {
std::wstring home = read_env_w(L"USERPROFILE");
if (home.empty()) {
std::wstring drive = read_env_w(L"HOMEDRIVE");
std::wstring path = read_env_w(L"HOMEPATH");
home = drive + path;
}
std::string result = wide_to_utf8_impl(home);
std::replace(result.begin(), result.end(), '\\', '/');
return result;
}
std::string get_exe_path() {
std::wstring buffer(MAX_PATH, L'\0');
for (;;) {
DWORD len = GetModuleFileNameW(nullptr, buffer.data(), static_cast<DWORD>(buffer.size()));
if (len == 0) {
return "";
}
if (len < buffer.size() - 1) {
buffer.resize(len);
break;
}
buffer.resize(buffer.size() * 2);
if (buffer.size() > 32768) {
return "";
}
}
std::string result = wide_to_utf8_impl(buffer);
std::replace(result.begin(), result.end(), '\\', '/');
return result;
}
std::string utf8_to_gbk(const std::string& utf8Str) {
return utf8_to_codepage(utf8Str, CP_ACP);
}
std::string gbk_to_utf8(const std::string& gbkStr) {
return codepage_to_utf8(gbkStr, CP_ACP);
}
bool set_thread_priority(std::thread* thread, int priority, bool realtime) {
HANDLE hThread = thread_to_win32_handle(thread);
if (!hThread) {
return false;
}
return set_thread_priority(reinterpret_cast<void*>(hThread), priority, realtime);
}
bool set_thread_priority(void* thread_ptr, int priority, bool realtime) {
HANDLE hThread = static_cast<HANDLE>(thread_ptr);
if (!hThread) {
return false;
}
if (realtime) {
if (!SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS)) {
return false;
}
return SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL) != 0;
}
if (priority < THREAD_PRIORITY_IDLE) {
priority = THREAD_PRIORITY_IDLE;
}
if (priority > THREAD_PRIORITY_TIME_CRITICAL) {
priority = THREAD_PRIORITY_TIME_CRITICAL;
}
return SetThreadPriority(hThread, priority) != 0;
}
bool set_thread_affinity(void* thread_ptr, unsigned cpu_index) {
HANDLE hThread = static_cast<HANDLE>(thread_ptr);
if (!hThread) {
return false;
}
const unsigned max_bits = sizeof(DWORD_PTR) * 8;
if (cpu_index >= max_bits) {
return false;
}
DWORD_PTR mask = (static_cast<DWORD_PTR>(1) << cpu_index);
return SetThreadAffinityMask(hThread, mask) != 0;
}
bool set_thread_affinity(std::thread* thread, unsigned cpu_index) {
HANDLE hThread = thread_to_win32_handle(thread);
if (!hThread) {
return false;
}
return set_thread_affinity(reinterpret_cast<void*>(hThread), cpu_index);
}
size_t get_system_memory() {
MEMORYSTATUSEX memStatus{};
memStatus.dwLength = sizeof(MEMORYSTATUSEX);
if (GlobalMemoryStatusEx(&memStatus)) {
return static_cast<size_t>(memStatus.ullTotalPhys);
}
return 0;
}
size_t get_process_memory() {
PROCESS_MEMORY_COUNTERS memCounter{};
if (GetProcessMemoryInfo(GetCurrentProcess(), &memCounter, sizeof(memCounter))) {
return static_cast<size_t>(memCounter.WorkingSetSize);
}
return 0;
}
std::string get_memory_info() {
MEMORYSTATUSEX status{};
status.dwLength = sizeof(status);
if (!GlobalMemoryStatusEx(&status)) {
return "Failed to get memory status";
}
auto ret = JSON::object();
ret.append({"Total physical memory (MB)", status.ullTotalPhys / (1024 * 1024)});
ret.append({"Available physical memory (MB)", status.ullAvailPhys / (1024 * 1024)});
ret.append({"Total virtual memory (MB)", status.ullTotalVirtual / (1024 * 1024)});
ret.append({"Available virtual memory (MB)", status.ullAvailVirtual / (1024 * 1024)});
ret.append({"Total page file (MB)", status.ullTotalPageFile / (1024 * 1024)});
ret.append({"Available page file (MB)", status.ullAvailPageFile / (1024 * 1024)});
return ret.to_json_string();
}
int GenerateMiniDump(PEXCEPTION_POINTERS pExceptionPointers) {
using MiniDumpWriteDumpT = BOOL(WINAPI*)(HANDLE,
DWORD,
HANDLE,
MINIDUMP_TYPE,
PMINIDUMP_EXCEPTION_INFORMATION,
PMINIDUMP_USER_STREAM_INFORMATION,
PMINIDUMP_CALLBACK_INFORMATION);
HMODULE hDbgHelp = LoadLibraryW(L"DbgHelp.dll");
if (!hDbgHelp) {
return EXCEPTION_CONTINUE_EXECUTION;
}
auto pfnMiniDumpWriteDump = reinterpret_cast<MiniDumpWriteDumpT>(GetProcAddress(hDbgHelp, "MiniDumpWriteDump"));
if (!pfnMiniDumpWriteDump) {
FreeLibrary(hDbgHelp);
return EXCEPTION_CONTINUE_EXECUTION;
}
wchar_t fileName[MAX_PATH]{};
SYSTEMTIME localTime{};
GetLocalTime(&localTime);
std::swprintf(fileName, MAX_PATH, L"DumpDemo_v1.0-%04u%02u%02u-%02u%02u%02u.dmp",
localTime.wYear,
localTime.wMonth,
localTime.wDay,
localTime.wHour,
localTime.wMinute,
localTime.wSecond);
HANDLE hDumpFile = CreateFileW(fileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE | FILE_SHARE_READ,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (hDumpFile == INVALID_HANDLE_VALUE) {
FreeLibrary(hDbgHelp);
return EXCEPTION_CONTINUE_EXECUTION;
}
MINIDUMP_EXCEPTION_INFORMATION expParam{};
expParam.ThreadId = GetCurrentThreadId();
expParam.ExceptionPointers = pExceptionPointers;
expParam.ClientPointers = FALSE;
pfnMiniDumpWriteDump(GetCurrentProcess(),
GetCurrentProcessId(),
hDumpFile,
MiniDumpWithDataSegs,
pExceptionPointers ? &expParam : nullptr,
nullptr,
nullptr);
CloseHandle(hDumpFile);
FreeLibrary(hDbgHelp);
return EXCEPTION_EXECUTE_HANDLER;
}
LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS lpExceptionInfo) {
if (IsDebuggerPresent()) {
return EXCEPTION_CONTINUE_SEARCH;
}
return GenerateMiniDump(lpExceptionInfo);
}
} // namespace Psc
#endif // defined(_WIN32)