优雅地退出资源管理器(以编程方式)

Posted

技术标签:

【中文标题】优雅地退出资源管理器(以编程方式)【英文标题】:Gracefully Exit Explorer (Programmatically) 【发布时间】:2011-08-07 02:08:38 【问题描述】:

您如何优雅地以编程方式关闭资源管理器?

我的意思是,你如何以编程方式调用这个函数:

编辑:图片中的错字,应该是“Ctrl-Shift-Right-Click”而不是“Shift-Click”。

【问题讨论】:

为什么要这样做?您是否正在卸载 shell 扩展?如果是这样,请记住外壳扩展可以加载到任何应用程序中...... @Anders:因为有时资源管理器会出错,我需要关闭它,修改一些文件,然后重新打开它。 出于好奇,当您选择“退出资源管理器”时会发生什么? explorer 是否完全消失了(包括开始菜单、任务栏等?您可以将 windbg 附加到 explorer.exe 并在 TrackPopupMenu(Ex) 上放置一个断点,然后返回查看代码的实际作用。 @Luke:是的,Explorer 完全消失了。 (试试看!只需按住 Ctrl-Shift 并右键单击开始菜单。)哦,关于 TrackPopupMenu 的好主意,我会试试的,谢谢! :) @David:问题的重点是避免这种情况......:P 【参考方案1】:

在 Windows Vista 及更高版本上,您可以使用RestartManager API 正常关闭资源管理器。

在伪代码中它看起来像这样:

RmStartSession(...); 
RM_UNIQUE_PROCESS[] processes = GetProcesses("explorer.exe"); // get special handles to process you want to close    
RmRegisterResources(processes); // register those processes with restart manager session    
RmShutdown(RM_SHUTDOWN_TYPE.RmForceShutdown);     
RmRestart(...); // restart them back, optionally    
RmEndSession(...); 

【讨论】:

如果有多个 explorer.exe 进程,这是唯一有效的方法,可以通过设置“在单独的进程中启动文件夹窗口,并在文件夹上右键单击并选择在新进程中打开,或使用 /separate 命令行选项。非 Shell_TrayWnd 窗口显然不支持 0x5B4 消息,它只会关闭主 explorer.exe 进程,而不是其他进程。 API 页面:msdn.microsoft.com/en-us/library/windows/desktop/… |使用说明:msdn.microsoft.com/en-us/library/windows/desktop/… 如果您不熟悉 WinAPI xD,这是一些非常邪恶的伪代码。第 1 行很好,但第 2 和第 3 行是 PITA【参考方案2】:

@Luke:首先感谢您对Shell_TrayWnd的0x5B4用户消息的详细分析和提示!

不幸的是,该方法有两个缺点;首先,它使用未记录的用户消息,在未来的 Windows 版本中可能会更改,其次,它在 Windows XP 下不起作用,因为退出窗口的“魔术程序”不同(打开关机对话框,然后按 SHIFT 取消它-CTRL-ALT-ESC) 并且不涉及消息发布。

无论 Windows 版本如何,如果有一种可靠且可移植的方式从另一个进程中干净地终止资源管理器,那就太好了。因此,我继续调试干净地终止资源管理器的代码的反汇编,以便找到有关如何实现此目的的提示。我仍然没有完美的解决方案,但我做了一些有趣的观察(在 Windows 7 和 Windows XP 上),我想与任何可能感兴趣的人分享:

Windows 7

0x5B4 消息最终由 CTray::_DoExitExplorer 方法处理。如果你启用了符号服务器,那么你可以在

中设置一个断点

,,explorer.exeCTray::_DoExitExplorer(Visual Studio 语法)

分别

explorer!CTray::_DoExitExplorer(windbg 语法)

Windows XP

在 WinXP 中,您必须将断点设置为

,,explorer.exeCTray::_ExitExplorerCleanly(Visual Studio 语法)

分别

explorer!CTray::_ExitExplorer(windbg 语法)

