Launch an application and wait until it has finished without blocking redraw - c

I have an interactive Win32 application and at some point I need to launch another application and wait until that other application has finished. During the time the other application runs, the interactive application shouldn't be responsive expect for resizing and moving the window (this implies of course that the interactive application still should continue redrawing).
My current approach is this:
Create a Thread T
Disable the main window (with EnableWindow(handle, FALSE))
Continue message loop
Special WM_APP message sent from thread T will enable the main window again (EnableWindow(handle, TRUE));
Thread T:
launch the application with CreateProcess
wait until application has terminated using WaitForSingleObject
post a special WM_APP message to the main window using PostMessage.
thread terminates here
This works fine, but I'm not sure if this is the correct approach.
Is there a better approach or should I continue like this?

Your approach is fine, just make sure to use PostMessage() for
send a special WM_APP message to the main window
to avoid deadlock if the main thread happens to wait for thread T.
As commenter noted, an alternative for creating a thread is to use a message loop with MsgWaitForMultipleObjectsEx.
The advantages I see:
There is no additional thread required, so there will be no worries about thread-related problems such as race conditions and deadlocks. These problems are often hard to find and debug (typically they only happen on customer machines), so I try to avoid threads as much as possible.
The program flow for creating the process and waiting for it is simpler. It can be sequential instead of event-based like the thread solution.
You have to judge for yourself if this is a better approach for your scenario.
A function that can be used to wait for a process (or any other waitable handle) while processing messages could be as follows. It's implementation is pretty involved (for background info see the links at the end of my answer) but usage is quite easy (see example afterwards).
// Function to process messages until the state of any of the given objects is signaled,
// the timeout is reached or a WM_QUIT message is received.
// Parameter hDialog can be nullptr, if there is no dialog.
//
// Returns ERROR_SUCCESS if any of the given handles is signaled.
// Returns ERROR_TIMEOUT in case of timeout.
// Returns ERROR_CANCELLED if the WM_QUIT message has been received.
// Returns the value of GetLastError() if MsgWaitForMultipleObjectsEx() fails.
DWORD WaitForObjectsWithMsgLoop(
HWND hDialog, const std::vector<HANDLE>& handles, DWORD timeOutMillis = INFINITE )
{
if( handles.empty() )
return ERROR_INVALID_PARAMETER;
DWORD handleCount = static_cast<DWORD>( handles.size() );
DWORD startTime = GetTickCount();
DWORD duration = 0;
do
{
DWORD status = MsgWaitForMultipleObjectsEx(
handleCount, handles.data(),
timeOutMillis - duration,
QS_ALLINPUT,
MWMO_INPUTAVAILABLE );
if( status == WAIT_FAILED )
{
// MsgWaitForMultipleObjectsEx() has failed.
return GetLastError();
}
else if( status >= WAIT_OBJECT_0 && status < WAIT_OBJECT_0 + handleCount )
{
// Any of the handles is signaled.
return ERROR_SUCCESS;
}
else if( status == WAIT_OBJECT_0 + handleCount )
{
// New input is available, process it.
MSG msg;
while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
if( msg.message == WM_QUIT )
{
// End the message loop because of quit message.
PostQuitMessage( static_cast<int>( msg.wParam ) );
return ERROR_CANCELLED;
}
// Enable message filter hooks (that's what the system does in it's message loops).
// You may use a custom code >= MSGF_USER.
// https://blogs.msdn.microsoft.com/oldnewthing/20050428-00/?p=35753
if( ! CallMsgFilter( &msg, MSGF_USER ) )
{
// Optionally process dialog messages.
if( ! hDialog || ! IsDialogMessage( hDialog, &msg ) )
{
// Standard message processing.
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
}
}
duration = GetTickCount() - startTime;
}
while( duration < timeOutMillis );
// Timeout reached.
return ERROR_TIMEOUT;
}
The function could be used in a dialog box procedure as follows. Error handling omitted for brevity.
INT_PTR CALLBACK YourDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
static bool s_childProcessRunning = false;
switch( message )
{
case YourMessageToLaunchProcess:
{
// prevent reentrancy in case the process is already running
if( s_childProcessRunning )
{
MessageBoxW( hDlg, L"Process already running", L"Error", MB_ICONERROR );
return TRUE;
}
// Prepare CreateProcess() arguments
STARTUPINFO si{ sizeof(si) };
PROCESS_INFORMATION pi{};
wchar_t command[] = L"notepad.exe"; // string must be writable!
// Launch the process
if( CreateProcessW( NULL, command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ) )
{
// Set flag to prevent reentrancy.
s_childProcessRunning = true;
// Wait until the child process exits while processing messages
// to keep the window responsive.
DWORD waitRes = WaitForObjectsWithMsgLoop( hDlg, { pi.hProcess } );
// TODO: Check waitRes for error
s_childProcessRunning = false;
// Cleanup
CloseHandle( pi.hThread );
CloseHandle( pi.hProcess );
}
return TRUE;
}
// more message handlers...
}
return FALSE;
}
Obligatory Old New Thing links:
Pumping messages while waiting for a period of time
The dialog manager, part 4: The dialog loop
Rescuing thread messages from modal loops via message filters

