Search code examples
cwinapivisual-studio-2015worker-threadwtsapi32

WTSRegisterSessionNotification causing process to hang


I followed the "A Simple Window" tutorial from the WinProg website.

When the code from the tutorial is compiled without C runtime library, everything seems to work as intended. The window is created and visibly shown to the user.

If I close the window by pressing the close button, the window is destroyed and the process is exited–the process no longer runs on the local machine.

However, when I link to the WTS library and add a call to the WTSRegisterSessionNotification function, the process continues to run on the local machine after closing its corresponding window.

This behavior only seems to occur when returning from WinMain after the call to WTSRegisterSessionNotification is made.

My guess would be that WTSRegisterSessionNotification creates some kind of worker thread which will never be notified to exit. Returning from WinMain doesn't seem to result in an ExitProcess call, likely because the code is compiled without C runtime library.

The problem could be avoided by calling ExitProcess before returning from WinMain. But this doesn't feel like the proper way to deal with the situation.

My question is: is there a WTS API function that I'm overlooking that could/should be called before returning from WinMain in order to make sure the process will exit?

Code Sample:

#include <windows.h>
#include <wtsapi32.h>

const char g_szClassName[] = "myWindowClass";

// Step 4: the Window Procedure
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch (msg)
    {
    case WM_WTSSESSION_CHANGE:
        if (wParam == WTS_SESSION_LOCK)
            OutputDebugString( "current session got locked" );
        else if (wParam == WTS_SESSION_UNLOCK)
            OutputDebugString( "current session got unlocked" );
        break;
    case WM_CLOSE:
        DestroyWindow( hwnd );
        break;
    case WM_DESTROY:
        WTSUnRegisterSessionNotification( hwnd );
        PostQuitMessage( 0 );
        break;
    default:
        return DefWindowProc( hwnd, msg, wParam, lParam );
    }
    return 0;
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow )
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    //Step 1: Registering the Window Class
    wc.cbSize = sizeof( WNDCLASSEX );
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm = LoadIcon( NULL, IDI_APPLICATION );

    if (!RegisterClassEx( &wc ))
    {
        MessageBox( NULL, "Window Registration Failed!", "Error!",
                    MB_ICONEXCLAMATION | MB_OK );
        return 0;
    }

    // Step 2: Creating the Window
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL );

    if (hwnd == NULL)
    {
        MessageBox( NULL, "Window Creation Failed!", "Error!",
                    MB_ICONEXCLAMATION | MB_OK );
        return 0;
    }

    if (!WTSRegisterSessionNotification( hwnd, NOTIFY_FOR_THIS_SESSION ))
    {
        MessageBox( NULL, "Register Session Notification Failed!", "Error!",
                    MB_ICONEXCLAMATION | MB_OK );
        return 0;
    }

    ShowWindow( hwnd, nCmdShow );
    UpdateWindow( hwnd );

    // Step 3: The Message Loop
    while (GetMessage( &Msg, NULL, 0, 0 ) > 0)
    {
        TranslateMessage( &Msg );
        DispatchMessage( &Msg );
    }
    return Msg.wParam;
}

Compiler Command Line:

/GS- /TC /GL /analyze- /W4 /Gy /Zc:wchar_t /Gm- /O1 /Ob2 /Fd"Release\vc100.pdb" /fp:fast /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /errorReport:prompt /WX- /Zc:forScope /GR- /Gd /Oy /Oi /MD /Fa"Release\" /nologo /Zl /Fo"Release\" /Os 

Linker Command Line:

/OUT:"C:\Users\treintje\Documents\Visual Studio 2015\Projects\sample\Release\sample.exe" /MANIFEST:NO /LTCG /NXCOMPAT /PDB:"C:\Users\treintje\Documents\Visual Studio 2015\Projects\sample\Release\sample.pdb" /DYNAMICBASE:NO "kernel32.lib" "user32.lib" "wtsapi32.lib" /ALLOWISOLATION /MACHINE:X86 /ENTRY:"WinMain" /OPT:REF /INCREMENTAL:NO /PGD:"C:\Users\treintje\Documents\Visual Studio 2015\Projects\sample\Release\sample.pgd" /SUBSYSTEM:WINDOWS /MANIFESTUAC:NO /ManifestFile:"Release\sample.exe.intermediate.manifest" /OPT:ICF /ERRORREPORT:PROMPT /NOLOGO /NODEFAULTLIB /TLBID:1

Solution

  • The problem could be avoided by calling ExitProcess before returning from WinMain. But this doesn't feel like the proper way to deal with the situation.

    The process for exit MUST directly or indirectly call ExitProcess. This is the absolute proper way and mandatory. When you use CRT WinMain is not really entry point of your application - it called from WinMainCRTStartup which called ExitProcess. If you not use CRT - you MUST directly and by itself call ExitProcess.

    From Windows 10 (on 1607 build exactly but may be on previous versions) there exists a new feature - "Parallel loader" for dll load. Now, when any dll is loaded in your process (except ntdll, kernel32 and kernelbase) the system creates working threads for loading dlls in "parallel". Even if you run a very simply program - say a single MessageBox in your WinMain - but do not call Exitprocess, your process not exit but will still be alive for 30-60 seconds - dll loader working threads (LdrpWorkCallback) have 30 second idle timeout and after this will exit.