在您在关机对话框中输入“魔法击键”(SHIFT-CTRL-ALT-ESC) 之前。从反汇编中可以看出,这两种方法非常相似(参见后续帖子)。伪代码是

    if (bUnnamedVariable == FALSE) 
        g_fFakeShutdown = TRUE;  // (1)

        PostMessage(hWndProgMan, WM_QUIT, 0, TRUE);   // (2)

        if (PostMessage(hWndTray, WM_QUIT, 0, 0))     // (3)
            bUnnamedVariable = TRUE;
        
    

请注意,第一个 PostMessage() 调用将 TRUE 作为 lParam 传递,WM_QUIT 正式未使用它。 lParam 的含义似乎是 bShutdown == TRUE。

当然不可能(或不可行)从另一个应用程序设置 g_fFakeShutdown。所以我测试了 PostMessage(hWndProgMan, WM_QUIT, 0, TRUE/FALSE) 的不同组合,后跟 PostMessage(hWndTray, WM_QUIT, 0, FALSE)。似乎资源管理器在 Windows XP 和 Windows 7 下表现出不同的行为。

以下两种方法似乎是在 Windows XP 下终止资源管理器的不错选择。不幸的是,它们不能在 Windows 7 下运行:

    BOOL ExitExplorer1() 
        HWND hWndProgMan = FindWindow(_T("Progman"), NULL);
        PostMessage(hWndProgMan, WM_QUIT, 0, TRUE);   // <=  lParam == TRUE !

        HWND hWndTray = FindWindow(_T("Shell_TrayWnd"), NULL);
        PostMessage(hWndTray, WM_QUIT, 0, 0); 

        return TRUE;
     


    BOOL ExitExplorer2() 
        HWND hWndProgMan = FindWindow(_T("Progman"), NULL);
        PostMessage(hWndProgMan, WM_QUIT, 0, FALSE);   // <=  lParam == FALSE !

        return TRUE;
     

Windows XP 中的行为

在这两种情况下,shell (explorer.exe) 都会终止,并在终止之前设置注册表项

    HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\CleanShutdown = TRUE

可以使用 Sysinternals Process Monitor 或通过在 ,,explorer_WriteCleanShutdown@4(resp. explorer!_WriteCleanShutdown)设置断点来观察。

Windows 7 中的行为

两种方法都不起作用:尽管 shell 似乎已终止,但 explorer.exe 进程仍在运行。

备注

如果我只在 lParam = TRUE 的情况下向 hWndProgMan 发布 WM_QUIT 而不向 hWndTray 发布消息,即,

    BOOL ExitExplorer3() 
        HWND hWndProgMan = FindWindow(_T("Progman"), NULL);
        PostMessage(hWndProgMan, WM_QUIT, 0, TRUE); 

        return TRUE;
     

然后我得到一个有趣的行为(Win7 和 WinXP):出现关机对话框。如果您取消它,一切似乎都正常,但在两到三 (!) 秒后,资源管理器终止。

结论

也许最好的解决方案是在 Windows 7 中使用带有未记录的 WM_USER 函数的 ExitExplorer(),在 Windows XP 中使用 ExitExplorer1() 或 ExitExplorer2()。这两种 XP 方法中的任何一种是否比另一种具有优势?我不知道。

附录

CTray::_DoExitExplorer (Windows 7) 和 CTray::_ExitExplorerCleanly (Windows XP) 的反汇编

