Gracefully closing a windowless app in WinAPI - c

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?

Related

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

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

sending messages to the main window in a thread in win32

I am relatively new to working with threads in Win32 api and have reached a problem that i am unable to work out.
Heres my problem, i have 4 threads (they work as intended) that allow the operator to test 4 terminals. In each thread i am trying to send a message to the main windows form with either Pass or Fail, this is placed within a listbox. Below is one of the threads, the remaining are exactly the same.
void Thread1(PVOID pvoid)
{
for(int i=0;i<numberOfTests1;i++) {
int ret;
double TimeOut = 60.0;
int Lng = 1;
test1[i].testNumber = getTestNumber(test1[i].testName);
unsigned char Param[255] = {0};
unsigned char Port1 = port1;
ret = PSB30_Open(Port1, 16);
ret = PSB30_SendOrder (Port1, test1[i].testNumber, &Param[0], &Lng, &TimeOut);
ret = PSB30_Close (Port1);
if(*Param == 1) SendDlgItemMessage(hWnd,IDT_RESULTLIST1,LB_ADDSTRING,i,(LPARAM)"PASS");
else SendDlgItemMessage(hWnd,IDT_RESULTLIST1,LB_ADDSTRING,i,(LPARAM)"FAIL");
}
_endthread();
}
I have debugged the code and it does everything except populate the listbox, i assume because its a thread i am missing something as the same code works outwith the thread. Do i need to put the thread to sleep while it sends the message to the main window?
Any help is appreciated.
Cheers
You don't want your secondary threads trying to manipulate your UI elements directly (such as the SendDlgItemMessage). Instead, you normally want to post something like a WM_COMMAND or WM_USER+N to the main window, and let that manipulate the UI elements accordingly.

Switch/case without break inside DllMain

