New submission from Eryk Sun <eryk...@gmail.com>:

A console script should be able to handle Windows console logoff and shutdown 
events with relatively simple ctypes code, such as the following:

    import ctypes
    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

    CTRL_C_EVENT = 0
    CTRL_BREAK_EVENT = 1
    CTRL_CLOSE_EVENT = 2
    CTRL_LOGOFF_EVENT = 5
    CTRL_SHUTDOWN_EVENT = 6

    @ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_ulong)
    def console_ctrl_handler(event):
        if event == CTRL_SHUTDOWN_EVENT:
            return handle_shutdown()
        if event == CTRL_LOGOFF_EVENT:
            return handle_logoff()
        if event == CTRL_CLOSE_EVENT:
            return handle_close()
        if event == CTRL_BREAK_EVENT:
            return handle_break()
        if event == CTRL_C_EVENT:
            return handle_cancel()
        return False # chain to next handler

    if not kernel32.SetConsoleCtrlHandler(console_ctrl_handler, True):
        raise ctypes.WinError(ctypes.get_last_error())

As of 3.9, it's not possible for python.exe to receive the above logoff and 
shutdown events via ctypes. In these two cases, the console doesn't even get to 
send a close event, so a console script cannot exit gracefully.

The session server (csrss.exe) doesn't send the logoff and shutdown console 
events to python.exe because it's seen as a GUI process, which is expected to 
handle WM_QUERYENDSESSION and WM_ENDSESSION instead. That requires creating a 
hidden window and running a message loop, which is not nearly as simple as a 
console control handler.

The system registers python.exe as a GUI process because user32.dll is loaded, 
which means the process is ready to interact with the desktop in every way, 
except for the final step of actually creating UI objects. In particular, 
loading user32.dll causes the system to extend the process and its threads with 
additional kernel data structures for use by win32k.sys (e.g. a message queue 
for each thread). It also opens handles for and connects to the session's 
"WinSta0" interactive window station (a container for an atom table, clipboard, 
and desktops) and "Default" desktop (a container for UI objects such as 
windows, menus, and hooks). (The process can connect to a different desktop or 
window station if set in the lpDesktop field of the process startup info. Also, 
if the process access token is for a service or batch logon, by default it 
connects to a non-interactive window station that's named for the logon ID. For 
example, the SYSTEM logon ID is 0x3e7, so a SYSTEM service or batch pro
 cess gets connected to "Service-0x0-3e7$".)

Prior to 3.9, python3x.dll loads shlwapi.dll (the lightweight shell API) to 
access the Windows path functions PathCanonicalizeW and PathCombineW. 
shlwapi.dll in turn loads user32.dll. 3.9+ is one step closer to the non-GUI 
goal because it no longer depends on shlwapi.dll. Instead it always uses the 
newer PathCchCanonicalizeEx and PathCchCombineEx functions from 
api-ms-win-core-path-l1-1-0.dll, which is implemented by the base API 
(kernelbase.dll) instead of the shell API. 

The next hurdle is extension modules, especially the _ctypes extension module, 
since it's needed for the console control handler. _ctypes.pyd loads ole32.dll, 
which in turn loads user32.dll. This is just to call ProgIDFromCLSID, which is 
rarely used. I see no reason that ole32.dll can't be delay loaded or just 
manually link to ProgIDFromCLSID on first use via GetModuleHandleW / 
LoadLibraryExW and GetProcAddress. I did a quick patch to implement the latter, 
and, since user32.dll no longer gets loaded, the console control handler is 
enabled for console logoff and shutdown events. So this is the minimal fix to 
resolve this issue in 3.9+.

--- 
Additional modules

winsound loads user32.dll for MessageBeep. The Beep and PlaySound functions 
don't require user32.dll, so winsound is still useful if it gets delay loaded. 

_ssl and _hashlib depend on libcrypto, which loads user32.dll for MessageBoxW, 
GetProcessWindowStation and GetUserObjectInformationW. The latter two are 
called in OPENSSL_isservice [1] in order to get the window station name. If 
StandardError isn't a valid file handle, OPENSSL_isservice determines whether 
an error should be reported as an event or interactively shown with a message 
box. user32.dll can be delay loaded for this, which, if I'm reading the source 
right, will never occur as long as StandardError is a valid file.
 
[1]: 
https://github.com/openssl/openssl/blob/e7fb44e7c3f7a37ff83a6b69ba51a738e549bf5c/crypto/cryptlib.c#L193

----------
components: Extension Modules, Library (Lib), Windows, ctypes
messages: 373659
nosy: eryksun, paul.moore, steve.dower, tim.golden, zach.ware
priority: normal
severity: normal
stage: needs patch
status: open
title: Enable handling logoff and shutdown Windows console events
type: enhancement
versions: Python 3.10, Python 3.9

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue41298>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to