Related

Windows WFP Driver: Getting BSOD when processing packets in ClassifyFn callback

I am trying to code a simple firewall application which can allow or block network connection attempts made from userlevel processes.
To do so, following the WFPStarterKit tutorial, I created a WFP Driver which is set to intercept data at FWPM_LAYER_OUTBOUND_TRANSPORT_V4 layer.
The ClassifyFn callback function is responsible for intercepting the connection attempt, and either allow or deny it.
Once the ClassifyFn callback gets hit, the ProcessID of the packet is sent, along with a few other info, to a userlevel process through the FltSendMessage function.
The userlevel process receives the message, checks the ProcessID, and replies a boolean allow/deny command to the driver.
While this approach works when blocking a first packet, in some cases (expecially when allowing multiple packets) the code generates a BSOD with the INVALID_PROCESS_ATTACH_ATTEMPT error code.
The error is triggered at the call to FltSendMessage.
While I am still unable to pinpoint the exact problem,
it seems that making the callout thread wait (through FltSendMessage) for a reply from userlevel can generate this BSOD error on some conditions.
I would be very grateful if you can point me to the right direction.
Here is the function where I register the callout:
NTSTATUS register_example_callout(DEVICE_OBJECT * wdm_device)
{
NTSTATUS status = STATUS_SUCCESS;
FWPS_CALLOUT s_callout = { 0 };
FWPM_CALLOUT m_callout = { 0 };
FWPM_DISPLAY_DATA display_data = { 0 };
if (filter_engine_handle == NULL)
return STATUS_INVALID_HANDLE;
display_data.name = EXAMPLE_CALLOUT_NAME;
display_data.description = EXAMPLE_CALLOUT_DESCRIPTION;
// Register a new Callout with the Filter Engine using the provided callout functions
s_callout.calloutKey = EXAMPLE_CALLOUT_GUID;
s_callout.classifyFn = example_classify;
s_callout.notifyFn = example_notify;
s_callout.flowDeleteFn = example_flow_delete;
status = FwpsCalloutRegister((void *)wdm_device, &s_callout, &example_callout_id);
if (!NT_SUCCESS(status)) {
DbgPrint("Failed to register callout functions for example callout, status 0x%08x", status);
goto Exit;
}
// Setup a FWPM_CALLOUT structure to store/track the state associated with the FWPS_CALLOUT
m_callout.calloutKey = EXAMPLE_CALLOUT_GUID;
m_callout.displayData = display_data;
m_callout.applicableLayer = FWPM_LAYER_OUTBOUND_TRANSPORT_V4;
m_callout.flags = 0;
status = FwpmCalloutAdd(filter_engine_handle, &m_callout, NULL, NULL);
if (!NT_SUCCESS(status)) {
DbgPrint("Failed to register example callout, status 0x%08x", status);
}
else {
DbgPrint("Example Callout Registered");
}
Exit:
return status;
}
Here is the callout function:
/*************************
ClassifyFn Function
**************************/
void example_classify(
const FWPS_INCOMING_VALUES * inFixedValues,
const FWPS_INCOMING_METADATA_VALUES * inMetaValues,
void * layerData,
const void * classifyContext,
const FWPS_FILTER * filter,
UINT64 flowContext,
FWPS_CLASSIFY_OUT * classifyOut)
{
UNREFERENCED_PARAMETER(layerData);
UNREFERENCED_PARAMETER(classifyContext);
UNREFERENCED_PARAMETER(flowContext);
UNREFERENCED_PARAMETER(filter);
UNREFERENCED_PARAMETER(inMetaValues);
NETWORK_ACCESS_QUERY AccessQuery;
BOOLEAN SafeToOpen = TRUE;
classifyOut->actionType = FWP_ACTION_PERMIT;
AccessQuery.remote_address = inFixedValues->incomingValue[FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_REMOTE_ADDRESS].value.uint32;
AccessQuery.remote_port = inFixedValues->incomingValue[FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_REMOTE_PORT].value.uint16;
// Get Process ID
AccessQuery.ProcessId = (UINT64)PsGetCurrentProcessId();
if (!AccessQuery.ProcessId)
{
return;
}
// Here we connect to our userlevel application using FltSendMessage.
// Some checks are done and the SafeToOpen variable is populated with a BOOLEAN which indicates if to allow or block the packet.
// However, sometimes, a BSOD is generated with an INVALID_PROCESS_ATTACH_ATTEMPT error on the FltSendMessage call
QueryUserLevel(QUERY_NETWORK, &AccessQuery, sizeof(NETWORK_ACCESS_QUERY), &SafeToOpen, NULL, 0);
if (!SafeToOpen) {
classifyOut->actionType = FWP_ACTION_BLOCK;
}
return;
}
WFP drivers communicate to user-mode applications using the inverted call model. In this method, you keep an IRP from the user-mode pending at your kernel-mode driver instance and whenever you want to send data back to the user-mode you complete the IRP along with the data you want to send back.
The problem was that sometimes the ClassifyFn callback function can be called at IRQL DISPATCH_LEVEL.
FltSendMessage does not support DISPATCH_LEVEL, as it can only be run at IRQL <= APC_LEVEL.
Running at DISPATCH_LEVEL can cause this function to generate a BSOD.
I solved the problem by invoking FltSendMessage from a worker thread which runs at IRQL PASSIVE_LEVEL.
The worker thread can be created using IoQueueWorkItem.

