You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 21:35:55