diff --git a/CMakeLists.txt b/CMakeLists.txt index 53d4833..de785e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,7 +240,7 @@ add_library(lvt_avalonia_plugin SHARED ) target_compile_definitions(lvt_avalonia_plugin PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX) target_include_directories(lvt_avalonia_plugin PRIVATE src) -target_link_libraries(lvt_avalonia_plugin PRIVATE ole32 version) +target_link_libraries(lvt_avalonia_plugin PRIVATE WIL::WIL ole32 version) set_target_properties(lvt_avalonia_plugin PROPERTIES OUTPUT_NAME "lvt_avalonia_plugin" RUNTIME_OUTPUT_DIRECTORY $/plugins @@ -253,6 +253,7 @@ add_library(lvt_avalonia_tap SHARED src/plugin_avalonia/avalonia_tap_native.def ) target_compile_definitions(lvt_avalonia_tap PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX) +target_link_libraries(lvt_avalonia_tap PRIVATE WIL::WIL) set_property(TARGET lvt_avalonia_tap PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") set_target_properties(lvt_avalonia_tap PROPERTIES OUTPUT_NAME "lvt_avalonia_tap_${TAP_ARCH_SUFFIX}" @@ -289,7 +290,7 @@ add_library(lvt_chromium_plugin SHARED ) target_compile_definitions(lvt_chromium_plugin PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX) target_include_directories(lvt_chromium_plugin PRIVATE src) -target_link_libraries(lvt_chromium_plugin PRIVATE nlohmann_json::nlohmann_json ole32 version) +target_link_libraries(lvt_chromium_plugin PRIVATE WIL::WIL nlohmann_json::nlohmann_json ole32 version) set_target_properties(lvt_chromium_plugin PROPERTIES OUTPUT_NAME "lvt_chromium_plugin" RUNTIME_OUTPUT_DIRECTORY $/plugins @@ -300,7 +301,7 @@ add_executable(lvt_chromium_host src/plugin_chromium/chromium_host.cpp ) target_compile_definitions(lvt_chromium_host PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX) -target_link_libraries(lvt_chromium_host PRIVATE advapi32) +target_link_libraries(lvt_chromium_host PRIVATE WIL::WIL advapi32) set_target_properties(lvt_chromium_host PROPERTIES RUNTIME_OUTPUT_DIRECTORY $/plugins/chromium ) diff --git a/src/plugin_avalonia/avalonia_tap_native.cpp b/src/plugin_avalonia/avalonia_tap_native.cpp index 7736f38..3ca7e13 100644 --- a/src/plugin_avalonia/avalonia_tap_native.cpp +++ b/src/plugin_avalonia/avalonia_tap_native.cpp @@ -3,6 +3,7 @@ // Avalonia apps are always .NET Core/.NET 5+, so only the hostfxr path is needed. #include +#include #include #include @@ -44,17 +45,16 @@ static std::wstring ReadPipeName() { std::wstring dir = GetDllDirectory(); std::wstring path = dir + L"\\lvt_avalonia_pipe.txt"; - HANDLE hFile = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, - nullptr, OPEN_EXISTING, 0, nullptr); - if (hFile == INVALID_HANDLE_VALUE) { + wil::unique_hfile hFile(CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, 0, nullptr)); + if (!hFile) { LogMsg("Failed to open pipe name file: %lu", GetLastError()); return {}; } char buf[256]{}; DWORD bytesRead = 0; - ReadFile(hFile, buf, sizeof(buf) - 1, &bytesRead, nullptr); - CloseHandle(hFile); + ReadFile(hFile.get(), buf, sizeof(buf) - 1, &bytesRead, nullptr); int wlen = MultiByteToWideChar(CP_UTF8, 0, buf, bytesRead, nullptr, 0); std::wstring result(wlen, L'\0'); @@ -87,18 +87,21 @@ static bool TryNetCore(const std::wstring& assemblyPath, const std::wstring& pip std::wstring dotnetDir = std::wstring(progFiles) + L"\\dotnet\\host\\fxr"; WIN32_FIND_DATAW fd; - HANDLE hFind = FindFirstFileW((dotnetDir + L"\\*").c_str(), &fd); + wil::unique_hfind hFind(FindFirstFileW((dotnetDir + L"\\*").c_str(), &fd)); std::wstring latestFxr; - if (hFind != INVALID_HANDLE_VALUE) { + if (hFind) { do { if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && fd.cFileName[0] != L'.') { latestFxr = dotnetDir + L"\\" + fd.cFileName + L"\\hostfxr.dll"; } - } while (FindNextFileW(hFind, &fd)); - FindClose(hFind); + } while (FindNextFileW(hFind.get(), &fd)); } if (!latestFxr.empty()) { + // Intentionally kept resident (raw, not scope-owned): hostfxr pins the + // hosted CoreCLR resolver for the lifetime of this injected process. + // Freeing it on scope exit would diverge from the original behavior and + // risk unloading a host library out from under the running runtime. hHostfxr = LoadLibraryW(latestFxr.c_str()); LogMsg("Loaded hostfxr from: %ls -> %p", latestFxr.c_str(), hHostfxr); } @@ -218,8 +221,7 @@ BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) { DisableThreadLibraryCalls(hMod); LogMsg("DllMain: DLL_PROCESS_ATTACH"); - HANDLE hThread = CreateThread(nullptr, 0, WorkerThread, reinterpret_cast(hMod), 0, nullptr); - if (hThread) CloseHandle(hThread); + wil::unique_handle hThread(CreateThread(nullptr, 0, WorkerThread, reinterpret_cast(hMod), 0, nullptr)); } return TRUE; } diff --git a/src/plugin_avalonia/lvt_avalonia_plugin.cpp b/src/plugin_avalonia/lvt_avalonia_plugin.cpp index 693a887..583f1ab 100644 --- a/src/plugin_avalonia/lvt_avalonia_plugin.cpp +++ b/src/plugin_avalonia/lvt_avalonia_plugin.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -125,48 +126,43 @@ static bool write_pipe_name_file(const std::wstring& dir, const std::wstring& pi } static bool inject_dll(DWORD pid, const std::wstring& dllPath) { - HANDLE proc = OpenProcess( + wil::unique_handle proc(OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION, - FALSE, pid); + FALSE, pid)); if (!proc) { DebugLog("failed to open target process %lu (error %lu)", pid, GetLastError()); return false; } size_t pathBytes = (dllPath.size() + 1) * sizeof(wchar_t); - void* remoteMem = VirtualAllocEx(proc, nullptr, pathBytes, MEM_COMMIT, PAGE_READWRITE); + void* remoteMem = VirtualAllocEx(proc.get(), nullptr, pathBytes, MEM_COMMIT, PAGE_READWRITE); if (!remoteMem) { DebugLog("VirtualAllocEx failed (error %lu)", GetLastError()); - CloseHandle(proc); return false; } - if (!WriteProcessMemory(proc, remoteMem, dllPath.c_str(), pathBytes, nullptr)) { + if (!WriteProcessMemory(proc.get(), remoteMem, dllPath.c_str(), pathBytes, nullptr)) { DebugLog("WriteProcessMemory failed (error %lu)", GetLastError()); - VirtualFreeEx(proc, remoteMem, 0, MEM_RELEASE); - CloseHandle(proc); + VirtualFreeEx(proc.get(), remoteMem, 0, MEM_RELEASE); return false; } auto loadLibAddr = reinterpret_cast( GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW")); - HANDLE thread = CreateRemoteThread( - proc, nullptr, 0, loadLibAddr, remoteMem, 0, nullptr); + wil::unique_handle thread(CreateRemoteThread( + proc.get(), nullptr, 0, loadLibAddr, remoteMem, 0, nullptr)); if (!thread) { DebugLog("CreateRemoteThread failed (error %lu)", GetLastError()); - VirtualFreeEx(proc, remoteMem, 0, MEM_RELEASE); - CloseHandle(proc); + VirtualFreeEx(proc.get(), remoteMem, 0, MEM_RELEASE); return false; } - WaitForSingleObject(thread, 5000); + WaitForSingleObject(thread.get(), 5000); DWORD exitCode = 0; - GetExitCodeThread(thread, &exitCode); - CloseHandle(thread); - VirtualFreeEx(proc, remoteMem, 0, MEM_RELEASE); - CloseHandle(proc); + GetExitCodeThread(thread.get(), &exitCode); + VirtualFreeEx(proc.get(), remoteMem, 0, MEM_RELEASE); if (exitCode == 0) { DebugLog("LoadLibraryW failed in target process"); @@ -195,15 +191,14 @@ __declspec(dllexport) LvtPluginInfo* lvt_plugin_info(void) { __declspec(dllexport) int lvt_detect_framework(DWORD pid, HWND /*hwnd*/, LvtFrameworkDetection* out) { if (!out) return 0; - HANDLE proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + wil::unique_handle proc(OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); if (!proc) return 0; - auto avaloniaPath = get_module_path(proc, L"Avalonia.Base.dll"); + auto avaloniaPath = get_module_path(proc.get(), L"Avalonia.Base.dll"); if (avaloniaPath.empty()) { // Try alternative: some older versions may just be "Avalonia.dll" - avaloniaPath = get_module_path(proc, L"Avalonia.dll"); + avaloniaPath = get_module_path(proc.get(), L"Avalonia.dll"); } - CloseHandle(proc); if (avaloniaPath.empty()) return 0; @@ -258,32 +253,33 @@ __declspec(dllexport) int lvt_enrich_tree(HWND hwnd, DWORD pid, SECURITY_ATTRIBUTES sa = {}; sa.nLength = sizeof(sa); sa.bInheritHandle = FALSE; + PSECURITY_DESCRIPTOR rawSd = nullptr; ConvertStringSecurityDescriptorToSecurityDescriptorW( - L"D:(A;;GRGW;;;WD)(A;;GRGW;;;AC)", SDDL_REVISION_1, &sa.lpSecurityDescriptor, nullptr); + L"D:(A;;GRGW;;;WD)(A;;GRGW;;;AC)", SDDL_REVISION_1, &rawSd, nullptr); + wil::unique_hlocal securityDescriptor(rawSd); + sa.lpSecurityDescriptor = securityDescriptor.get(); - HANDLE pipe = CreateNamedPipeW( + wil::unique_hfile pipe(CreateNamedPipeW( pipeName.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, - 1, 0, 1024 * 1024, 10000, &sa); - LocalFree(sa.lpSecurityDescriptor); + 1, 0, 1024 * 1024, 10000, &sa)); - if (pipe == INVALID_HANDLE_VALUE) { + if (!pipe) { DebugLog("failed to create named pipe (error %lu)", GetLastError()); return 0; } // Start overlapped connect before injection OVERLAPPED ov = {}; - ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - ConnectNamedPipe(pipe, &ov); + wil::unique_event connectEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + ov.hEvent = connectEvent.get(); + ConnectNamedPipe(pipe.get(), &ov); DWORD connectErr = GetLastError(); // Inject TAP DLL if (!inject_dll(pid, tapDll)) { - CancelIo(pipe); - CloseHandle(ov.hEvent); - CloseHandle(pipe); + CancelIo(pipe.get()); return 0; } @@ -293,36 +289,32 @@ __declspec(dllexport) int lvt_enrich_tree(HWND hwnd, DWORD pid, if (connectErr == ERROR_IO_PENDING) { if (WaitForSingleObject(ov.hEvent, 15000) != WAIT_OBJECT_0) { DebugLog("TAP DLL did not connect (timeout)"); - CancelIo(pipe); - CloseHandle(ov.hEvent); - CloseHandle(pipe); + CancelIo(pipe.get()); return 0; } } else if (connectErr != ERROR_PIPE_CONNECTED) { DebugLog("ConnectNamedPipe failed (error %lu)", connectErr); - CloseHandle(ov.hEvent); - CloseHandle(pipe); return 0; } - CloseHandle(ov.hEvent); // Read all data std::string data; char buf[4096]; DWORD bytesRead = 0; OVERLAPPED readOv = {}; - readOv.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + wil::unique_event readEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + readOv.hEvent = readEvent.get(); for (;;) { ResetEvent(readOv.hEvent); - BOOL ok = ReadFile(pipe, buf, sizeof(buf), &bytesRead, &readOv); + BOOL ok = ReadFile(pipe.get(), buf, sizeof(buf), &bytesRead, &readOv); if (!ok) { DWORD err = GetLastError(); if (err == ERROR_IO_PENDING) { if (WaitForSingleObject(readOv.hEvent, 15000) != WAIT_OBJECT_0) { - CancelIo(pipe); + CancelIo(pipe.get()); break; } - if (!GetOverlappedResult(pipe, &readOv, &bytesRead, FALSE) || bytesRead == 0) + if (!GetOverlappedResult(pipe.get(), &readOv, &bytesRead, FALSE) || bytesRead == 0) break; } else { break; @@ -332,8 +324,6 @@ __declspec(dllexport) int lvt_enrich_tree(HWND hwnd, DWORD pid, } data.append(buf, bytesRead); } - CloseHandle(readOv.hEvent); - CloseHandle(pipe); // Clean up sidecar file DeleteFileW((tapDir + L"\\lvt_avalonia_pipe.txt").c_str()); diff --git a/src/plugin_chromium/chromium_host.cpp b/src/plugin_chromium/chromium_host.cpp index 1b568a1..c6b88c0 100644 --- a/src/plugin_chromium/chromium_host.cpp +++ b/src/plugin_chromium/chromium_host.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -62,15 +63,18 @@ static bool write_native_message(const std::string& msg) { // ---------- Named pipe server ---------- -static HANDLE create_pipe() { +static wil::unique_hfile create_pipe() { SECURITY_ATTRIBUTES sa = {}; sa.nLength = sizeof(sa); sa.bInheritHandle = FALSE; // Allow all users to connect (lvt.exe may run as different user context) + PSECURITY_DESCRIPTOR rawSd = nullptr; ConvertStringSecurityDescriptorToSecurityDescriptorA( - "D:(A;;GRGW;;;WD)(A;;GRGW;;;AC)", SDDL_REVISION_1, &sa.lpSecurityDescriptor, nullptr); + "D:(A;;GRGW;;;WD)(A;;GRGW;;;AC)", SDDL_REVISION_1, &rawSd, nullptr); + wil::unique_hlocal securityDescriptor(rawSd); + sa.lpSecurityDescriptor = securityDescriptor.get(); - HANDLE pipe = CreateNamedPipeA( + return wil::unique_hfile(CreateNamedPipeA( PIPE_NAME, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, @@ -78,10 +82,7 @@ static HANDLE create_pipe() { 64 * 1024, // out buffer 4 * 1024 * 1024, // in buffer (DOM trees can be large) 0, - &sa); - - LocalFree(sa.lpSecurityDescriptor); - return pipe; + &sa)); } // Read a length-prefixed message from the named pipe @@ -90,21 +91,19 @@ static bool read_pipe_message(HANDLE pipe, std::string& out) { uint32_t len = 0; DWORD bytesRead = 0; OVERLAPPED ov = {}; - ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + wil::unique_event event(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + ov.hEvent = event.get(); BOOL ok = ReadFile(pipe, &len, 4, &bytesRead, &ov); if (!ok && GetLastError() == ERROR_IO_PENDING) { if (WaitForSingleObject(ov.hEvent, 30000) != WAIT_OBJECT_0) { CancelIo(pipe); - CloseHandle(ov.hEvent); return false; } if (!GetOverlappedResult(pipe, &ov, &bytesRead, FALSE)) { - CloseHandle(ov.hEvent); return false; } } - CloseHandle(ov.hEvent); if (bytesRead != 4 || len == 0 || len > 4 * 1024 * 1024) return false; @@ -112,20 +111,18 @@ static bool read_pipe_message(HANDLE pipe, std::string& out) { DWORD totalRead = 0; while (totalRead < len) { ov = {}; - ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + wil::unique_event loopEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + ov.hEvent = loopEvent.get(); ok = ReadFile(pipe, out.data() + totalRead, len - totalRead, &bytesRead, &ov); if (!ok && GetLastError() == ERROR_IO_PENDING) { if (WaitForSingleObject(ov.hEvent, 30000) != WAIT_OBJECT_0) { CancelIo(pipe); - CloseHandle(ov.hEvent); return false; } if (!GetOverlappedResult(pipe, &ov, &bytesRead, FALSE)) { - CloseHandle(ov.hEvent); return false; } } - CloseHandle(ov.hEvent); if (bytesRead == 0) return false; totalRead += bytesRead; } @@ -137,18 +134,18 @@ static bool write_pipe_message(HANDLE pipe, const std::string& msg) { uint32_t len = static_cast(msg.size()); DWORD bytesWritten = 0; OVERLAPPED ov = {}; - ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + wil::unique_event event(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + ov.hEvent = event.get(); // Write length prefix BOOL ok = WriteFile(pipe, &len, 4, &bytesWritten, &ov); if (!ok && GetLastError() == ERROR_IO_PENDING) { WaitForSingleObject(ov.hEvent, 5000); if (!GetOverlappedResult(pipe, &ov, &bytesWritten, FALSE)) { - CloseHandle(ov.hEvent); return false; } } - if (bytesWritten != 4) { CloseHandle(ov.hEvent); return false; } + if (bytesWritten != 4) { return false; } // Write message body ResetEvent(ov.hEvent); @@ -156,12 +153,10 @@ static bool write_pipe_message(HANDLE pipe, const std::string& msg) { if (!ok && GetLastError() == ERROR_IO_PENDING) { WaitForSingleObject(ov.hEvent, 30000); if (!GetOverlappedResult(pipe, &ov, &bytesWritten, FALSE)) { - CloseHandle(ov.hEvent); return false; } } - CloseHandle(ov.hEvent); return bytesWritten == len; } @@ -226,19 +221,18 @@ static bool register_host() { bool ok = true; for (auto regPath : regPaths) { - HKEY key; + wil::unique_hkey key; LSTATUS status = RegCreateKeyExW(HKEY_CURRENT_USER, regPath, 0, nullptr, - 0, KEY_SET_VALUE, nullptr, &key, nullptr); + 0, KEY_SET_VALUE, nullptr, key.put(), nullptr); if (status != ERROR_SUCCESS) { fprintf(stderr, "Failed to create registry key: %ls (error %ld)\n", regPath, status); ok = false; continue; } - status = RegSetValueExW(key, nullptr, 0, REG_SZ, + status = RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast(manifestPathW.c_str()), static_cast((manifestPathW.size() + 1) * sizeof(wchar_t))); - RegCloseKey(key); if (status != ERROR_SUCCESS) { fprintf(stderr, "Failed to set registry value (error %ld)\n", status); @@ -262,8 +256,8 @@ static void run_relay() { _setmode(_fileno(stdin), _O_BINARY); _setmode(_fileno(stdout), _O_BINARY); - HANDLE pipe = create_pipe(); - if (pipe == INVALID_HANDLE_VALUE) { + wil::unique_hfile pipe = create_pipe(); + if (!pipe) { // Pipe may already exist from another host instance — not fatal, // but we can't relay without it. write_native_message("{\"type\":\"error\",\"message\":\"Failed to create named pipe\"}"); @@ -278,8 +272,9 @@ static void run_relay() { while (g_running) { // Wait for lvt to connect OVERLAPPED ov = {}; - ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - ConnectNamedPipe(pipe, &ov); + wil::unique_event event(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + ov.hEvent = event.get(); + ConnectNamedPipe(pipe.get(), &ov); DWORD err = GetLastError(); if (err == ERROR_IO_PENDING) { @@ -289,28 +284,25 @@ static void run_relay() { break; } if (!g_running) { - CancelIo(pipe); - CloseHandle(ov.hEvent); + CancelIo(pipe.get()); break; } } else if (err != ERROR_PIPE_CONNECTED && err != 0) { - CloseHandle(ov.hEvent); Sleep(1000); continue; } - CloseHandle(ov.hEvent); // lvt is connected — relay messages while (g_running) { std::string msg; - if (!read_pipe_message(pipe, msg)) + if (!read_pipe_message(pipe.get(), msg)) break; // Forward lvt's request to the extension if (!write_native_message(msg)) break; } - DisconnectNamedPipe(pipe); + DisconnectNamedPipe(pipe.get()); } }); @@ -322,14 +314,12 @@ static void run_relay() { break; } // Forward extension's response to lvt via pipe - write_pipe_message(pipe, msg); + write_pipe_message(pipe.get(), msg); } g_running = false; if (pipeReader.joinable()) pipeReader.join(); - - CloseHandle(pipe); } // ---------- Entry point ---------- diff --git a/src/plugin_chromium/lvt_chromium_plugin.cpp b/src/plugin_chromium/lvt_chromium_plugin.cpp index d8034db..c191331 100644 --- a/src/plugin_chromium/lvt_chromium_plugin.cpp +++ b/src/plugin_chromium/lvt_chromium_plugin.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -110,29 +111,27 @@ static bool write_pipe_message(HANDLE pipe, const std::string& msg) { uint32_t len = static_cast(msg.size()); DWORD bytesWritten = 0; OVERLAPPED ov = {}; - ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + wil::unique_event event(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + ov.hEvent = event.get(); BOOL ok = WriteFile(pipe, &len, 4, &bytesWritten, &ov); if (!ok && GetLastError() == ERROR_IO_PENDING) { WaitForSingleObject(ov.hEvent, 5000); if (!GetOverlappedResult(pipe, &ov, &bytesWritten, FALSE)) { - CloseHandle(ov.hEvent); return false; } } - if (bytesWritten != 4) { CloseHandle(ov.hEvent); return false; } + if (bytesWritten != 4) { return false; } ResetEvent(ov.hEvent); ok = WriteFile(pipe, msg.data(), len, &bytesWritten, &ov); if (!ok && GetLastError() == ERROR_IO_PENDING) { WaitForSingleObject(ov.hEvent, 30000); if (!GetOverlappedResult(pipe, &ov, &bytesWritten, FALSE)) { - CloseHandle(ov.hEvent); return false; } } - CloseHandle(ov.hEvent); return bytesWritten == len; } @@ -141,21 +140,19 @@ static bool read_pipe_message(HANDLE pipe, std::string& out, DWORD timeoutMs = 3 uint32_t len = 0; DWORD bytesRead = 0; OVERLAPPED ov = {}; - ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + wil::unique_event event(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + ov.hEvent = event.get(); BOOL ok = ReadFile(pipe, &len, 4, &bytesRead, &ov); if (!ok && GetLastError() == ERROR_IO_PENDING) { if (WaitForSingleObject(ov.hEvent, timeoutMs) != WAIT_OBJECT_0) { CancelIo(pipe); - CloseHandle(ov.hEvent); return false; } if (!GetOverlappedResult(pipe, &ov, &bytesRead, FALSE)) { - CloseHandle(ov.hEvent); return false; } } - CloseHandle(ov.hEvent); if (bytesRead != 4 || len == 0 || len > 64 * 1024 * 1024) // 64MB max for large DOMs return false; @@ -164,20 +161,18 @@ static bool read_pipe_message(HANDLE pipe, std::string& out, DWORD timeoutMs = 3 DWORD totalRead = 0; while (totalRead < len) { ov = {}; - ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + wil::unique_event loopEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + ov.hEvent = loopEvent.get(); ok = ReadFile(pipe, out.data() + totalRead, len - totalRead, &bytesRead, &ov); if (!ok && GetLastError() == ERROR_IO_PENDING) { if (WaitForSingleObject(ov.hEvent, timeoutMs) != WAIT_OBJECT_0) { CancelIo(pipe); - CloseHandle(ov.hEvent); return false; } if (!GetOverlappedResult(pipe, &ov, &bytesRead, FALSE)) { - CloseHandle(ov.hEvent); return false; } } - CloseHandle(ov.hEvent); if (bytesRead == 0) return false; totalRead += bytesRead; } @@ -202,21 +197,19 @@ __declspec(dllexport) LvtPluginInfo* lvt_plugin_info(void) { __declspec(dllexport) int lvt_detect_framework(DWORD pid, HWND /*hwnd*/, LvtFrameworkDetection* out) { if (!out) return 0; - HANDLE proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + wil::unique_handle proc(OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); if (!proc) return 0; // Check for Chrome or Edge - bool isChrome = has_module(proc, L"chrome.dll"); - bool isEdge = has_module(proc, L"msedge.dll"); + bool isChrome = has_module(proc.get(), L"chrome.dll"); + bool isEdge = has_module(proc.get(), L"msedge.dll"); if (!isChrome && !isEdge) { - CloseHandle(proc); return 0; } const wchar_t* versionModule = isEdge ? L"msedge.dll" : L"chrome.dll"; - auto version = get_module_version(proc, versionModule); - CloseHandle(proc); + auto version = get_module_version(proc.get(), versionModule); if (!version.empty()) { // Include browser name in version for display: "145.0.3800.70 (Edge)" @@ -241,16 +234,16 @@ __declspec(dllexport) int lvt_enrich_tree(HWND /*hwnd*/, DWORD /*pid*/, *json_out = nullptr; // Connect to the native messaging host's named pipe - HANDLE pipe = CreateFileA( + wil::unique_hfile pipe(CreateFileA( PIPE_NAME, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, - nullptr); + nullptr)); - if (pipe == INVALID_HANDLE_VALUE) { + if (!pipe) { DebugLog("failed to connect to native messaging host pipe (error %lu). " "Is the LVT Chromium extension installed and active?", GetLastError()); fprintf(stderr, "lvt-chromium: Cannot connect to browser extension.\n" @@ -266,16 +259,14 @@ __declspec(dllexport) int lvt_enrich_tree(HWND /*hwnd*/, DWORD /*pid*/, if (!selector.empty()) { json listRequest = {{"type", "listTabs"}, {"requestId", "1"}}; - if (!write_pipe_message(pipe, listRequest.dump())) { + if (!write_pipe_message(pipe.get(), listRequest.dump())) { DebugLog("failed to send listTabs request"); - CloseHandle(pipe); return 0; } std::string listResponse; - if (!read_pipe_message(pipe, listResponse, 30000)) { + if (!read_pipe_message(pipe.get(), listResponse, 30000)) { DebugLog("failed to read listTabs response"); - CloseHandle(pipe); return 0; } @@ -285,7 +276,6 @@ __declspec(dllexport) int lvt_enrich_tree(HWND /*hwnd*/, DWORD /*pid*/, auto msg = tabsEnvelope.value("message", "unknown error"); DebugLog("extension returned error while listing tabs: %s", msg.c_str()); fprintf(stderr, "lvt-chromium: %s\n", msg.c_str()); - CloseHandle(pipe); return 0; } @@ -294,7 +284,6 @@ __declspec(dllexport) int lvt_enrich_tree(HWND /*hwnd*/, DWORD /*pid*/, if (!selected) { DebugLog("%s", error.c_str()); fprintf(stderr, "lvt-chromium: %s\n", error.c_str()); - CloseHandle(pipe); return 0; } DebugLog("selected tab %d: %s (%s)", @@ -303,16 +292,14 @@ __declspec(dllexport) int lvt_enrich_tree(HWND /*hwnd*/, DWORD /*pid*/, request["tabId"] = selected->tab_id; } catch (const json::parse_error& e) { DebugLog("failed to parse listTabs response JSON: %s", e.what()); - CloseHandle(pipe); return 0; } } // Send getDOM request std::string requestString = request.dump(); - if (!write_pipe_message(pipe, requestString)) { + if (!write_pipe_message(pipe.get(), requestString)) { DebugLog("failed to send getDOM request"); - CloseHandle(pipe); return 0; } @@ -320,14 +307,11 @@ __declspec(dllexport) int lvt_enrich_tree(HWND /*hwnd*/, DWORD /*pid*/, // Read response (may be large — full DOM tree) std::string response; - if (!read_pipe_message(pipe, response, 60000)) { + if (!read_pipe_message(pipe.get(), response, 60000)) { DebugLog("failed to read DOM response (timeout or error)"); - CloseHandle(pipe); return 0; } - CloseHandle(pipe); - DebugLog("received %zu bytes of DOM data", response.size()); if (response.empty()) { diff --git a/src/plugin_loader.cpp b/src/plugin_loader.cpp index 38a49c8..06c26a9 100644 --- a/src/plugin_loader.cpp +++ b/src/plugin_loader.cpp @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include #pragma comment(lib, "userenv.lib") @@ -30,14 +32,14 @@ void load_plugins() { std::wstring pattern = dir + L"\\*.dll"; WIN32_FIND_DATAW fd; - HANDLE hFind = FindFirstFileW(pattern.c_str(), &fd); - if (hFind == INVALID_HANDLE_VALUE) return; + wil::unique_hfind hFind(FindFirstFileW(pattern.c_str(), &fd)); + if (!hFind) return; do { if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; std::wstring fullPath = dir + L"\\" + fd.cFileName; - HMODULE mod = LoadLibraryW(fullPath.c_str()); + wil::unique_hmodule mod(LoadLibraryW(fullPath.c_str())); if (!mod) { if (g_debug) fprintf(stderr, "lvt: failed to load plugin %ls (error %lu)\n", @@ -46,12 +48,11 @@ void load_plugins() { } auto infoFn = reinterpret_cast( - GetProcAddress(mod, LVT_PLUGIN_INFO_FUNC)); + GetProcAddress(mod.get(), LVT_PLUGIN_INFO_FUNC)); if (!infoFn) { if (g_debug) fprintf(stderr, "lvt: %ls has no %s export, skipping\n", fd.cFileName, LVT_PLUGIN_INFO_FUNC); - FreeLibrary(mod); continue; } @@ -61,35 +62,29 @@ void load_plugins() { if (g_debug) fprintf(stderr, "lvt: %ls has incompatible plugin API version\n", fd.cFileName); - FreeLibrary(mod); continue; } LoadedPlugin lp{}; - lp.module = mod; + lp.module = std::move(mod); lp.info = info; lp.detect = reinterpret_cast( - GetProcAddress(mod, LVT_PLUGIN_DETECT_FUNC)); + GetProcAddress(lp.module.get(), LVT_PLUGIN_DETECT_FUNC)); lp.enrich = reinterpret_cast( - GetProcAddress(mod, LVT_PLUGIN_ENRICH_FUNC)); + GetProcAddress(lp.module.get(), LVT_PLUGIN_ENRICH_FUNC)); lp.free_fn = reinterpret_cast( - GetProcAddress(mod, LVT_PLUGIN_FREE_FUNC)); + GetProcAddress(lp.module.get(), LVT_PLUGIN_FREE_FUNC)); if (g_debug) fprintf(stderr, "lvt: loaded plugin '%s' (%s)\n", info->name ? info->name : "?", info->description ? info->description : ""); - s_plugins.push_back(lp); - } while (FindNextFileW(hFind, &fd)); - - FindClose(hFind); + s_plugins.push_back(std::move(lp)); + } while (FindNextFileW(hFind.get(), &fd)); } void unload_plugins() { - for (auto& p : s_plugins) { - FreeLibrary(p.module); - } s_plugins.clear(); } diff --git a/src/plugin_loader.h b/src/plugin_loader.h index 8913292..25b66c4 100644 --- a/src/plugin_loader.h +++ b/src/plugin_loader.h @@ -4,11 +4,12 @@ #include #include #include +#include namespace lvt { struct LoadedPlugin { - HMODULE module; + wil::unique_hmodule module; LvtPluginInfo* info; LvtDetectFrameworkFn detect; LvtEnrichTreeFn enrich; diff --git a/src/providers/winforms_inject.cpp b/src/providers/winforms_inject.cpp index 7ee5602..fb21ed9 100644 --- a/src/providers/winforms_inject.cpp +++ b/src/providers/winforms_inject.cpp @@ -228,66 +228,63 @@ bool inject_and_collect_winforms_tree(Element& root, HWND /*hwnd*/, DWORD pid) { SECURITY_ATTRIBUTES sa = {}; sa.nLength = sizeof(sa); sa.bInheritHandle = FALSE; + PSECURITY_DESCRIPTOR rawSd = nullptr; ConvertStringSecurityDescriptorToSecurityDescriptorW( - L"D:(A;;GRGW;;;WD)(A;;GRGW;;;AC)", SDDL_REVISION_1, &sa.lpSecurityDescriptor, nullptr); + L"D:(A;;GRGW;;;WD)(A;;GRGW;;;AC)", SDDL_REVISION_1, &rawSd, nullptr); + wil::unique_hlocal securityDescriptor(rawSd); + sa.lpSecurityDescriptor = securityDescriptor.get(); - HANDLE pipe = CreateNamedPipeW( + wil::unique_hfile pipe(CreateNamedPipeW( pipeName.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, - 1, 0, 1024 * 1024, 10000, &sa); - LocalFree(sa.lpSecurityDescriptor); + 1, 0, 1024 * 1024, 10000, &sa)); - if (pipe == INVALID_HANDLE_VALUE) { + if (!pipe) { DeleteFileW((exeDir + L"\\lvt_winforms_pipe.txt").c_str()); return false; } OVERLAPPED ov = {}; - ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - ConnectNamedPipe(pipe, &ov); + wil::unique_event connectEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + ov.hEvent = connectEvent.get(); + ConnectNamedPipe(pipe.get(), &ov); DWORD connectErr = GetLastError(); if (!inject_dll(pid, tapDll)) { - CancelIo(pipe); - CloseHandle(ov.hEvent); - CloseHandle(pipe); + CancelIo(pipe.get()); DeleteFileW((exeDir + L"\\lvt_winforms_pipe.txt").c_str()); return false; } if (connectErr == ERROR_IO_PENDING) { if (WaitForSingleObject(ov.hEvent, 15000) != WAIT_OBJECT_0) { - CancelIo(pipe); - CloseHandle(ov.hEvent); - CloseHandle(pipe); + CancelIo(pipe.get()); DeleteFileW((exeDir + L"\\lvt_winforms_pipe.txt").c_str()); return false; } } else if (connectErr != ERROR_PIPE_CONNECTED) { - CloseHandle(ov.hEvent); - CloseHandle(pipe); DeleteFileW((exeDir + L"\\lvt_winforms_pipe.txt").c_str()); return false; } - CloseHandle(ov.hEvent); std::string data; char buf[4096]; DWORD bytesRead = 0; OVERLAPPED readOv = {}; - readOv.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + wil::unique_event readEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + readOv.hEvent = readEvent.get(); for (;;) { ResetEvent(readOv.hEvent); - BOOL ok = ReadFile(pipe, buf, sizeof(buf), &bytesRead, &readOv); + BOOL ok = ReadFile(pipe.get(), buf, sizeof(buf), &bytesRead, &readOv); if (!ok) { DWORD err = GetLastError(); if (err == ERROR_IO_PENDING) { if (WaitForSingleObject(readOv.hEvent, 15000) != WAIT_OBJECT_0) { - CancelIo(pipe); + CancelIo(pipe.get()); break; } - if (!GetOverlappedResult(pipe, &readOv, &bytesRead, FALSE) || bytesRead == 0) + if (!GetOverlappedResult(pipe.get(), &readOv, &bytesRead, FALSE) || bytesRead == 0) break; } else { break; @@ -297,8 +294,6 @@ bool inject_and_collect_winforms_tree(Element& root, HWND /*hwnd*/, DWORD pid) { } data.append(buf, bytesRead); } - CloseHandle(readOv.hEvent); - CloseHandle(pipe); DeleteFileW((exeDir + L"\\lvt_winforms_pipe.txt").c_str()); if (data.empty()) diff --git a/src/providers/winui3_provider.cpp b/src/providers/winui3_provider.cpp index b04b333..acf49d2 100644 --- a/src/providers/winui3_provider.cpp +++ b/src/providers/winui3_provider.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace lvt { @@ -26,28 +27,25 @@ static void label_winui3_windows(Element& el) { // Find the FrameworkUdk.dll path loaded in the target process static std::wstring find_framework_udk(DWORD pid) { - HANDLE proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + wil::unique_handle proc(OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); if (!proc) return {}; HMODULE modules[1024]; DWORD needed = 0; - if (!EnumProcessModulesEx(proc, modules, sizeof(modules), &needed, LIST_MODULES_ALL)) { - CloseHandle(proc); + if (!EnumProcessModulesEx(proc.get(), modules, sizeof(modules), &needed, LIST_MODULES_ALL)) { return {}; } for (DWORD i = 0; i < needed / sizeof(HMODULE); i++) { wchar_t name[MAX_PATH]{}; - if (GetModuleBaseNameW(proc, modules[i], name, MAX_PATH)) { + if (GetModuleBaseNameW(proc.get(), modules[i], name, MAX_PATH)) { if (_wcsicmp(name, L"Microsoft.Internal.FrameworkUdk.dll") == 0) { wchar_t fullPath[MAX_PATH]{}; - GetModuleFileNameExW(proc, modules[i], fullPath, MAX_PATH); - CloseHandle(proc); + GetModuleFileNameExW(proc.get(), modules[i], fullPath, MAX_PATH); return fullPath; } } } - CloseHandle(proc); return {}; } diff --git a/src/providers/wpf_inject.cpp b/src/providers/wpf_inject.cpp index 22e67cf..c35ef45 100644 --- a/src/providers/wpf_inject.cpp +++ b/src/providers/wpf_inject.cpp @@ -216,33 +216,34 @@ bool inject_and_collect_wpf_tree(Element& root, HWND /*hwnd*/, DWORD pid) { SECURITY_ATTRIBUTES sa = {}; sa.nLength = sizeof(sa); sa.bInheritHandle = FALSE; + PSECURITY_DESCRIPTOR rawSd = nullptr; ConvertStringSecurityDescriptorToSecurityDescriptorW( - L"D:(A;;GRGW;;;WD)(A;;GRGW;;;AC)", SDDL_REVISION_1, &sa.lpSecurityDescriptor, nullptr); + L"D:(A;;GRGW;;;WD)(A;;GRGW;;;AC)", SDDL_REVISION_1, &rawSd, nullptr); + wil::unique_hlocal securityDescriptor(rawSd); + sa.lpSecurityDescriptor = securityDescriptor.get(); - HANDLE pipe = CreateNamedPipeW( + wil::unique_hfile pipe(CreateNamedPipeW( pipeName.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, - 1, 0, 1024 * 1024, 10000, &sa); - LocalFree(sa.lpSecurityDescriptor); + 1, 0, 1024 * 1024, 10000, &sa)); - if (pipe == INVALID_HANDLE_VALUE) { + if (!pipe) { fprintf(stderr, "lvt: failed to create named pipe (error %lu)\n", GetLastError()); return false; } // Start overlapped connect before injection OVERLAPPED ov = {}; - ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - ConnectNamedPipe(pipe, &ov); + wil::unique_event connectEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + ov.hEvent = connectEvent.get(); + ConnectNamedPipe(pipe.get(), &ov); DWORD connectErr = GetLastError(); // Inject TAP DLL. Since the TAP DLL calls FreeLibraryAndExitThread after // collection, it unloads itself, so each run is a fresh injection. if (!inject_dll(pid, tapDll)) { - CancelIo(pipe); - CloseHandle(ov.hEvent); - CloseHandle(pipe); + CancelIo(pipe.get()); return false; } @@ -253,36 +254,32 @@ bool inject_and_collect_wpf_tree(Element& root, HWND /*hwnd*/, DWORD pid) { if (connectErr == ERROR_IO_PENDING) { if (WaitForSingleObject(ov.hEvent, 15000) != WAIT_OBJECT_0) { fprintf(stderr, "lvt: WPF TAP DLL did not connect (timeout)\n"); - CancelIo(pipe); - CloseHandle(ov.hEvent); - CloseHandle(pipe); + CancelIo(pipe.get()); return false; } } else if (connectErr != ERROR_PIPE_CONNECTED) { fprintf(stderr, "lvt: WPF ConnectNamedPipe failed (error %lu)\n", connectErr); - CloseHandle(ov.hEvent); - CloseHandle(pipe); return false; } - CloseHandle(ov.hEvent); // Read all data std::string data; char buf[4096]; DWORD bytesRead = 0; OVERLAPPED readOv = {}; - readOv.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + wil::unique_event readEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + readOv.hEvent = readEvent.get(); for (;;) { ResetEvent(readOv.hEvent); - BOOL ok = ReadFile(pipe, buf, sizeof(buf), &bytesRead, &readOv); + BOOL ok = ReadFile(pipe.get(), buf, sizeof(buf), &bytesRead, &readOv); if (!ok) { DWORD err = GetLastError(); if (err == ERROR_IO_PENDING) { if (WaitForSingleObject(readOv.hEvent, 15000) != WAIT_OBJECT_0) { - CancelIo(pipe); + CancelIo(pipe.get()); break; } - if (!GetOverlappedResult(pipe, &readOv, &bytesRead, FALSE) || bytesRead == 0) + if (!GetOverlappedResult(pipe.get(), &readOv, &bytesRead, FALSE) || bytesRead == 0) break; } else { break; @@ -292,8 +289,6 @@ bool inject_and_collect_wpf_tree(Element& root, HWND /*hwnd*/, DWORD pid) { } data.append(buf, bytesRead); } - CloseHandle(readOv.hEvent); - CloseHandle(pipe); // Clean up sidecar file DeleteFileW((exeDir + L"\\lvt_wpf_pipe.txt").c_str()); diff --git a/src/providers/xaml_diag_common.cpp b/src/providers/xaml_diag_common.cpp index f3fd20f..5a8b6bd 100644 --- a/src/providers/xaml_diag_common.cpp +++ b/src/providers/xaml_diag_common.cpp @@ -92,12 +92,13 @@ static std::wstring stage_tap_dll_for_appcontainer(const std::wstring& srcDll) { } // Grant ALL_APPLICATION_PACKAGES read+execute access to the directory and DLL - PSECURITY_DESCRIPTOR sd = nullptr; + PSECURITY_DESCRIPTOR rawSd = nullptr; if (ConvertStringSecurityDescriptorToSecurityDescriptorW( - L"D:(A;;GRGX;;;AC)(A;;GRGX;;;WD)", SDDL_REVISION_1, &sd, nullptr)) { + L"D:(A;;GRGX;;;AC)(A;;GRGX;;;WD)", SDDL_REVISION_1, &rawSd, nullptr)) { + wil::unique_hlocal sd(rawSd); PACL dacl = nullptr; BOOL daclPresent = FALSE, daclDefaulted = FALSE; - if (GetSecurityDescriptorDacl(sd, &daclPresent, &dacl, &daclDefaulted) && daclPresent) { + if (GetSecurityDescriptorDacl(sd.get(), &daclPresent, &dacl, &daclDefaulted) && daclPresent) { SetNamedSecurityInfoW(const_cast(destDir.c_str()), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, dacl, nullptr); @@ -105,7 +106,6 @@ static std::wstring stage_tap_dll_for_appcontainer(const std::wstring& srcDll) { SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, dacl, nullptr); } - LocalFree(sd); } if (g_debug) @@ -250,41 +250,40 @@ bool inject_and_collect_xaml_tree( SECURITY_ATTRIBUTES sa = {}; sa.nLength = sizeof(sa); sa.bInheritHandle = FALSE; + PSECURITY_DESCRIPTOR rawPipeSd = nullptr; ConvertStringSecurityDescriptorToSecurityDescriptorW( - L"D:(A;;GRGW;;;WD)(A;;GRGW;;;AC)", SDDL_REVISION_1, &sa.lpSecurityDescriptor, nullptr); + L"D:(A;;GRGW;;;WD)(A;;GRGW;;;AC)", SDDL_REVISION_1, &rawPipeSd, nullptr); + wil::unique_hlocal pipeSecurityDescriptor(rawPipeSd); + sa.lpSecurityDescriptor = pipeSecurityDescriptor.get(); - HANDLE pipe = CreateNamedPipeW( + wil::unique_hfile pipe(CreateNamedPipeW( pipeName.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, - 1, 0, 1024 * 1024, 10000, &sa); - LocalFree(sa.lpSecurityDescriptor); + 1, 0, 1024 * 1024, 10000, &sa)); - if (pipe == INVALID_HANDLE_VALUE) { + if (!pipe) { fprintf(stderr, "lvt: failed to create named pipe (error %lu)\n", GetLastError()); return false; } // Load InitializeXamlDiagnosticsEx from the specified DLL. // This function runs in OUR process but injects into the target. - HMODULE hXaml = LoadLibraryExW(initDllPath.c_str(), nullptr, - LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + wil::unique_hmodule hXaml(LoadLibraryExW(initDllPath.c_str(), nullptr, + LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)); if (!hXaml) { - hXaml = LoadLibraryW(initDllPath.c_str()); + hXaml.reset(LoadLibraryW(initDllPath.c_str())); } if (!hXaml) { fprintf(stderr, "lvt: failed to load %ls (error %lu)\n", initDllPath.c_str(), GetLastError()); - CloseHandle(pipe); return false; } using FnInit = HRESULT(WINAPI*)(LPCWSTR, DWORD, LPCWSTR, LPCWSTR, CLSID, LPCWSTR); auto pInit = reinterpret_cast( - GetProcAddress(hXaml, "InitializeXamlDiagnosticsEx")); + GetProcAddress(hXaml.get(), "InitializeXamlDiagnosticsEx")); if (!pInit) { fprintf(stderr, "lvt: InitializeXamlDiagnosticsEx not found in %ls\n", initDllPath.c_str()); - FreeLibrary(hXaml); - CloseHandle(pipe); return false; } @@ -294,8 +293,9 @@ bool inject_and_collect_xaml_tree( // synchronously and the TAP DLL can connect+write+close before we'd // otherwise reach ConnectNamedPipe, causing a missed connection. OVERLAPPED ov = {}; - ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - ConnectNamedPipe(pipe, &ov); + wil::unique_event connectEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + ov.hEvent = connectEvent.get(); + ConnectNamedPipe(pipe.get(), &ov); DWORD connectErr = GetLastError(); HRESULT hr = E_FAIL; @@ -318,13 +318,11 @@ bool inject_and_collect_xaml_tree( break; } - FreeLibrary(hXaml); + hXaml.reset(); if (FAILED(hr)) { fprintf(stderr, "lvt: InitializeXamlDiagnosticsEx failed (0x%08lX)\n", hr); - CancelIo(pipe); - CloseHandle(ov.hEvent); - CloseHandle(pipe); + CancelIo(pipe.get()); return false; } @@ -336,36 +334,32 @@ bool inject_and_collect_xaml_tree( DWORD waitResult = WaitForSingleObject(ov.hEvent, 15000); if (waitResult != WAIT_OBJECT_0) { fprintf(stderr, "lvt: TAP DLL did not connect (timeout)\n"); - CancelIo(pipe); - CloseHandle(ov.hEvent); - CloseHandle(pipe); + CancelIo(pipe.get()); return false; } } else if (connectErr != ERROR_PIPE_CONNECTED) { fprintf(stderr, "lvt: ConnectNamedPipe failed (error %lu)\n", connectErr); - CloseHandle(ov.hEvent); - CloseHandle(pipe); return false; } - CloseHandle(ov.hEvent); // Read all data from pipe (overlapped with timeout) std::string data; char buf[4096]; DWORD bytesRead = 0; OVERLAPPED readOv = {}; - readOv.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + wil::unique_event readEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr)); + readOv.hEvent = readEvent.get(); for (;;) { ResetEvent(readOv.hEvent); - BOOL ok = ReadFile(pipe, buf, sizeof(buf), &bytesRead, &readOv); + BOOL ok = ReadFile(pipe.get(), buf, sizeof(buf), &bytesRead, &readOv); if (!ok) { DWORD err = GetLastError(); if (err == ERROR_IO_PENDING) { if (WaitForSingleObject(readOv.hEvent, 15000) != WAIT_OBJECT_0) { - CancelIo(pipe); + CancelIo(pipe.get()); break; } - if (!GetOverlappedResult(pipe, &readOv, &bytesRead, FALSE) || bytesRead == 0) + if (!GetOverlappedResult(pipe.get(), &readOv, &bytesRead, FALSE) || bytesRead == 0) break; } else { break; @@ -375,8 +369,6 @@ bool inject_and_collect_xaml_tree( } data.append(buf, bytesRead); } - CloseHandle(readOv.hEvent); - CloseHandle(pipe); if (g_debug) fprintf(stderr, "lvt: received %zu bytes of XAML tree data\n", data.size()); diff --git a/src/screenshot.cpp b/src/screenshot.cpp index be0afc0..151207f 100644 --- a/src/screenshot.cpp +++ b/src/screenshot.cpp @@ -175,13 +175,14 @@ static void annotate_pixels(BYTE* pixels, int bmpWidth, int bmpHeight, bmi.bmiHeader.biCompression = BI_RGB; void* dibBits = nullptr; - HDC memDC = CreateCompatibleDC(nullptr); - HBITMAP hBitmap = CreateDIBSection(memDC, &bmi, DIB_RGB_COLORS, &dibBits, nullptr, 0); - if (!hBitmap || !dibBits) { DeleteDC(memDC); return; } + wil::unique_hdc memDC(CreateCompatibleDC(nullptr)); + if (!memDC) return; + wil::unique_hbitmap hBitmap(CreateDIBSection(memDC.get(), &bmi, DIB_RGB_COLORS, &dibBits, nullptr, 0)); + if (!hBitmap || !dibBits) return; // Copy pixels into the DIB memcpy(dibBits, pixels, bmpWidth * bmpHeight * 4); - HGDIOBJ old = SelectObject(memDC, hBitmap); + HGDIOBJ old = SelectObject(memDC.get(), hBitmap.get()); RECT winRect{}; GetWindowRect(hwnd, &winRect); @@ -195,17 +196,17 @@ static void annotate_pixels(BYTE* pixels, int bmpWidth, int bmpHeight, std::vector elements; collect_elements(*tree, elements); - HPEN pen = CreatePen(PS_SOLID, 2, RGB(255, 50, 50)); + wil::unique_hpen pen(CreatePen(PS_SOLID, 2, RGB(255, 50, 50))); HBRUSH brush = static_cast(GetStockObject(NULL_BRUSH)); - HGDIOBJ oldPen = SelectObject(memDC, pen); - HGDIOBJ oldBrush = SelectObject(memDC, brush); - SetBkMode(memDC, TRANSPARENT); - SetTextColor(memDC, RGB(255, 50, 50)); + HGDIOBJ oldPen = SelectObject(memDC.get(), pen.get()); + HGDIOBJ oldBrush = SelectObject(memDC.get(), brush); + SetBkMode(memDC.get(), TRANSPARENT); + SetTextColor(memDC.get(), RGB(255, 50, 50)); - HFONT font = CreateFontW(-12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, - DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, - CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Consolas"); - HGDIOBJ oldFont = SelectObject(memDC, font); + wil::unique_hfont font(CreateFontW(-12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Consolas")); + HGDIOBJ oldFont = SelectObject(memDC.get(), font.get()); for (auto* el : elements) { if (el->bounds.width <= 0 || el->bounds.height <= 0) continue; @@ -240,12 +241,12 @@ static void annotate_pixels(BYTE* pixels, int bmpWidth, int bmpHeight, int h = static_cast(lh * scaleY); if (x + w <= 0 || y + h <= 0 || x >= bmpWidth || y >= bmpHeight) continue; - Rectangle(memDC, x, y, x + w, y + h); + Rectangle(memDC.get(), x, y, x + w, y + h); if (!el->id.empty()) { std::wstring label(el->id.begin(), el->id.end()); SIZE textSize{}; - GetTextExtentPoint32W(memDC, label.c_str(), static_cast(label.size()), &textSize); + GetTextExtentPoint32W(memDC.get(), label.c_str(), static_cast(label.size()), &textSize); int labelW = textSize.cx + 4; int labelH = textSize.cy + 2; @@ -264,20 +265,17 @@ static void annotate_pixels(BYTE* pixels, int bmpWidth, int bmpHeight, labelRect = {x, y + h, x + labelW, y + h + labelH}; } - HBRUSH bgBrush = CreateSolidBrush(RGB(255, 255, 220)); - FillRect(memDC, &labelRect, bgBrush); - DeleteObject(bgBrush); - TextOutW(memDC, labelRect.left + 2, labelRect.top + 1, + wil::unique_hbrush bgBrush(CreateSolidBrush(RGB(255, 255, 220))); + FillRect(memDC.get(), &labelRect, bgBrush.get()); + TextOutW(memDC.get(), labelRect.left + 2, labelRect.top + 1, label.c_str(), static_cast(label.size())); } } - SelectObject(memDC, oldFont); - SelectObject(memDC, oldBrush); - SelectObject(memDC, oldPen); - SelectObject(memDC, old); - DeleteObject(font); - DeleteObject(pen); + SelectObject(memDC.get(), oldFont); + SelectObject(memDC.get(), oldBrush); + SelectObject(memDC.get(), oldPen); + SelectObject(memDC.get(), old); // Copy annotated pixels back memcpy(pixels, dibBits, bmpWidth * bmpHeight * 4); @@ -288,8 +286,6 @@ static void annotate_pixels(BYTE* pixels, int bmpWidth, int bmpHeight, pixels[i] = 255; } - DeleteObject(hBitmap); - DeleteDC(memDC); } // Save BGRA pixels to PNG using WIC diff --git a/src/tap/lvt_tap.cpp b/src/tap/lvt_tap.cpp index 9fb556a..53e7c82 100644 --- a/src/tap/lvt_tap.cpp +++ b/src/tap/lvt_tap.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -98,7 +99,7 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 { LONG m_refCount = 1; wil::com_ptr m_site; wil::com_ptr m_diag; - HWND m_msgWnd = nullptr; // Message-only window for UI thread dispatch + wil::unique_hwnd m_msgWnd; // Message-only window for UI thread dispatch std::map m_nodes; std::vector m_roots; std::wstring m_pipeName; @@ -173,11 +174,11 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 { return S_OK; } - BSTR initData = nullptr; - diag->GetInitializationData(&initData); + BSTR rawInitData = nullptr; + diag->GetInitializationData(&rawInitData); + wil::unique_bstr initData(rawInitData); if (initData) { - std::wstring data(initData); - SysFreeString(initData); + std::wstring data(initData.get()); // Format: "pipe_name" or "pipe_name|PROPS" auto sep = data.find(L'|'); if (sep != std::wstring::npos) { @@ -205,11 +206,11 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 { wc.hInstance = GetCurrentModuleHandle(); wc.lpszClassName = L"LvtTapMsg"; RegisterClassW(&wc); - m_msgWnd = CreateWindowExW(0, L"LvtTapMsg", nullptr, 0, - 0, 0, 0, 0, HWND_MESSAGE, nullptr, wc.hInstance, nullptr); + m_msgWnd.reset(CreateWindowExW(0, L"LvtTapMsg", nullptr, 0, + 0, 0, 0, 0, HWND_MESSAGE, nullptr, wc.hInstance, nullptr)); if (m_msgWnd) { - SetWindowLongPtrW(m_msgWnd, GWLP_USERDATA, reinterpret_cast(this)); - LogMsg("Created message window %p on thread %lu", m_msgWnd, GetCurrentThreadId()); + SetWindowLongPtrW(m_msgWnd.get(), GWLP_USERDATA, reinterpret_cast(this)); + LogMsg("Created message window %p on thread %lu", m_msgWnd.get(), GetCurrentThreadId()); } // AdviseVisualTreeChange hangs if called on the SetSite thread. @@ -217,10 +218,9 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 { AddRef(); wil::com_ptr threadSelf; threadSelf.attach(this); - HANDLE hThread = CreateThread(nullptr, 0, &AdviseThreadProc, threadSelf.get(), 0, nullptr); + wil::unique_handle hThread(CreateThread(nullptr, 0, &AdviseThreadProc, threadSelf.get(), 0, nullptr)); if (hThread) { (void)threadSelf.detach(); - CloseHandle(hThread); // Don't wait — return immediately to unblock UI thread } else { LogMsg("CreateThread failed: %lu", GetLastError()); } @@ -255,14 +255,14 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 { // SendMessage blocks until the UI thread processes the message. if (m_msgWnd) { LogMsg("Dispatching CollectBounds to UI thread via SendMessage"); - SendMessageW(m_msgWnd, WM_COLLECT_BOUNDS, 0, + SendMessageW(m_msgWnd.get(), WM_COLLECT_BOUNDS, 0, reinterpret_cast(this)); } // Get element positions via TransformToVisual (works around broken // ActualOffset serialization in WinUI3). Must run on the UI thread. #if LVT_HAS_XAML_PROJECTION if (m_msgWnd) { - SendMessageW(m_msgWnd, WM_COLLECT_BOUNDS + 1, 0, + SendMessageW(m_msgWnd.get(), WM_COLLECT_BOUNDS + 1, 0, reinterpret_cast(this)); } #endif @@ -344,11 +344,24 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 { if (FAILED(hr)) { return; } + wil::unique_cotaskmem propsMem(props); + wil::unique_cotaskmem sourcesMem(sources); bool hasWidth = false, hasHeight = false; bool foundOffset = false; for (unsigned int i = 0; i < propCount; i++) { - std::wstring name = props[i].PropertyName ? props[i].PropertyName : L""; - std::wstring value = props[i].Value ? props[i].Value : L""; + wil::unique_bstr type(props[i].Type); + wil::unique_bstr declaringType(props[i].DeclaringType); + wil::unique_bstr valueTypeBstr(props[i].ValueType); + wil::unique_bstr propertyName(props[i].PropertyName); + wil::unique_bstr propertyValue(props[i].Value); + props[i].Type = nullptr; + props[i].DeclaringType = nullptr; + props[i].ValueType = nullptr; + props[i].PropertyName = nullptr; + props[i].Value = nullptr; + + std::wstring name = propertyName ? propertyName.get() : L""; + std::wstring value = propertyValue ? propertyValue.get() : L""; if (name == L"ActualWidth" && !value.empty()) { double v = _wtof(value.c_str()); if (std::isfinite(v)) { @@ -373,7 +386,7 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 { // Only capture the first occurrence of each (most-specific in the chain). // For text-like properties, check ValueType to avoid handle references // (XAML serializes reference types as numeric handle IDs). - std::wstring valueType = props[i].ValueType ? props[i].ValueType : L""; + std::wstring valueType = valueTypeBstr ? valueTypeBstr.get() : L""; bool isTextProp = (name == L"Text" || name == L"Content" || name == L"Header" || name == L"PlaceholderText" || name == L"Description" || name == L"Title" || @@ -407,18 +420,13 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 { node.properties.emplace_back(name, value); } } - if (props[i].Type) SysFreeString(props[i].Type); - if (props[i].DeclaringType) SysFreeString(props[i].DeclaringType); - if (props[i].ValueType) SysFreeString(props[i].ValueType); - if (props[i].PropertyName) SysFreeString(props[i].PropertyName); - if (props[i].Value) SysFreeString(props[i].Value); } for (unsigned int i = 0; i < srcCount; i++) { - if (sources[i].TargetType) SysFreeString(sources[i].TargetType); - if (sources[i].Name) SysFreeString(sources[i].Name); + wil::unique_bstr targetType(sources[i].TargetType); + wil::unique_bstr sourceName(sources[i].Name); + sources[i].TargetType = nullptr; + sources[i].Name = nullptr; } - if (props) CoTaskMemFree(props); - if (sources) CoTaskMemFree(sources); node.hasBounds = hasWidth && hasHeight; } @@ -619,22 +627,17 @@ class LvtTap : public IObjectWithSite, public IVisualTreeServiceCallback2 { WideCharToMultiByte(CP_UTF8, 0, json.c_str(), (int)json.size(), utf8.data(), len, nullptr, nullptr); - HANDLE pipe = CreateFileW(m_pipeName.c_str(), GENERIC_WRITE, 0, - nullptr, OPEN_EXISTING, 0, nullptr); - if (pipe != INVALID_HANDLE_VALUE) { + wil::unique_hfile pipe(CreateFileW(m_pipeName.c_str(), GENERIC_WRITE, 0, + nullptr, OPEN_EXISTING, 0, nullptr)); + if (pipe) { DWORD written = 0; - WriteFile(pipe, utf8.data(), (DWORD)utf8.size(), &written, nullptr); - FlushFileBuffers(pipe); - CloseHandle(pipe); + WriteFile(pipe.get(), utf8.data(), (DWORD)utf8.size(), &written, nullptr); + FlushFileBuffers(pipe.get()); LogMsg("Wrote %lu bytes to pipe", written); } else { LogMsg("Failed to open pipe: %lu", GetLastError()); } } - - ~LvtTap() { - if (m_msgWnd) DestroyWindow(m_msgWnd); - } }; // Window procedure for dispatching GetPropertyValuesChain to UI thread diff --git a/src/tap_winforms/winforms_tap_native.cpp b/src/tap_winforms/winforms_tap_native.cpp index 9617153..585e602 100644 --- a/src/tap_winforms/winforms_tap_native.cpp +++ b/src/tap_winforms/winforms_tap_native.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -46,17 +47,16 @@ static std::wstring ReadPipeName() { std::wstring dir = GetDllDirectory(); std::wstring path = dir + L"\\lvt_WinForms_pipe.txt"; - HANDLE hFile = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, - nullptr, OPEN_EXISTING, 0, nullptr); - if (hFile == INVALID_HANDLE_VALUE) { + wil::unique_hfile hFile(CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, 0, nullptr)); + if (!hFile) { LogMsg("Failed to open pipe name file: %lu", GetLastError()); return {}; } char buf[256]{}; DWORD bytesRead = 0; - ReadFile(hFile, buf, sizeof(buf) - 1, &bytesRead, nullptr); - CloseHandle(hFile); + ReadFile(hFile.get(), buf, sizeof(buf) - 1, &bytesRead, nullptr); // Convert UTF-8 to wide int wlen = MultiByteToWideChar(CP_UTF8, 0, buf, bytesRead, nullptr, 0); @@ -73,7 +73,7 @@ static std::wstring ReadPipeName() { // Try .NET Framework hosting via ICLRMetaHost static bool TryNetFramework(const std::wstring& assemblyPath, const std::wstring& pipeName) { - HMODULE hMscoree = LoadLibraryW(L"mscoree.dll"); + wil::unique_hmodule hMscoree(LoadLibraryW(L"mscoree.dll")); if (!hMscoree) { LogMsg("mscoree.dll not found"); return false; @@ -81,10 +81,9 @@ static bool TryNetFramework(const std::wstring& assemblyPath, const std::wstring using CLRCreateInstanceFn = HRESULT(WINAPI*)(REFCLSID, REFIID, LPVOID*); auto pCLRCreateInstance = reinterpret_cast( - GetProcAddress(hMscoree, "CLRCreateInstance")); + GetProcAddress(hMscoree.get(), "CLRCreateInstance")); if (!pCLRCreateInstance) { LogMsg("CLRCreateInstance not found in mscoree.dll"); - FreeLibrary(hMscoree); return false; } @@ -92,7 +91,6 @@ static bool TryNetFramework(const std::wstring& assemblyPath, const std::wstring HRESULT hr = pCLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(metaHost.put())); if (FAILED(hr)) { LogMsg("CLRCreateInstance failed: 0x%08X", hr); - FreeLibrary(hMscoree); return false; } @@ -182,18 +180,21 @@ static bool TryNetCore(const std::wstring& assemblyPath, const std::wstring& pip std::wstring dotnetDir = std::wstring(progFiles) + L"\\dotnet\\host\\fxr"; WIN32_FIND_DATAW fd; - HANDLE hFind = FindFirstFileW((dotnetDir + L"\\*").c_str(), &fd); + wil::unique_hfind hFind(FindFirstFileW((dotnetDir + L"\\*").c_str(), &fd)); std::wstring latestFxr; - if (hFind != INVALID_HANDLE_VALUE) { + if (hFind) { do { if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && fd.cFileName[0] != L'.') { latestFxr = dotnetDir + L"\\" + fd.cFileName + L"\\hostfxr.dll"; } - } while (FindNextFileW(hFind, &fd)); - FindClose(hFind); + } while (FindNextFileW(hFind.get(), &fd)); } if (!latestFxr.empty()) { + // Intentionally kept resident (raw, not scope-owned): hostfxr pins the + // hosted CoreCLR resolver for the lifetime of this injected process. + // Freeing it on scope exit would diverge from the original behavior and + // risk unloading a host library out from under the running runtime. hHostfxr = LoadLibraryW(latestFxr.c_str()); LogMsg("Loaded hostfxr from: %ls -> %p", latestFxr.c_str(), hHostfxr); } @@ -356,8 +357,7 @@ BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) { // Spawn worker thread for the initial collection. // The worker calls FreeLibraryAndExitThread when done to unload this DLL. - HANDLE hThread = CreateThread(nullptr, 0, WorkerThread, reinterpret_cast(hMod), 0, nullptr); - if (hThread) CloseHandle(hThread); + wil::unique_handle hThread(CreateThread(nullptr, 0, WorkerThread, reinterpret_cast(hMod), 0, nullptr)); } return TRUE; } diff --git a/src/tap_wpf/wpf_tap_native.cpp b/src/tap_wpf/wpf_tap_native.cpp index ee7008d..afd045b 100644 --- a/src/tap_wpf/wpf_tap_native.cpp +++ b/src/tap_wpf/wpf_tap_native.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -46,17 +47,16 @@ static std::wstring ReadPipeName() { std::wstring dir = GetDllDirectory(); std::wstring path = dir + L"\\lvt_wpf_pipe.txt"; - HANDLE hFile = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, - nullptr, OPEN_EXISTING, 0, nullptr); - if (hFile == INVALID_HANDLE_VALUE) { + wil::unique_hfile hFile(CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, 0, nullptr)); + if (!hFile) { LogMsg("Failed to open pipe name file: %lu", GetLastError()); return {}; } char buf[256]{}; DWORD bytesRead = 0; - ReadFile(hFile, buf, sizeof(buf) - 1, &bytesRead, nullptr); - CloseHandle(hFile); + ReadFile(hFile.get(), buf, sizeof(buf) - 1, &bytesRead, nullptr); // Convert UTF-8 to wide int wlen = MultiByteToWideChar(CP_UTF8, 0, buf, bytesRead, nullptr, 0); @@ -73,7 +73,7 @@ static std::wstring ReadPipeName() { // Try .NET Framework hosting via ICLRMetaHost static bool TryNetFramework(const std::wstring& assemblyPath, const std::wstring& pipeName) { - HMODULE hMscoree = LoadLibraryW(L"mscoree.dll"); + wil::unique_hmodule hMscoree(LoadLibraryW(L"mscoree.dll")); if (!hMscoree) { LogMsg("mscoree.dll not found"); return false; @@ -81,10 +81,9 @@ static bool TryNetFramework(const std::wstring& assemblyPath, const std::wstring using CLRCreateInstanceFn = HRESULT(WINAPI*)(REFCLSID, REFIID, LPVOID*); auto pCLRCreateInstance = reinterpret_cast( - GetProcAddress(hMscoree, "CLRCreateInstance")); + GetProcAddress(hMscoree.get(), "CLRCreateInstance")); if (!pCLRCreateInstance) { LogMsg("CLRCreateInstance not found in mscoree.dll"); - FreeLibrary(hMscoree); return false; } @@ -92,7 +91,6 @@ static bool TryNetFramework(const std::wstring& assemblyPath, const std::wstring HRESULT hr = pCLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(metaHost.put())); if (FAILED(hr)) { LogMsg("CLRCreateInstance failed: 0x%08X", hr); - FreeLibrary(hMscoree); return false; } @@ -182,18 +180,21 @@ static bool TryNetCore(const std::wstring& assemblyPath, const std::wstring& pip std::wstring dotnetDir = std::wstring(progFiles) + L"\\dotnet\\host\\fxr"; WIN32_FIND_DATAW fd; - HANDLE hFind = FindFirstFileW((dotnetDir + L"\\*").c_str(), &fd); + wil::unique_hfind hFind(FindFirstFileW((dotnetDir + L"\\*").c_str(), &fd)); std::wstring latestFxr; - if (hFind != INVALID_HANDLE_VALUE) { + if (hFind) { do { if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && fd.cFileName[0] != L'.') { latestFxr = dotnetDir + L"\\" + fd.cFileName + L"\\hostfxr.dll"; } - } while (FindNextFileW(hFind, &fd)); - FindClose(hFind); + } while (FindNextFileW(hFind.get(), &fd)); } if (!latestFxr.empty()) { + // Intentionally kept resident (raw, not scope-owned): hostfxr pins the + // hosted CoreCLR resolver for the lifetime of this injected process. + // Freeing it on scope exit would diverge from the original behavior and + // risk unloading a host library out from under the running runtime. hHostfxr = LoadLibraryW(latestFxr.c_str()); LogMsg("Loaded hostfxr from: %ls -> %p", latestFxr.c_str(), hHostfxr); } @@ -357,8 +358,7 @@ BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) { // Spawn worker thread for the initial collection. // The worker calls FreeLibraryAndExitThread when done to unload this DLL. - HANDLE hThread = CreateThread(nullptr, 0, WorkerThread, reinterpret_cast(hMod), 0, nullptr); - if (hThread) CloseHandle(hThread); + wil::unique_handle hThread(CreateThread(nullptr, 0, WorkerThread, reinterpret_cast(hMod), 0, nullptr)); } return TRUE; } diff --git a/tests/integration_tests.cpp b/tests/integration_tests.cpp index beb98e4..6cd6ea5 100644 --- a/tests/integration_tests.cpp +++ b/tests/integration_tests.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +86,8 @@ class NotepadFixture : public ::testing::Test { std::string cmd = "notepad.exe \"" + s_temp_file + "\""; CreateProcessA(nullptr, cmd.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &s_pi); + s_process.reset(s_pi.hProcess); + s_thread.reset(s_pi.hThread); if (s_pi.hProcess) { WaitForInputIdle(s_pi.hProcess, 5000); } @@ -118,8 +121,10 @@ class NotepadFixture : public ::testing::Test { static void TearDownTestSuite() { if (s_pi.hProcess) { TerminateProcess(s_pi.hProcess, 0); - CloseHandle(s_pi.hProcess); - CloseHandle(s_pi.hThread); + s_process.reset(); + s_thread.reset(); + s_pi.hProcess = nullptr; + s_pi.hThread = nullptr; } fs::remove(s_temp_file); } @@ -136,12 +141,16 @@ class NotepadFixture : public ::testing::Test { static std::string s_temp_file; static PROCESS_INFORMATION s_pi; + static wil::unique_handle s_process; + static wil::unique_handle s_thread; static DWORD s_pid; static HWND s_hwnd; }; std::string NotepadFixture::s_temp_file; PROCESS_INFORMATION NotepadFixture::s_pi = {}; +wil::unique_handle NotepadFixture::s_process; +wil::unique_handle NotepadFixture::s_thread; DWORD NotepadFixture::s_pid = 0; HWND NotepadFixture::s_hwnd = nullptr; @@ -668,6 +677,8 @@ class AvaloniaFixture : public ::testing::Test { ASSERT_TRUE(CreateProcessA(nullptr, cmd.data(), nullptr, nullptr, FALSE, 0, nullptr, workdir.c_str(), &si, &pi_)) << "Failed to launch " << appExe.string() << " (error " << GetLastError() << ")"; + process_.reset(pi_.hProcess); + thread_.reset(pi_.hThread); if (pi_.hProcess) WaitForInputIdle(pi_.hProcess, 5000); @@ -692,8 +703,10 @@ class AvaloniaFixture : public ::testing::Test { void TearDown() override { if (pi_.hProcess) { TerminateProcess(pi_.hProcess, 0); - CloseHandle(pi_.hProcess); - CloseHandle(pi_.hThread); + process_.reset(); + thread_.reset(); + pi_.hProcess = nullptr; + pi_.hThread = nullptr; } } @@ -702,6 +715,8 @@ class AvaloniaFixture : public ::testing::Test { } PROCESS_INFORMATION pi_{}; + wil::unique_handle process_; + wil::unique_handle thread_; json initialDump_; }; @@ -766,6 +781,8 @@ class WinFormsSampleFixture : public ::testing::Test { return; } s_pid = s_pi.dwProcessId; + s_process.reset(s_pi.hProcess); + s_thread.reset(s_pi.hThread); if (s_pi.hProcess) { WaitForInputIdle(s_pi.hProcess, 5000); } @@ -790,8 +807,10 @@ class WinFormsSampleFixture : public ::testing::Test { static void TearDownTestSuite() { if (s_pi.hProcess) { TerminateProcess(s_pi.hProcess, 0); - CloseHandle(s_pi.hProcess); - CloseHandle(s_pi.hThread); + s_process.reset(); + s_thread.reset(); + s_pi.hProcess = nullptr; + s_pi.hThread = nullptr; } } @@ -805,6 +824,8 @@ class WinFormsSampleFixture : public ::testing::Test { } static PROCESS_INFORMATION s_pi; + static wil::unique_handle s_process; + static wil::unique_handle s_thread; static DWORD s_pid; static bool s_ready; static std::string s_sample_exe; @@ -812,6 +833,8 @@ class WinFormsSampleFixture : public ::testing::Test { }; PROCESS_INFORMATION WinFormsSampleFixture::s_pi = {}; +wil::unique_handle WinFormsSampleFixture::s_process; +wil::unique_handle WinFormsSampleFixture::s_thread; DWORD WinFormsSampleFixture::s_pid = 0; bool WinFormsSampleFixture::s_ready = false; std::string WinFormsSampleFixture::s_sample_exe; @@ -941,6 +964,8 @@ class WpfSampleFixture : public ::testing::Test { return; } s_pid = s_pi.dwProcessId; + s_process.reset(s_pi.hProcess); + s_thread.reset(s_pi.hThread); if (s_pi.hProcess) { WaitForInputIdle(s_pi.hProcess, 5000); } @@ -966,8 +991,10 @@ class WpfSampleFixture : public ::testing::Test { static void TearDownTestSuite() { if (s_pi.hProcess) { TerminateProcess(s_pi.hProcess, 0); - CloseHandle(s_pi.hProcess); - CloseHandle(s_pi.hThread); + s_process.reset(); + s_thread.reset(); + s_pi.hProcess = nullptr; + s_pi.hThread = nullptr; } } @@ -981,6 +1008,8 @@ class WpfSampleFixture : public ::testing::Test { } static PROCESS_INFORMATION s_pi; + static wil::unique_handle s_process; + static wil::unique_handle s_thread; static DWORD s_pid; static bool s_ready; static std::string s_sample_exe; @@ -988,6 +1017,8 @@ class WpfSampleFixture : public ::testing::Test { }; PROCESS_INFORMATION WpfSampleFixture::s_pi = {}; +wil::unique_handle WpfSampleFixture::s_process; +wil::unique_handle WpfSampleFixture::s_thread; DWORD WpfSampleFixture::s_pid = 0; bool WpfSampleFixture::s_ready = false; std::string WpfSampleFixture::s_sample_exe; @@ -1058,6 +1089,8 @@ class WinUI3SampleFixture : public ::testing::Test { return; } s_pid = s_pi.dwProcessId; + s_process.reset(s_pi.hProcess); + s_thread.reset(s_pi.hThread); if (s_pi.hProcess) { WaitForInputIdle(s_pi.hProcess, 10000); } @@ -1088,8 +1121,10 @@ class WinUI3SampleFixture : public ::testing::Test { static void TearDownTestSuite() { if (s_pi.hProcess) { TerminateProcess(s_pi.hProcess, 0); - CloseHandle(s_pi.hProcess); - CloseHandle(s_pi.hThread); + s_process.reset(); + s_thread.reset(); + s_pi.hProcess = nullptr; + s_pi.hThread = nullptr; } } @@ -1103,6 +1138,8 @@ class WinUI3SampleFixture : public ::testing::Test { } static PROCESS_INFORMATION s_pi; + static wil::unique_handle s_process; + static wil::unique_handle s_thread; static DWORD s_pid; static bool s_ready; static std::string s_sample_exe; @@ -1110,6 +1147,8 @@ class WinUI3SampleFixture : public ::testing::Test { }; PROCESS_INFORMATION WinUI3SampleFixture::s_pi = {}; +wil::unique_handle WinUI3SampleFixture::s_process; +wil::unique_handle WinUI3SampleFixture::s_thread; DWORD WinUI3SampleFixture::s_pid = 0; bool WinUI3SampleFixture::s_ready = false; std::string WinUI3SampleFixture::s_sample_exe; @@ -1330,13 +1369,13 @@ class ComCtlWindowFixture : public ::testing::Test { INITCOMMONCONTROLSEX icc{sizeof(INITCOMMONCONTROLSEX), ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES}; ASSERT_TRUE(InitCommonControlsEx(&icc)) << "InitCommonControlsEx failed"; - readyEvent_ = CreateEventW(nullptr, TRUE, FALSE, nullptr); + readyEvent_.reset(CreateEventW(nullptr, TRUE, FALSE, nullptr)); ASSERT_NE(readyEvent_, nullptr); - thread_ = CreateThread(nullptr, 0, &ComCtlWindowFixture::thread_proc, this, 0, nullptr); + thread_.reset(CreateThread(nullptr, 0, &ComCtlWindowFixture::thread_proc, this, 0, nullptr)); ASSERT_NE(thread_, nullptr); - ASSERT_EQ(WaitForSingleObject(readyEvent_, 5000), WAIT_OBJECT_0) + ASSERT_EQ(WaitForSingleObject(readyEvent_.get(), 5000), WAIT_OBJECT_0) << "Timed out creating ComCtl test window"; ASSERT_NE(parentHwnd_, nullptr); ASSERT_NE(listViewHwnd_, nullptr); @@ -1349,11 +1388,10 @@ class ComCtlWindowFixture : public ::testing::Test { if (parentHwnd_) PostMessageW(parentHwnd_, WM_APP + 1, 0, 0); if (thread_) { - WaitForSingleObject(thread_, 5000); - CloseHandle(thread_); + WaitForSingleObject(thread_.get(), 5000); + thread_.reset(); } - if (readyEvent_) - CloseHandle(readyEvent_); + readyEvent_.reset(); } std::string get_hwnd_arg() const { @@ -1370,6 +1408,7 @@ class ComCtlWindowFixture : public ::testing::Test { static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_APP + 1) { + // Window proc owns the HWND lifetime; close on the UI thread. DestroyWindow(hwnd); return 0; } @@ -1396,7 +1435,7 @@ class ComCtlWindowFixture : public ::testing::Test { 120, 120, 520, 360, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); if (!parentHwnd_) { - SetEvent(readyEvent_); + SetEvent(readyEvent_.get()); return; } @@ -1418,7 +1457,7 @@ class ComCtlWindowFixture : public ::testing::Test { UpdateWindow(parentHwnd_); pump_pending_messages(); - SetEvent(readyEvent_); + SetEvent(readyEvent_.get()); MSG msg; while (GetMessageW(&msg, nullptr, 0, 0) > 0) { @@ -1517,8 +1556,8 @@ class ComCtlWindowFixture : public ::testing::Test { HWND parentHwnd_ = nullptr; HWND listViewHwnd_ = nullptr; HWND treeViewHwnd_ = nullptr; - HANDLE readyEvent_ = nullptr; - HANDLE thread_ = nullptr; + wil::unique_event readyEvent_; + wil::unique_handle thread_; bool listTextOk_ = false; bool treeTextOk_ = false; }; @@ -1703,10 +1742,11 @@ class KnownWindowFixture : public ::testing::Test { RegisterClassExW(&wc); // Create parent window - parentHwnd_ = CreateWindowExW(0, L"LvtTestWindow", L"LVT Test Window", + parentWindow_.reset(CreateWindowExW(0, L"LvtTestWindow", L"LVT Test Window", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 400, 300, - nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + nullptr, nullptr, GetModuleHandle(nullptr), nullptr)); + parentHwnd_ = parentWindow_.get(); ASSERT_NE(parentHwnd_, nullptr) << "Failed to create test window"; // Child controls at known client-area positions @@ -1737,7 +1777,8 @@ class KnownWindowFixture : public ::testing::Test { } void TearDown() override { - if (parentHwnd_) DestroyWindow(parentHwnd_); + parentWindow_.reset(); + parentHwnd_ = nullptr; UnregisterClassW(L"LvtTestWindow", GetModuleHandle(nullptr)); } @@ -1747,6 +1788,7 @@ class KnownWindowFixture : public ::testing::Test { return buf; } + wil::unique_hwnd parentWindow_; HWND parentHwnd_ = nullptr; HWND buttonHwnd_ = nullptr; HWND editHwnd_ = nullptr;