Does WriteFile signal the event if it completes synchronously

Does the WriteFile function signal the event passed in via the lpOverlapped parameter if it completes synchronously and succeeds? Does it signal the event if it fails synchronously? I have opened the handle to a file with the FILE_FLAG_OVERLAPPED flag. I wasn't to able to figure this out from the documentation and couldn't repro this case easily in code.
first of all this question related not only to WriteFile but to any asynchronous I/O function - almost all functions which get pointer to an OVERLAPPED structure. because for all this functions IRP (I/O request packet) (look it definition in wdm.h) is allocated. hEvent handle from OVERLAPPED converted to object pointer and stored in PKEVENT UserEvent; member of IRP. the event is set (or not set) exactly when IRP is completed in IopCompleteRequest routine. the IRP completion function is common for all I/O api, so and rules (when completion fire) is related to all. unfortunately this is very bad documented. the win32 layer (compare NT layer) added additional ,very thin, issues here.
based on wrk src code, we can see that I/O Manager fire completion (was 3 types - event, apc and iocp (mutually exclusive)) for asynchronous io when
!NT_ERROR( irp->IoStatus.Status ) or irp->PendingReturned.
if we use native api, which direct return NTSTATUS - when (ULONG)status < 0xc0000000. but here was very problematic range 0x80000000 <= status < 0xc0000000 or NT_WARNING(status) when unclear - are completion (even set, apc or packet to iocp queue) will be set. this is because before allocate IRP I/O Manager do some basic checks and can return error from here. usually I/O Manager return errors from NT_ERROR(status) , which correct mean that will be no completion (event will be not set)), but exist and rarely exceptions. for example for ReadDirectoryChangesW (or ZwNotifyChangeDirectoryFile) the lpBuffer pointer must be DWORD-aligned (aligned exactly as FILE_NOTIFY_INFORMATION) otherwise I/O Manager return STATUS_DATATYPE_MISALIGNMENT (0x80000002) from NT_WARNING range. but will be no completion (event set) in this case, because function fail before allocate IRP. from another case, if we call FSCTL_FILESYSTEM_GET_STATISTICS with not large enough buffer - file system driver (not I/O Manager ) return STATUS_BUFFER_OVERFLOW (0x80000005). but because at this point IRP already allocated and code not from NT_ERROR range - will be event set.
so if error from I/O Manager (before IRP allocated) - will be no completion. otherwise if error from driver (to which passed IRP) completion will be if function return !NT_ERROR(status). as result if we get:
NT_SUCCESS(status) (the STATUS_PENDING (0x103) is part of this) - will
be
completion
NT_ERROR(status) will be no completion
NT_WARNING(status) - unclear, depend this error from I/O Manager
(no) or driver(yes)
but with win32 layer make situation more worse. because unclear how it interpret NT_WARNING(status) - most win32 api interpret this as error - return false and set last error (converted from status). but some api - for example ReadDirectoryChangesW interpret this as success code - return true and not set last error. as result if we call ReadDirectoryChangesW with bad aligned buffer (but valid other parameters) - it return.. true and not set any error. but api call is really fail. the ZwNotifyChangeDirectoryFile internal return STATUS_DATATYPE_MISALIGNMENT here.
from another side, if DeviceIoControl for FSCTL_FILESYSTEM_GET_STATISTICS fail (return false) with code ERROR_MORE_DATA (converted from STATUS_BUFFER_OVERFLOW) event(completion) will be set in this case.
also by win32 error code we can not understand - are initial status was NT_ERROR or NT_WARNING code - conversion (RtlNtStatusToDosError) status to win32 error lost this info
problem with NT_WARNING(status) range, begin from vista, can be resolved if we use IOCP completion (instead event) and set FILE_SKIP_COMPLETION_PORT_ON_SUCCESS on file - in this case I/O manager queue a completion entry to the port, when and only when STATUS_PENDING will be returned by native api call. for win32 layer this usually mean that api return false and last error is ERROR_IO_PENDING. exceptions - WriteFileEx, ReadFileEx which return true here. however this not help in case ReadDirectoryChangesW anyway (I assume that this is windows bug)
also read FILE_SKIP_SET_EVENT_ON_HANDLE section - this implicitly say when explicit event (from overlapped) set in case asynchronous function - when request returns with a success code, or the error returned is ERROR_IO_PENDING. but here question - what is success code ? true returned by win32 api ? not always, as visible from FSCTL_FILESYSTEM_GET_STATISTICS - the ERROR_MORE_DATA (STATUS_BUFFER_OVERFLOW) also success code. or STATUS_NO_MORE_FILES returned by NtQueryDirectoryFile also success code - event (apc or iocp completion) will be set. but same NtQueryDirectoryFile can return STATUS_DATATYPE_MISALIGNMENT, when FileInformation bad aligned - this is fail code, because returned from I/O Manager before allocate IRP
the NT_WARNING status in most case is success code (will be completion), but win32 layer in most case interpret it as fail code (return false).
code example for tests:
ULONG BOOL_TO_ERROR(BOOL fOk)
{
return fOk ? NOERROR : GetLastError();
}
void CheckEventState(HANDLE hEvent, ULONG err, NTSTATUS status = RtlGetLastNtStatus())
{
DbgPrint("error = %u(%x)", err, err ? status : STATUS_SUCCESS);
switch (WaitForSingleObject(hEvent, 0))
{
case WAIT_OBJECT_0:
DbgPrint("Signaled\n");
break;
case WAIT_TIMEOUT:
DbgPrint("NON signaled\n");
break;
default:
DbgPrint("error=%u\n", GetLastError());
}
#if 1
EVENT_BASIC_INFORMATION ebi;
if (0 <= ZwQueryEvent(hEvent, EventBasicInformation, &ebi, sizeof(ebi), 0))
{
DbgPrint("EventState = %x\n", ebi.EventState);
}
#endif
}
void demoIoEvent()
{
WCHAR sz[MAX_PATH];
GetSystemDirectoryW(sz, RTL_NUMBER_OF(sz));
HANDLE hFile = CreateFileW(sz, 0, FILE_SHARE_VALID_FLAGS, 0,
OPEN_EXISTING, FILE_FLAG_OVERLAPPED|FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
FILESYSTEM_STATISTICS fs;
OVERLAPPED ov = {};
if (ov.hEvent = CreateEvent(0, TRUE, FALSE, 0))
{
FILE_NOTIFY_INFORMATION fni;
IO_STATUS_BLOCK iosb;
// STATUS_DATATYPE_MISALIGNMENT from I/O manager
// event will be not set
NTSTATUS status = ZwNotifyChangeDirectoryFile(hFile, ov.hEvent, 0, 0, &iosb,
(FILE_NOTIFY_INFORMATION*)(1 + (PBYTE)&fni), 1, FILE_NOTIFY_VALID_MASK, FALSE);
CheckEventState(ov.hEvent, ERROR_NOACCESS, status);
// windows bug ! ReadDirectoryChangesW return .. true and no last error
// but really api fail. event will be not set and no notifications
ULONG err = BOOL_TO_ERROR(ReadDirectoryChangesW(hFile,
(FILE_NOTIFY_INFORMATION*)(1 + (PBYTE)&fni), 1, 0, FILE_NOTIFY_VALID_MASK, 0, &ov, 0));
CheckEventState(ov.hEvent, err);
// fail with ERROR_INSUFFICIENT_BUFFER (STATUS_BUFFER_TOO_SMALL)
// NT_ERROR(c0000023) - event will be not set
err = BOOL_TO_ERROR(DeviceIoControl(hFile,
FSCTL_FILESYSTEM_GET_STATISTICS, 0, 0, 0, 0, 0, &ov));
CheckEventState(ov.hEvent, err);
// ERROR_MORE_DATA (STATUS_BUFFER_OVERFLOW)
// !NT_ERROR(80000005) - event will be set
// note - win 32 api return false and error != ERROR_IO_PENDING
err = BOOL_TO_ERROR(DeviceIoControl(hFile,
FSCTL_FILESYSTEM_GET_STATISTICS, 0, 0, &fs, sizeof(fs), 0, &ov));
CheckEventState(ov.hEvent, err);
if (err == ERROR_MORE_DATA)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
ULONG cb = si.dwNumberOfProcessors * fs.SizeOfCompleteStructure;
union {
PVOID pv;
PBYTE pb;
PFILESYSTEM_STATISTICS pfs;
};
pv = alloca(cb);
// must be NOERROR(0) here
// !NT_ERROR(0) - event will be set
err = BOOL_TO_ERROR(DeviceIoControl(hFile, FSCTL_FILESYSTEM_GET_STATISTICS, 0, 0,
pv, cb, 0, &ov));
CheckEventState(ov.hEvent, err);
if (!err && GetOverlappedResult(hFile, &ov, &cb, FALSE))
{
do
{
// use pfs here
} while (pb += fs.SizeOfCompleteStructure, --si.dwNumberOfProcessors);
}
}
CloseHandle(ov.hEvent);
}
CloseHandle(hFile);
}
}
and output:
error = 998(80000002)NON signaled
EventState = 0
error = 0(0)NON signaled
EventState = 0
error = 122(c0000023)NON signaled
EventState = 0
error = 234(80000005)Signaled
EventState = 1
error = 0(0)Signaled
EventState = 1

