用 Python 为 Windows 优雅地杀死 Chrome

Gracefully kill Chrome for Windows with Python

我一直在尝试确定一种方法来优雅地利用 Python 关闭 Chrome(和其他进程)。我已经使用了几种方法,但所有方法都会导致 Chrome 显示一条消息,说明 "Chrome didn't shut down correctly." 并带有“恢复”按钮。在 Windows 中从任务管理器关闭程序时不会发生这种情况。我编写了一个程序,利用 Falcon 提供 API 关闭 'managed' 程序的权限。该代码不相关,但这里是我尝试过的方法,它们确实可以关闭 Chrome,效果很好,但不是很好。

def stop_app(app_exe):
    for process in psutil.process_iter():
        if process.name() == app_exe:
            process.terminate()

备用:

for process in psutil.process_iter():
    if process.name() == app_exe:
        children = process.children(recursive=True)
        children.append(process)
        for p in children:
            p.send_signal(signal.CTRL_BREAK_EVENT)

我还尝试用 kill() 代替 terminate(),各种信号类型(Windows 实际上只支持三种,CTRL_BREAK_EVENT、CNTL_C_EVENT、SIGTERM)。

我也尝试过使用 win32 库,但遇到了同样的问题。 Win32 也不是平台无关的,我发现 win32/wmi 库在查找 运行 windows 进程时会占用大量系统资源。 psutil 库允许与平台无关,并且在遍历 Windows 进程时不会出现相同的性能问题。这是它的代码。

c = wmi.WMI()
for process in c.Win32_Process():
    if process.Name == app_exe:
        process.Terminate()

有没有人尝试过类似的东西并提出解决方案?

根据 CristiFati 的建议快速解决。:

def enumWindowsProc(hwnd, lParam):
    if (lParam is None) or ((lParam is not None) and 
          win32process.GetWindowThreadProcessId(hwnd)[1] == pid)):
        text = win32gui.GetWindowText(hwnd)
        if text:
            wStyle = win32api.GetWindowLong(hwnd, win32con.GWL_STYLE)
            if wStyle & win32con.WS_VISIBLE:
                win32api.SendMessage(hwnd, win32con.WM_CLOSE)


def stop_app(app_exe):
    for process in psutil.process_iter():
        if process.name() == app_exe:
            win32gui.EnumWindows(enumWindowsProc, process.pid)

显然,Chrome 在被进程终止关闭时并不开心。它有一种检测这种确切情况的机制。
它有一堆进程运行,我认为这可能是一种优雅地关闭它的方式(我可能是错的,但我记得一旦我设法做到了),通过仔细选择进程被终止的顺序(crashpad-handlerutilityrenderers,之前master 一个),但现在我做不到,我也不知道那到底有多优雅。

注意:我个人认为这个行为非常有用,因为我已经打开了很多Chromewindows每个都有很多选项卡,每次我需要重新启动笔记本电脑时,我都会从 Task Manager 中关闭 Chrome,以便下次我打开它会恢复当前状态。

回到我们的问题,一种方法是获取它的 window 并发送给它 [MS.Docs]: WM_CLOSE message (also check [MS.Docs]: Closing the Window)。请注意,它是 Win 特定的!

  • 您已经拥有获取具有特定名称的进程 (pids) 的代码
  • [SO]: 使用进程名获取另一个程序的window的标题 (@CristiFati 的回答) 包含从进程 (ids) 获取 windows(标题)的代码。实际上它包含更多,但这就是你需要的(enumWindowsProcenumProcWnds 函数)
  • 关闭window的代码:

    win32api.SendMessage(0x005C0974, win32con.WM_CLOSE)  # 0x005C0974 was Chrome's (main) window handle in my test
    
  • 您需要做的就是将以上 3 项结合起来(稍作改动)

与 windows 一起使用的另一个示例:[SO]: Keyboard event not sent to window with pywin32 (@CristiFati's answer)

一个更通用的版本,它向应用程序发送关闭消息,包括那些只在系统托盘中可见的应用程序,包括所有需要的导入:

import win32con
import win32api
import win32gui
import win32process
import psutil

def enumWindowsProc(hwnd, lParam):
    if (lParam is None) or ((lParam is not None) and win32process.GetWindowThreadProcessId(hwnd)[1] == lParam):
        text = win32gui.GetWindowText(hwnd)
        if text:
            win32api.SendMessage(hwnd, win32con.WM_CLOSE)

def stop_app(app_exe):
    for process in psutil.process_iter():
        if process.name() == app_exe:
            win32gui.EnumWindows(enumWindowsProc, process.pid)

stop_app("appname.exe")

(基于问题中的 'Quick solution per CristiFati recommendation',但将 'pid' 更改为 'lParam',包括所需的导入,并且还适用于仅在系统托盘中可见的应用程序。)