Windows 7

    ,,explorer.exeCTray::_DoExitExplorer:
    explorer!CTray::_DoExitExplorer:
    00fdde24 833df027020100  cmp     dword ptr [explorer!g_fInSizeMove+0x4 (010227f0)],0 ds:0023:010227f0=00000000
    00fdde2b 53              push    ebx
    00fdde2c 8bd9            mov     ebx,ecx
    00fdde2e 7535            jne     explorer!CTray::_DoExitExplorer+0x41 (00fdde65)
    00fdde30 56              push    esi
    00fdde31 8b35ec14f700    mov     esi,dword ptr [explorer!_imp__PostMessageW (00f714ec)]
    00fdde37 57              push    edi
    00fdde38 33ff            xor     edi,edi
    00fdde3a 47              inc     edi
    00fdde3b 57              push    edi
    00fdde3c 6a00            push    0
    00fdde3e 6a12            push    12h
    00fdde40 ff35e8000201    push    dword ptr [explorer!v_hwndDesktop (010200e8)]
    00fdde46 893ddc270201    mov     dword ptr [explorer!g_fFakeShutdown (010227dc)],edi
    00fdde4c ffd6            call    esi
    00fdde4e 6a00            push    0
    00fdde50 6a00            push    0
    00fdde52 6a12            push    12h
    00fdde54 ff7304          push    dword ptr [ebx+4]
    00fdde57 ffd6            call    esi
    00fdde59 85c0            test    eax,eax
    00fdde5b 7406            je      explorer!CTray::_DoExitExplorer+0x3f (00fdde63)
    00fdde5d 893df0270201    mov     dword ptr [explorer!g_fInSizeMove+0x4 (010227f0)],edi
    00fdde63 5f              pop     edi
    00fdde64 5e              pop     esi
    00fdde65 a1f0270201      mov     eax,dword ptr [explorer!g_fInSizeMove+0x4 (010227f0)]
    00fdde6a 5b              pop     ebx
    00fdde6b c3              ret

('bUnnamedVariable' 是模块全局变量,地址为 g_fInSizeMove+4)

Windows XP

    ,,explorer.exeCTray::_ExitExplorerCleanly:
    01031973 8B FF            mov         edi,edi 
    01031975 57               push        edi  
    01031976 8B F9            mov         edi,ecx 
    01031978 83 BF 40 04 00 00 00 cmp         dword ptr [edi+440h],0 
    0103197F 75 35            jne         CTray::_ExitExplorerCleanly+43h (10319B6h) 
    01031981 53               push        ebx  
    01031982 56               push        esi  
    01031983 8B 35 94 17 00 01 mov         esi,dword ptr [__imp__PostMessageW@16 (1001794h)] 
    01031989 33 DB            xor         ebx,ebx 
    0103198B 43               inc         ebx  
    0103198C 53               push        ebx  
    0103198D 6A 00            push        0    
    0103198F 6A 12            push        12h  
    01031991 FF 35 8C 60 04 01 push        dword ptr [_v_hwndDesktop (104608Ch)] 
    01031997 89 1D 48 77 04 01 mov         dword ptr [_g_fFakeShutdown (1047748h)],ebx 
    0103199D FF D6            call        esi  
    0103199F 6A 00            push        0    
    010319A1 6A 00            push        0    
    010319A3 6A 12            push        12h  
    010319A5 FF 77 04         push        dword ptr [edi+4] 
    010319A8 FF D6            call        esi  
    010319AA 85 C0            test        eax,eax 
    010319AC 74 06            je          CTray::_ExitExplorerCleanly+41h (10319B4h) 
    010319AE 89 9F 40 04 00 00 mov         dword ptr [edi+440h],ebx 
    010319B4 5E               pop         esi  
    010319B5 5B               pop         ebx  
    010319B6 8B 87 40 04 00 00 mov         eax,dword ptr [edi+440h] 
    010319BC 5F               pop         edi  
    010319BD C3               ret              

('bUnnamedVariable' 似乎是 CTray 在相对偏移 440h 处的成员)

备注 看来这里WM_QUIT的使用方式很不规范,对比下面摘自MSDNWM_QUIT on MSDN

此消息没有返回 值,因为它会导致消息 循环在消息之前终止 被发送到应用程序的窗口 程序。

备注 WM_QUIT 消息不是 与窗口相关联,因此 永远不会通过 窗口的窗口过程。它是 仅由 GetMessage 或 PeekMessage 函数。

不要使用发布 WM_QUIT 消息 PostMessage 函数;采用 PostQuitMessage。

【讨论】:

+1 这是一篇非常的好帖子,感谢分享。我希望我能更多地投票,哈哈.. :) 从 Windows x64 安装中获取的原始 Windows 7 反汇编。我用 Windows 7 32 位的反汇编替换它。现在 Windows 7 和 Windows XP 的相似性更加明显了。 @Mehrdad:看来这里WM_QUIT的使用方式很不规范,对比下面这段摘自msdnlink: @Mehrdad:抱歉,我的上一条评论不完整。我过早地按了返回键。 (看来我太笨了,无法正确使用这个论坛;-) 相反,我在原始答案的末尾添加了关于 WM_QUIT 的备注。 我一直试图让它在 XP 中工作但无济于事。当您通过 ctrl+alt+shift+Cancel 路线时,它会神奇地工作。当您按照上述发布 WM_QUIT 时,它可以工作,但是当 Explorer 再次启动时,它会继续执行 Startup/Run 中的所有内容! ctrl+alt+shift+Cancel 在某处设置某种魔术标志以指示会话没有被终止,而 WM_QUIT 欺骗它相信会话已终止。我可以一遍又一遍地重现这一点,但 Process Monitor 并没有明确指出这个标志可能是什么以及它可能在哪里。有什么想法吗?【参考方案3】:

出于好奇,我对此进行了调试。它所做的只是向资源管理器的一个窗口发布一条消息:

BOOL ExitExplorer()

    HWND hWndTray = FindWindow(_T("Shell_TrayWnd"), NULL);
    return PostMessage(hWndTray, 0x5B4, 0, 0);

当然,这是一条未记录的 WM_USER 消息,因此该行为很可能在未来发生变化。

【讨论】:

这个答案需要很多赞!非常感谢您花时间找到这个! :) @Luke:感谢您对 Shell_TrayWnd 的 0x5B4 用户消息的详细分析和提示!我发布了一个您可能感兴趣的相关答案。 (不知道大家有没有注意到。我之前没有资格到处评论) 注 1:这在 Windows 7 上完美运行。但在 Windows 10 上,Microsoft 实现了 3 秒的延迟。这意味着您发送此消息后,所有资源管理器窗口都会立即隐藏,但 Explorer.exe 会继续在后台不可见地运行。您可以在循环中调用 IsWindow(hWndTray) 以等待资源管理器最终退出。注意 2:如果您想稍后重新启动资源管理器,则不能使用 ShellExecute(),它会在没有任务栏的情况下打开一个新的资源管理器。要正确重启 Explorer,您必须改用 CreateProcess()。【参考方案4】:

我认为不能“优雅地”关闭资源管理器。 EnumProcesses -> 比较路径 -> TerminateProcess

编辑:尝试发送 WM_CLOSE/WM_QUIT (http://support.microsoft.com/kb/178893) 或 EndTask

【讨论】:

@cprogrammer:必须有办法……这就是菜单项的作用。 @Mehrdad:你为什么认为菜单项会关闭资源管理器“优雅地” @Cody:因为 (1) 关闭资源管理器比杀死资源需要更长的时间,并且 (2) 因为我怀疑 MS 会经历所有这些麻烦来隐藏会强行杀死资源管理器的菜单项,当用户可以在任务管理器中杀死它时。 @cprogrammer:我应该将它发送到哪个窗口?我尝试将其发送到GetDesktopWindow(),但它没有做任何事情...... EnumProcesses 或 FindWindow 找到资源管理器窗口然后 dwThreadId=GetWindowThreadProcessId 然后 ::PostThreadMessage(dwThreadId, WM_QUIT/WM_CLOSE, NULL, NULL)。或在 hwnd 上使用 Endtask

以上是关于优雅地退出资源管理器(以编程方式)的主要内容,如果未能解决你的问题,请参考以下文章

Python,Chrome 任务管理器 - 以编程方式访问 Chrome 任务管理器的文本而不使用 CHROMIUM

如何以与在 Windows 资源管理器中“发送给邮件收件人”相同的方式以编程方式发送电子邮件?

如何以编程方式在线程中获取事务管理器?

是否可以在资源管理器窗口中以编程方式将文件夹添加到 Windows 10 快速访问面板?

在recyclerview android中以编程方式更改布局管理器

是否有通过 Windows 资源管理器以编程方式剪切/复制/粘贴文件的 Windows API?