How to synch the following scenario?

I've tried several ways to synch the following scenario. The last way I tried is to use an event to signal after FifoQueueData();. Thread 2 would then release from the event and send the data over the network. The problem is that thread 1 loops around too fast and signals the event before Thread 2 waits on that same event. This causes the event to wait until it times out.
thread1 {
for(1 .. 10) {
GenerateData();
FifoQueueData();
}
signal();
}
thread2 {
while(not signalled) {
WaitForQueuedData();
FifoDequeueData();
SendDataOverNetwork();
}
}
Because you are on Windows, you could indeed use the thread message queue for synchronization (this particular queue is threadsafe, maybe message queues are what Olaf means by "normally"). To do that, use GetMessage and PostThreadMessage.
Be aware, however, that message queues are accessible from other code -- for type safety, you might put objects in your own queue, and use the thread message queue only for waking the other thread. (To see why this is important, read about "window shatter attacks"). In that case, you might as well just be using auto-reset events.
The last way I tried is to use an event to signal after FifoQueueData();. Thread 2 would then release from the event and send the data over the network. The problem is that thread 1 loops around too fast and signals the event before Thread 2 waits on that same event. This causes the event to wait until it times out.
If you are going to use events, use 2 manual-reset events and a thread-safe lock, like a critical section. One event signals when the queue has space available, and one event signals when the queue has data. Thread1 would wait for the Available event to be signaled, lock the queue, put data into it, set the Data event, and reset the Available event if the queue is full, before finally unlocking the queue. Thread2 would wait for the Data event, lock the queue, dequeue an item, set the Available event, and reset the Data event if the queue is empty, before finally unlocking the queue and processing the item.
CRITICAL_SECTION csQueue;
HANDLE hAvailableEvent, hDataEvent;
BOOL bFinished;
...
InitializeCriticalSection(&csQueue);
hAvailableEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
hDataEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
bFinished = FALSE;
...
void FifoQueueData(BOOL IsFinished)
{
WaitForSingleObject(hAvailableEvent, INFINITE);
EnterCriticalSection(&csQueuelock);
if (IsFinished)
bFinished = TRUE;
else
{
// put data in the queue
if (queue is full)
ResetEvent(hAvailableEvent);
}
SetEvent(hDataEvent);
LeaveCriticalSection(&csQueuelock);
}
BOOL FifoDequeueData()
{
WaitForSingleObject(hDataEvent, INFINITE);
EnterCriticalSection(&csQueuelock);
if ((queue is empty) && (bFinished))
{
LeaveCriticalSection(&csQueuelock);
return FALSE;
}
// remove data from the queue
SetEvent(hAvailableEvent);
if (queue is empty)
ResetEvent(hDataEvent);
LeaveCriticalSection(&csQueuelock);
}
thread1
{
bFinished = FALSE;
for(1 .. 10)
{
GenerateData();
FifoQueueData(FALSE);
}
FifoQueueData(TRUE);
}
thread2
{
while (FifoDequeueData())
SendDataOverNetwork();
}
Another option is to use an I/O Completion Port. Call CreateIoCompletionPort() before starting the threads, then have Thread1 queue data using PostQueuedCompletionStatus() and Thread2 dequeue data using GetQueuedCompletionStatus(). IOCP uses a FIFO queue and is self-syncing.
HANDLE hQueue;
...
hQueue = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
...
void FifoQueueData(BOOL IsFinished)
{
if (IsFinished)
PostQueuedCompletionStatus(hQueue, 0, 1, NULL);
else
{
LPBYTE buffer = LocalAlloc(LMEM_FIXED, size of data);
if (buffer)
{
// copy data into buffer...
if (!PostQueuedCompletionStatus(hQueue, size of data, 0, (LPOVERLAPPED)buffer))
LocalFree(buffer);
}
}
}
BOOL FifoDequeueData()
{
DWORD dwSize = 0;
ULONG_PTR ulCompletionKey = 0;
LPBYTE buffer;
if (!GetQueuedCompletionStatus(hQueue, &dwSize, &ulCompletionKey, (LPOVERLAPPED*)&buffer, INFINITE))
return FALSE;
if (ulCompletionKey == 1)
return FALSE;
// copy data from buffer up to dwSize bytes as needed...
LocalFree(buffer);
return TRUE;
}
thread1
{
for(1 .. 10)
{
GenerateData();
FifoQueueData(FALSE);
}
FifoQueueData(TRUE);
}
thread2
{
while (FifoDequeueData())
SendDataOverNetwork();
}

