WTSRegisterSessionNotification 导致进程挂起

WTSRegisterSessionNotification causing process to hang

我关注了 WinProg 网站上的 "A Simple Window" tutorial

当教程中的代码在没有 C 运行time 库的情况下编译时,一切似乎都按预期工作。 window 已创建并显示给用户。

如果我按关闭按钮关闭 window,window 将被销毁并退出该进程——该进程不再在本地机器上 运行。

然而,当我 link 到 WTS 库并添加对 WTSRegisterSessionNotification 函数的调用时,进程在关闭其对应的 window 后继续在本地机器上 运行。

此行为似乎只在调用 WTSRegisterSessionNotification 后从 WinMain 返回时发生。

我的猜测是 WTSRegisterSessionNotification 创建了某种永远不会被通知退出的工作线程。从 WinMain 返回似乎不会导致 ExitProcess 调用,可能是因为代码是在没有 C 运行time 库的情况下编译的。

在从 WinMain 返回之前调用 ExitProcess 可以避免这个问题。但这似乎不是处理这种情况的正确方法。

我的问题是:是否有一个我忽略的 WTS API 函数 could/should 在从 WinMain 返回之前被调用以确保进程退出?

代码示例:

#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;
}

编译器命令行:

/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 

链接器命令行:

/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

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.

退出 的进程必须 直接或间接调用 ExitProcess。这是绝对正确的方式和强制性的。当您使用 CRT 时,WinMain 并不是您应用程序的真正入口点 - 它从 WinMainCRTStartup 调用,后者调用 ExitProcess。如果你不使用 CRT - 你 必须 直接调用 ExitProcess.

从 Windows10(在 1607 版本上完全正确,但可能在以前的版本上)存在一个新功能 - 用于 dll 加载的“并行加载器”。现在,当您的进程中加载​​任何 dll 时(ntdllkernel32kernelbase 除外),系统会创建工作线程以“并行”加载 dll。即使你 运行 一个非常简单的程序 - 比如你的 WinMain 中的一个 MessageBox - 但不调用 Exitprocess,你的进程不会退出但仍然存在 30-60 秒 - dll 加载程序工作线程(LdrpWorkCallback) 有 30 秒的空闲超时,之后将退出。