Reputation: 2477
Goal: I want to monitor notepad.exe and restart it whenever it terminates.
My program should,
ShellExecuteEx
RegisterWaitForSingleObject
ProcessWaitCallback
invoked whenever the notepad process terminates.But My program does,
ShellExecuteEx
RegisterWaitForSingleObject
ProcessWaitCallback
invoked as soon as I run the program resulting in a duplicate notepad process spawning up.The problem is that as soon as I run the program, the callback gets executed (which I expect to get executed when I close the notepad process) resulting in 2 instances of notepad running.
#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;
}
The output is as follows (without me killing the notepad that opens up)
.\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
See how even though I don't close the notepad process still the line ProcessWaitCallback(): Process terminated. Restarting...
gets printed.
Update 1: I see the exact same behavior with CreateProcess as well
#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;
}
here is the output without me closing the notepad process
Asynchronous wait registered. Waiting for process exit...
Process has exited asynchronously.
CloseHandle success
UnregisterWait success
Upvotes: 0
Views: 169
Reputation: 33754
instead RegisterWaitForSingleObject
better use CreateThreadpoolWait
once and SetThreadpoolWait
(every time when you start new process). this api internally called by RegisterWaitForSingleObject
. code can be next:
class RundownProtection
{
LONG _Value;
public:
enum {
v_complete = 0, v_init = 0x80000000
};
BOOL IsRundownCompleted()
{
return v_complete == _Value;
}
_NODISCARD BOOL IsRundownBegin()
{
return 0 <= _Value;
}
_NODISCARD BOOL Acquire()
{
LONG Value, NewValue;
if (0 > (Value = _Value))
{
do
{
NewValue = InterlockedCompareExchangeNoFence(&_Value, Value + 1, Value);
if (NewValue == Value) return TRUE;
} while (0 > (Value = NewValue));
}
return FALSE;
}
_NODISCARD BOOL Release()
{
return InterlockedDecrement(&_Value) == v_complete;
}
// if (Acquire()) { Rundown_l(); Release(); }
void Rundown_l()
{
InterlockedBitTestAndReset(&_Value, 31);
}
RundownProtection(LONG Value = v_complete) : _Value(Value)
{
}
BOOL Init()
{
return InterlockedCompareExchange(&_Value, v_init, v_complete) == v_complete;
}
};
struct AR
{
PTP_WAIT _M_Wait = 0;
RundownProtection _M_lock = RundownProtection::v_init;
static VOID CALLBACK _S_WaitCallback(
_In_ PTP_CALLBACK_INSTANCE /*Instance*/,
_In_ PVOID Context,
_In_ PTP_WAIT Wait,
_In_ TP_WAIT_RESULT WaitResult
)
{
reinterpret_cast<AR*>(Context)->WaitCallback(Wait, WaitResult);
}
VOID WaitCallback(
_In_ PTP_WAIT Wait,
_In_ TP_WAIT_RESULT WaitResult
)
{
switch (WaitResult)
{
case WAIT_OBJECT_0:
if (_M_lock.Acquire())
{
Start(Wait);
if (_M_lock.Release())
{
CloseThreadpoolWait(Wait);
delete this;
}
}
break;
}
}
void Start(_In_ PTP_WAIT Wait)
{
static const WCHAR notepad[] = L"\\notepad.exe";
WCHAR buf[MAX_PATH];
if (ULONG cch = GetWindowsDirectoryW(buf, _countof(buf)))
{
if (cch <= _countof(buf) - _countof(notepad))
{
memcpy(buf + cch, notepad, sizeof(notepad));
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi;
if (CreateProcessW(buf, 0, 0, 0, 0, 0, 0, 0, &si, &pi))
{
NtClose(pi.hThread);
SetThreadpoolWait(Wait, pi.hProcess, 0);
NtClose(pi.hProcess);
}
}
}
}
void Start()
{
if (PTP_WAIT Wait = CreateThreadpoolWait(_S_WaitCallback, this, 0))
{
_M_Wait = Wait;
Start(Wait);
}
}
void Stop()
{
if (_M_lock.Acquire())
{
_M_lock.Rundown_l();
if (_M_lock.Release())
{
if (_M_Wait)
{
WaitForThreadpoolWaitCallbacks(_M_Wait, TRUE);
CloseThreadpoolWait(_M_Wait);
}
delete this;
}
}
}
};
void demo()
{
if (AR* p = new AR)
{
p->Start();
MessageBoxW(0,0,0,0);
p->Stop();
}
}
if use RegisterWaitForSingleObject
then
struct AR
{
HANDLE _M_hWaitObject = 0;
SRWLOCK _M_lock {};
LONG _M_dwRef = 1;
BOOLEAN _M_bStop = FALSE;
void AddRef()
{
InterlockedIncrementNoFence(&_M_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_M_dwRef))
{
delete this;
}
}
static VOID CALLBACK _S_WaitOrTimerCallback(
_In_ PVOID lpParameter,
_In_ BOOLEAN TimerOrWaitFired
)
{
if (TimerOrWaitFired)
{
__debugbreak();
}
reinterpret_cast<AR*>(lpParameter)->WaitOrTimerCallback();
}
VOID WaitOrTimerCallback()
{
AcquireSRWLockExclusive(&_M_lock);
if (HANDLE hWaitObject = _M_hWaitObject)
{
_M_hWaitObject = 0;
if (UnregisterWaitEx(hWaitObject, 0) || ERROR_IO_PENDING != GetLastError())
{
__debugbreak();
};
}
if (!_M_bStop)
{
Start();
}
ReleaseSRWLockExclusive(&_M_lock);
Release();
}
void Start()
{
static const WCHAR notepad[] = L"\\notepad.exe";
WCHAR buf[MAX_PATH];
if (ULONG cch = GetWindowsDirectoryW(buf, _countof(buf)))
{
if (cch <= _countof(buf) - _countof(notepad))
{
memcpy(buf + cch, notepad, sizeof(notepad));
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi;
if (CreateProcessW(buf, 0, 0, 0, 0, 0, 0, 0, &si, &pi))
{
NtClose(pi.hThread);
AddRef();
if (!RegisterWaitForSingleObject(&_M_hWaitObject, pi.hProcess,
_S_WaitOrTimerCallback, this, INFINITE, WT_EXECUTEONLYONCE))
{
Release();
}
NtClose(pi.hProcess);
__nop();
}
}
}
}
void Stop()
{
_M_bStop = TRUE;
AcquireSRWLockExclusive(&_M_lock);
if (HANDLE hWaitObject = _M_hWaitObject)
{
_M_hWaitObject = 0;
if (UnregisterWaitEx(hWaitObject, 0))
{
Release();
}
else if (ERROR_IO_PENDING != GetLastError())
{
__debugbreak();
}
}
ReleaseSRWLockExclusive(&_M_lock);
}
};
void demo()
{
if (AR* p = new AR)
{
p->Start();
MessageBoxW(0,0,0,0);
p->Stop();
p->Release();
}
}
Upvotes: 1