RegisterWaitForSingleObject回调提前触发问题求助(监控Notepad)
问题:Notepad进程监控程序启动后立刻触发终止回调
需求目标
监控notepad.exe进程,当其终止时自动重启。
预期程序行为
- 使用
ShellExecuteEx启动Notepad进程; - 通过
RegisterWaitForSingleObject注册该进程; - 仅当Notepad进程终止时,触发
ProcessWaitCallback回调函数。
实际异常行为
- 程序一运行就触发
ProcessWaitCallback回调,导致Notepad进程被重复启动; - 未手动关闭Notepad时,回调仍会触发,生成两个Notepad实例;
- 使用
CreateProcess替代ShellExecuteEx时,出现完全相同的异常。
相关代码及运行输出
基于ShellExecuteEx的代码
#include <windows.h> #include <iostream> #include <atlbase.h> #include "conio.h" using namespace ::std; HANDLE hWaitHandle; LPCSTR notepadExePath = "C:\\Windows\\notepad.exe"; SHELLEXECUTEINFO shellExecInfo = { 0 }; bool IsValidHandle(HANDLE hHandle, string logString) { bool returnVal = hHandle != NULL && hHandle != INVALID_HANDLE_VALUE; cout << "IsValidHandle(): " << logString << " " << returnVal << endl; return returnVal; } SHELLEXECUTEINFO ShellExecEx( HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPCSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd, ULONG fmask) { SHELLEXECUTEINFO shellExecInfoLocal = { 0 }; shellExecInfoLocal.cbSize = sizeof(SHELLEXECUTEINFO); shellExecInfoLocal.fMask = fmask; shellExecInfoLocal.hwnd = hwnd; shellExecInfoLocal.lpVerb = lpOperation; shellExecInfoLocal.lpFile = lpFile; shellExecInfoLocal.lpParameters = lpParameters; shellExecInfoLocal.lpDirectory = lpDirectory; shellExecInfoLocal.nShow = nShowCmd; shellExecInfoLocal.hInstApp = NULL; cout << "ShellExecEx(): Invoking LPCWSTR version of ShellExecEx" << endl; ShellExecuteEx(&shellExecInfoLocal); return shellExecInfoLocal; } // Callback function to be called when the process terminates void CALLBACK ProcessWaitCallback(PVOID lpParam, BOOLEAN TimerOrWaitFired) { cout << "ProcessWaitCallback(): Process terminated. Restarting..." << std::endl; if (IsValidHandle(shellExecInfo.hProcess, "ProcessWaitCallback(): shellExecInfo.hProcess")) { CloseHandle(shellExecInfo.hProcess); } shellExecInfo = ShellExecEx(nullptr, "open", notepadExePath, "", "", SW_SHOWNORMAL, SEE_MASK_NOCLOSEPROCESS); auto code = reinterpret_cast<INT_PTR>(shellExecInfo.hInstApp); if (code <= 32) { cout << "ProcessWaitCallback(): Error re-starting Windows 365:" << GetLastError() << endl; return; } else { cout << "ProcessWaitCallback(): Restarted notepad with pid:" << GetProcessId(shellExecInfo.hProcess) << endl; } if (!RegisterWaitForSingleObject( &hWaitHandle, shellExecInfo.hProcess, ProcessWaitCallback, NULL, INFINITE, WT_EXECUTEONLYONCE)) { cout << "ProcessWaitCallback(): Error registering wait operation: " << GetLastError() << std::endl; } } int main() { shellExecInfo = ShellExecEx(nullptr, "open", notepadExePath, "", "", SW_SHOWNORMAL, SEE_MASK_NOCLOSEPROCESS); auto code = reinterpret_cast<INT_PTR>(shellExecInfo.hInstApp); if (code <= 32) { cout << "main(): Error Starting Notepad:" << GetLastError() << endl; return 1; } DWORD w365ProcessId = GetProcessId(shellExecInfo.hProcess); cout << "main(): Process started successfully pid:" << w365ProcessId << endl; if (!RegisterWaitForSingleObject( &hWaitHandle, shellExecInfo.hProcess, ProcessWaitCallback, NULL, INFINITE, WT_EXECUTEONLYONCE )) { cout << "main(): Error registering wait operation: " << GetLastError() << std::endl; } cout << "main(): Press any key to exit 1..." << std::endl; _getch(); cout << "main(): Press any key to exit 2..." << std::endl; _getch(); if (IsValidHandle(hWaitHandle, "main(): hWaitHandle")) { if (UnregisterWait(hWaitHandle) == 0) { cout << "main(): Error while unregistering wait handle. error:" << GetLastError() << std::endl; } else { cout << "main(): Wait handle successfully unregistered." << std::endl; } } if (IsValidHandle(shellExecInfo.hProcess, "main(): shellExecInfo.hProcess")) { if (CloseHandle(shellExecInfo.hProcess) == 0) { cout << "main(): Error while closing process handle. error:" << GetLastError() << std::endl; } else { cout << "main(): Process handle successfully closed." << std::endl; } } return 0; }
运行输出(未手动关闭Notepad)
.\MonitorNotepad_WT_EXECUTEONLYONCE.exe ShellExecEx(): Invoking LPCWSTR version of ShellExecEx main(): Process started successfully pid:46116 main(): Press any key to exit 1... ProcessWaitCallback(): Process terminated. Restarting... IsValidHandle(): ProcessWaitCallback(): shellExecInfo.hProcess 1 ShellExecEx(): Invoking LPCWSTR version of ShellExecEx ProcessWaitCallback(): Restarted notepad with pid:39648
基于CreateProcess的测试代码
#include <windows.h> #include <iostream> #include "conio.h" using namespace::std; void CALLBACK ProcessExitCallback(PVOID lpParam, BOOLEAN TimerOrWaitFired) { std::cout << "Process has exited asynchronously." << std::endl; } int main() { LPSTR processPath = "C:\\Windows\\notepad.exe"; // Replace with your actual process path STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(STARTUPINFO)); ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); if (CreateProcess( nullptr, processPath, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) { CloseHandle(pi.hThread); HANDLE waitHandle = NULL; if (RegisterWaitForSingleObject(&waitHandle, pi.hProcess, &ProcessExitCallback, nullptr, INFINITE, WT_EXECUTEONLYONCE)) { std::cout << "Asynchronous wait registered. Waiting for process exit..." << std::endl; _getch(); _getch(); if (CloseHandle(pi.hProcess) == 0) { cout << "CloseHandle error" << endl; } else { cout << "CloseHandle success" << endl; } if (UnregisterWait(waitHandle) == 0) { cout << "UnregisterWait error" << endl; } else { cout << "UnregisterWait success" << endl; } } else { std::cout << "Failed to register asynchronous wait. Error: " << GetLastError() << std::endl; CloseHandle(pi.hProcess); } } else { std::cout << "Failed to create the process. Error: " << GetLastError() << std::endl; } return 0; }
运行输出(未手动关闭Notepad)
Asynchronous wait registered. Waiting for process exit... Process has exited asynchronously. CloseHandle success UnregisterWait success
问题原因
1. ShellExecuteEx场景
使用SEE_MASK_NOCLOSEPROCESS获取的hProcess并非Notepad自身的进程句柄,而是系统用于启动Notepad的辅助进程(如explorer.exe的子进程)。该辅助进程完成启动Notepad的任务后会立即退出,导致hProcess对应的对象进入信号状态,触发RegisterWaitForSingleObject的回调。
2. CreateProcess场景
代码中传入的命令行参数是字符串常量(LPSTR processPath = "C:\\Windows\\notepad.exe"),但CreateProcess的lpCommandLine参数要求传入可修改的缓冲区(系统会内部修改该字符串解析命令行)。传入常量会导致未定义行为,可能使返回的进程句柄无效,而无效句柄会被RegisterWaitForSingleObject判定为已信号状态,触发回调。
解决方案
方案1:修复CreateProcess调用
将命令行参数改为可修改的char数组,确保获取有效的Notepad进程句柄:
#include <windows.h> #include <iostream> #include "conio.h" using namespace std; HANDLE g_hWaitHandle = NULL; char g_szNotepadPath[] = "C:\\Windows\\notepad.exe"; void CALLBACK ProcessWaitCallback(PVOID lpParam, BOOLEAN TimerOrWaitFired) { cout << "Process terminated. Restarting..." << endl; STARTUPINFO si = {0}; PROCESS_INFORMATION pi = {0}; si.cb = sizeof(STARTUPINFO); if (CreateProcess(nullptr, g_szNotepadPath, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) { CloseHandle(pi.hThread); cout << "Restarted Notepad with PID: " << pi.dwProcessId << endl; // 重新注册等待,先注销旧的等待句柄 if (g_hWaitHandle != NULL) { UnregisterWait(g_hWaitHandle); g_hWaitHandle = NULL; } if (!RegisterWaitForSingleObject(&g_hWaitHandle, pi.hProcess, ProcessWaitCallback, nullptr, INFINITE, WT_EXECUTEONLYONCE)) { cout << "Failed to register wait: " << GetLastError() << endl; CloseHandle(pi.hProcess); } } else { cout << "Failed to restart Notepad: " << GetLastError() << endl; } } int main() { STARTUPINFO si = {0}; PROCESS_INFORMATION pi = {0}; si.cb = sizeof(STARTUPINFO); if (CreateProcess(nullptr, g_szNotepadPath, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) { CloseHandle(pi.hThread); cout << "Started Notepad with PID: " << pi.dwProcessId << endl; if (!RegisterWaitForSingleObject(&g_hWaitHandle, pi.hProcess, ProcessWaitCallback, nullptr, INFINITE, WT_EXECUTEONLYONCE)) { cout << "Failed to register wait: " << GetLastError() << endl; CloseHandle(pi.hProcess); return 1; } cout << "Press any key to exit..." << endl; _getch(); // 注销等待并关闭句柄 if (g_hWaitHandle != NULL) { UnregisterWait(g_hWaitHandle); } CloseHandle(pi.hProcess); cout << "Cleanup done." << endl; } else { cout << "Failed to start Notepad: " << GetLastError() << endl; return 1; } return 0; }
方案2:ShellExecuteEx配合进程枚举(若必须使用ShellExecuteEx)
如果需要保留ShellExecuteEx,可在启动后通过枚举进程找到Notepad的真实PID,再打开其进程句柄用于等待:
// 辅助函数:根据路径查找进程PID DWORD FindProcessByPath(const char* szPath) { DWORD dwPID = 0; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot != INVALID_HANDLE_VALUE) { PROCESSENTRY32 pe = {0}; pe.dwSize = sizeof(PROCESSENTRY32); if (Process32First(hSnapshot, &pe)) { do { char szExePath[MAX_PATH] = {0}; HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe.th32ProcessID); if (hProcess != NULL) { GetModuleFileNameExA(hProcess, NULL, szExePath, MAX_PATH); CloseHandle(hProcess); if (_stricmp(szExePath, szPath) == 0) { dwPID = pe.th32ProcessID; break; } } } while (Process32Next(hSnapshot, &pe)); } CloseHandle(hSnapshot); } return dwPID; }
在ShellExecuteEx启动后,调用该函数获取真实PID,再用OpenProcess获取带SYNCHRONIZE权限的句柄,然后注册等待。
内容的提问来源于stack exchange,提问作者coda
相关产品推荐
相关产品推荐