I have a Dllmain that allocates Thread local storage when a thread attaches to this DLL. Code as below:
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
LPVOID lpvData;
BOOL fIgnore;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
onProcessAttachDLL();
// Allocate a TLS index.
if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
return FALSE;
// how can it jump to next case???
case DLL_THREAD_ATTACH:
// Initialize the TLS index for this thread.
lpvData = (LPVOID) LocalAlloc(LPTR, MAX_BUFFER_SIZE);
if (lpvData != NULL)
fIgnore = TlsSetValue(dwTlsIndex, lpvData);
break;
...
}
I know that for the main thread, the DLL_THREAD_ATTACH is not entered, as per Microsoft Documentation. However, the above code worked. I am using VC2005. When I entered the debugger, I saw that after it entered DLL_THREAD_ATTACH case when ul_reason_for_call = 1! How can that happen? If I add `break' at the end of DLL_PROCESS_ATTACH block, the DLL failed to work.
How can this happen?
If I understand you correctly, you are wondering why, after entering the DLL_PROCESS_ATTACH case, the execution continues on the DLL_THREAD_ATTACH case, instead of after the end of the switch.
This behaviour is called "fall through", and it is standard C. Without an explicit break statement, the execution continues on the next case.
Of course, it is quite counterintuitive for programmers who see it the first time, so it is a constant source of misunderstanding and even bugs (you may not always know whether the break was left out intentionally, or by mistake). It is considered good practice, therefore, when using this construct, to mark it explicitly with a comment, like:
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
onProcessAttachDLL();
// Allocate a TLS index.
if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
return FALSE;
// fall through
case DLL_THREAD_ATTACH:
// Initialize the TLS index for this thread.
lpvData = (LPVOID) LocalAlloc(LPTR, MAX_BUFFER_SIZE);
if (lpvData != NULL)
fIgnore = TlsSetValue(dwTlsIndex, lpvData);
break;
...
Do you understand how switch statements work? if you do NOT put a break at the end of the case, then the code just continues into the next case:
switch (3)
{
case 3:
cout << "3";
case 4:
cout << "4";
}
prints both 3 and 4.

Implementing progress control using threading

I am new to the concept of threading in C, so I find it difficult to implement that
in my function. I have a simple application in which I want to display a progress bar at a
particular place. In a particular function I will read files(in a for loop) for some manipulations(regarding my application). While it's reading the files I want to display a progress bar, stating that it's in process of reading files. I know it should be done using the concept of threading, but I am not quite sure how to do it.
Create a worker thread in the main program and set the callback routine that does the file processing.
That routine also will calculate the percentage that is completed. Whenever that percent changes, post the
value as a window message which the main thread will catch and update the progress bar control.
You can define application inner messages like #define MSG_PROGRESS_VALUE (WM_USER + 1).
Edit: sample,
#define MSG_PROGRESS_VALUE (WM_USER + 1)
#define MSG_WORKER_DONE (WM_USER + 2)
...
DWORD WINAPI jobroutine(LPVOID lpParameter) {
while (TRUE) {
// process files ...
// calculate new percent
if (newpercent != oldpercent) {
PostMessage(mainwnd, MSG_PROGRESS_VALUE, 0, newpercent);
oldpercent = newpercent;
}
...
}
PostMessage(mainwnd, MSG_WORKER_DONE, 0, 0);
return 0;
}
...
MainWndProc(...) {
switch (uMsg) {
...
case MSG_PROGRESS_VALUE:
// update progress bar value (lParam)
break;
...
}
...
WinMain(...) {
HANDLE worker = CreateThread(NULL, 0, jobroutine, NULL, NULL, NULL);
...
// Start classic windows message loop
...
}

CreateDesktop() with vista and UAC on (C, windows)

I asked this in CreateDesktop() with Vista UAC (C Windows)
I set a bounty but in trying to vote down the only answer the "accept" was pressed by mistake (i've been awake for more than 48 hs). so I am asking it again.
I'm using CreateDesktop() to create a temporary desktop where an application will run, perform a cleanup action (while remaining out of the way) and terminate. I'm closing that desktop once the application is gone. Everything is fine when using Windows XP and even Vista. The problem arises when you enable the (annoying) UAC.
Everything is OK when you create a desktop, but when you call CreateProcess() to open a program on that desktop it causes the opened application to crash with an exception on User32.dll.
I've been reading a lot about the different desktops and layers on Windows and the restrictions of memory. However, most of the programs I open (as test scenarios) are OK, but a few (like IE, Notepad, Calc and my own application) cause the crash.
Anyone has any idea why this happen on Vista with UAC, or more specifically for those specific programs? and how to fix this?
Anyone has a good solid example on how to create a desktop and open an application there without switching to it under Vista with UAC on?
Code is appreciated.
Thanks
The code used is
SECURITY_ATTRIBUTES sa;
HDESK dOld;
HDESK dNew;
BOOL switchdesk, switchdesk2, closedesk;
int AppPid;
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
//Get handle to current desktop
dOld = OpenDesktopA("default", 0, TRUE, DESKTOP_SWITCHDESKTOP|
DESKTOP_WRITEOBJECTS|
DESKTOP_READOBJECTS|
DESKTOP_ENUMERATE|
DESKTOP_CREATEWINDOW|
DESKTOP_CREATEMENU);
if(!dOld)
{
printf("Failed to get current desktop handle !!\n\n");
return 0;
}
//Make a new desktop
dNew = CreateDesktopA("kaka", 0, 0, 0, DESKTOP_SWITCHDESKTOP|
DESKTOP_WRITEOBJECTS|
DESKTOP_READOBJECTS|
DESKTOP_ENUMERATE|
DESKTOP_CREATEWINDOW|
DESKTOP_CREATEMENU, &sa);
if(!dNew)
{
printf("Failed to create new desktop !!\n\n");
return 0;
}
AppPid = PerformOpenApp(SomeAppPath);
if(AppPid == 0)
{
printf("failed to open app, err = %d\n", GetLastError());
}
else
{
printf("App pid = %d\n", AppPid);
}
closedesk = CloseDesktop(dNew);
if(!closedesk)
{
printf("Failed to close new desktop !!\n\n");
return 0;
}
return 0;
The correct solution is given as a short comment by ChristianWimmer above:
The desktop must have a security descriptor that allows access to lower integrity level like IE has. Otherwise the GUI cannot access the desktop. – ChristianWimmer Jul 22 '10 at 17:00
Since the answer is a little bit hidden and there's no source code example, let me state it clearly here:
If IE runs in protected mode then the browser tabs are created as low integrity processes. The low integrity tab process will fail to initialize if the desktop does not have a low integrity mandatory label.
As a consequence, the main IE process terminates, too. An interesting observation is that if you start IE providing a command line URL from the secure zone, then IE will succeed to start, because protected mode is disabled by default for the secure zone.
I checked the integrity level of the default desktop, and indeed I was able to verify that the default desktop has a low integrity level! So the easiest solution to the problem is to (1) create the new desktop, (2) get the mandatory label from the default desktop, and (3) copy it into the new desktop. For (2) and (3), you can use the following code
PACL pSacl;
PSECURITY_DESCRIPTOR pSecurityDescriptor;
DWORD dwResult;
dwResult = GetSecurityInfo(hDefaultDesktop, SE_WINDOW_OBJECT, LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, &pSacl, &pSecurityDescriptor);
if (dwResult == ERROR_SUCCESS) {
if (pSacl != NULL) {
dwResult = SetSecurityInfo(hNewDesktop, SE_WINDOW_OBJECT, LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, pSacl);
if (dwResult != ERROR_SUCCESS)
_tprintf(_T("SetSecurityInfo(hNewDesktop) failed, error = %d"), dwResult);
}
LocalFree(pSecurityDescriptor);
} else {
_tprintf(_T("GetSecurityInfo(hDefaultDesktop) failed, error = %d"), dwResult);
}
#CristianWimmer: Thanks for providing the hint to the correct solution. This saved my a lot of time!!
You appear to have come across a bug in IE as it interacts with UAC. If protected mode is set to on you cannot run IE as an ordinary user in any desktop except the default one. In order to run IE in an alternate desktop you must be running as administrator or have protected mode set to off. This is true for Vista, W2K8 and Win7.
As to the other programs that you cannot run, unfortunately I can't confirm anything. I tried upwards of thirty different programs including notepad, calc, all the office apps, visual studio 2005, 2008 and 2010, MSDN help and a number of others and all worked as expected with the noted exception of IE. Is there something truly unusual about your app that might make it behave in an unexpected manner?
One note - if you attempt to run an application like this that needs elevation (such as regedit, etc.) it will fail in CreateProcess with the last error set to ERROR_ELEVATION_REQUIRED.
For your reference, in case I'm doing something different from you, the code I used is:
#ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows Vista.
#define _WIN32_WINNT 0x0600 // Change this to the appropriate value to target other versions of Windows.
#endif
#include <stdio.h>
#include <tchar.h>
#include "windows.h"
HANDLE PerformOpenApp(TCHAR* appPath);
int _tmain(int argc, _TCHAR* argv[])
{
HDESK dNew;
BOOL closedesk;
HANDLE hApp;
//Make a new desktop
dNew = CreateDesktop(_T("kaka"), 0, 0, 0, DESKTOP_SWITCHDESKTOP|
DESKTOP_WRITEOBJECTS|
DESKTOP_READOBJECTS|
DESKTOP_ENUMERATE|
DESKTOP_CREATEWINDOW|
DESKTOP_CREATEMENU, NULL);
if(!dNew)
{
_tprintf(_T("Failed to create new desktop !!\n\n"));
return 0;
}
TCHAR path[MAX_PATH];
_putts(_T("Enter the path of a program to run in the new desktop:\n"));
_getts(path);
while(_tcslen(path) > 0)
{
hApp = PerformOpenApp(path);
if(hApp == 0)
{
_tprintf(_T("Failed to open app, err = %d\n"), GetLastError());
}
else
{
_tprintf(_T("App pid = %d\n"), GetProcessId(hApp));
_putts(_T("Press any key to close the app.\n"));
_gettchar();
TerminateProcess(hApp, 0);
CloseHandle(hApp);
}
_putts(_T("Enter the path of a program to run in the new desktop:\n"));
_getts(path);
}
closedesk = CloseDesktop(dNew);
if(!closedesk)
{
_tprintf(_T("Failed to close new desktop !!\n\n"));
return 0;
}
return 0;
}
HANDLE PerformOpenApp(TCHAR* appPath)
{
STARTUPINFO si = {0};
PROCESS_INFORMATION pi;
si.cb = sizeof(si);
si.lpDesktop = _T("kaka");
BOOL retVal = CreateProcess(NULL, appPath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL,
NULL, &si, &pi);
if (retVal)
{
CloseHandle(pi.hThread);
}
return pi.hProcess;
}

Resources