How to start multiple threads when an event occurs?

I am creating a service, which is waiting for a Logon event. When this event occurs, the service should start a thread, which will do the rest of the work.
In the service:
while ( WaitForSingleObject( ghSvcStopEvent, 0 ) != WAIT_OBJECT_0 )
{
DWORD dwEventFlags;
BOOL bRes;
// WTSWaitSystemEvent waits until a logon event ocurs
bRes = WTSWaitSystemEvent( WTS_CURRENT_SERVER_HANDLE, WTS_EVENT_LOGON, &dwEventFlags );
if ( dwEventFlags == WTS_EVENT_NONE )
{
ShowErrorText( "Cancelling waiting for logon event. Service shutting down.", 0, true );
}
if ( bRes )
{
// Someone has logged on
HANDLE hThread = CreateThread( NULL, 0, ServiceWorkerThread, NULL, 0, &dwThreadID );
}
else
{
ShowErrorText( "WTSWaitSystemEvent failed.", GetLastError(), true );
}
}//while
My question is, how can I close correctly the handles returned by CreateThread? If I want to use WaitForMultipleObjects then the first parameter is the size of the threads. The maximum number of object handles is MAXIMUM_WAIT_OBJECTS, which is 36. So this means that I can only start 36 threads. But if the service is running a very long time, then it won't be enough, if a user is logging on more than 36 times, without restarting the system.
Use a thread pool to avoid needing to destroy threads. When your application exits you do not need to close the handles - Windows will do that for you.

Gracefully closing a windowless app in WinAPI

I'm writing a little utility app to control the system master volume via hotkeys for my GF, whose laptop is for some reason deprived of such function keys. I whipped up the code pretty much instantly and I've got the main functionality working perfectly; however, since I'm not creating any windows (just a message loop handling the WM_HOTKEY message), I can't terminate the app in a more elegant manner than just gracelessly terminating the process (also, when the system is shutting down, it shows the "should I wait for the process to end or kill it now" window with some rubbish in the place where the window title usually is).
Is there any way of doing this which doesn't involve creating a fake window just for the sake of intercepting WM_CLOSE messages?
Here's the code (I left out the mixer control functions intentionally, they're irrelevant to the question):
int WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) {
MSG msg;
int step;
MixerInfo_t mi;
HANDLE mutex;
mutex = CreateMutex(NULL, TRUE, "volhotkey");
if (mutex == NULL)
return 1;
if (GetLastError() == ERROR_ALREADY_EXISTS)
return 0;
RegisterHotKey(NULL, 1, MOD_ALT | MOD_CONTROL, VK_F5);
RegisterHotKey(NULL, 2, MOD_ALT | MOD_CONTROL, VK_F6);
RegisterHotKey(NULL, 3, MOD_ALT | MOD_CONTROL, VK_F7);
mi = GetMixerControls();
step = (mi.maxVolume - mi.minVolume) / 20;
while (GetMessage(&msg, NULL, 0, 0)) {
switch (msg.message) {
case WM_HOTKEY:
switch (msg.wParam) {
case 1:
AdjustVolume(&mi, -step);
break;
case 2:
AdjustVolume(&mi, step);
break;
case 3:
SetMute(&mi, !IsMuted(&mi));
break;
}
MessageBeep(MB_ICONASTERISK);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
break;
}
}
UnregisterHotKey(NULL, 1);
UnregisterHotKey(NULL, 2);
return msg.wParam;
}
Thanks in advance!
Oh, and for the record, WM_DESTROY is also never posted.
You could use the SetConsoleCtrlHandler() function to listen for the shutdown event.
SetConsoleCtrlHandler( ShutdownHandler, TRUE );
You handler would look something like this:
BOOL WINAPI ShutdownHandler( DWORD dwCtrlType )
{
if( dwCtrlType == CTRL_SHUTDOWN_EVENT || dwCtrlType == CTRL_LOGOFF_EVENT )
{
ExitProcess( 0 );
return TRUE; // just to keep the compiler happy
}
return FALSE;
}
Despite the name, SetConsoleCtrlHandler() works regardless of whether or not the application is a console application.
Have a look at the ExitProcess API call for shutting down a process gracefully. To detect a Windows shutdown, include WM_ENDSESSION in your message handling. If your application is more complex than what is posted, you may also want to review the ExitThread function.
You didn't create any window, not even a hidden one, so there's no way to get the loop to exit by sending a message to a window. Also the reason that WM_DESTROY never fires.
All that's left is PostThreadMessage() to post WM_QUIT. You'd have to be able to find the thread ID somehow. Using Shell_NotifyIcon() would be wise.
You could always show something in the system tray easily enough from which you could elegantly close it. And as your application grows, that may be desirable, because the user may eventually want to be able to configure or change the hotkeys, or temporarily turn them off it they interfere with another application etc.
Or have another hotkey which shows a small window with configuration options?

Resources