Изящно Exit Explorer (программно) - PullRequest
34 голосов
/ 17 апреля 2011

Как вы изящно программно закрываете Проводник?

Под этим я подразумеваю, как вы вызываете эту функцию программно:

Изменить: Опечатка на картинке, она должна сказать «Ctrl-Shift-Right-Click» вместо «Shift-Click».

Ответы [ 4 ]

51 голосов
/ 18 апреля 2011

Я отладил это из любопытства.Все, что он делает, это отправляет сообщение в одно из окон проводника:

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

Конечно, это недокументированное сообщение WM_USER, поэтому поведение в будущем вполне может измениться.

33 голосов
/ 06 июня 2011

@ Люк: прежде всего, спасибо за подробный анализ и подсказку о пользовательском сообщении 0x5B4 в Shell_TrayWnd!

К сожалению, у этого метода есть два недостатка;Во-первых, он использует недокументированное сообщение пользователя, которое может измениться в будущих версиях Windows, а во-вторых, он не работает в Windows XP, поскольку «волшебная процедура» для выхода из окон отличается (откройте диалоговое окно выключения, затем отмените его, нажав клавишу SHIFT-CTRL-ALT-ESC) и там нет сообщений о публикации.

Было бы неплохо иметь надежный и переносимый способ чистого завершения проводника из другого процесса независимо от версии Windows.Поэтому я продолжил отладку дизассемблирования кода, который чисто завершает работу обозревателя, чтобы найти подсказку о том, как мне этого добиться.У меня до сих пор нет идеального решения, но я сделал несколько интересных наблюдений (на Windows 7 и Windows XP), которыми хочу поделиться с теми, кто может быть заинтересован:

Windows 7

Сообщение 0x5B4 в конечном итоге обрабатывается методом CTray :: _ DoExitExplorer.Если у вас включен сервер символов, вы можете установить точку останова в

{,,explorer.exe}CTray::_DoExitExplorer (синтаксис visual studio)

соответственно

explorer!CTray::_DoExitExplorer (синтаксис windbg)

Windows XP

В WinXP вы должны установить точку останова на

{,,explorer.exe}CTray::_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 () принимает значение ИСТИНА как 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

В обоих случаях оболочка (explorer.exe) завершается и до ее завершенияустанавливает ключ реестра

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

, что можно наблюдать с помощью Sysinternals Process Monitor или путем установки точки останова на {,, explorer} _WriteCleanShutdown @ 4 (соответственно, explorer! _WriteCleanShutdown).

Поведение в Windows 7

Оба метода не работают: несмотря на то, что оболочка завершена, процесс explorer.exe все еще выполняется.

Примечание

Если я только отправляю WM_QUIT в hWndProgMan с lParam = TRUE, не отправляя сообщение в hWndTray, т.е.

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

        return TRUE;
    } 

, тогда я получаю интересное поведение (и Win7, и WinXP): Появляется диалоговое окно выключения.Если вы отмените его, все будет нормально, но через две или три (!) Секунды проводник завершит работу.

Вывод

Возможно, лучшим решением будет использованиеExitExplorer () с недокументированной функцией WM_USER для Windows 7 и ExitExplorer1 () или ExitExplorer2 () для Windows XP.Есть ли у одного из двух методов XP преимущества перед другими?Я не знаю.

Приложение

Разборка CTray :: _ DoExitExplorer (Windows 7) и CTray :: _ ExitExplorerCleanly (Windows XP)

Windows 7

    {,,explorer.exe}CTray::_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.exe}CTray::_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 используется здесь очень нестандартным способом, сравните следующую выдержку из MSDN WM_QUIT на MSDN

Это сообщение не имеет возврата значение, потому что это вызывает сообщение цикл для завершения перед сообщением отправляется в окно приложения процедура.

Замечания Сообщение WM_QUIT не является связано с окном и, следовательно, никогда не будет получен через процедура окна окна. это извлекается только GetMessage или PeekMessage функции.

Не публиковать сообщение WM_QUIT, используя функция PostMessage; использование PostQuitMessage.

10 голосов
/ 14 августа 2012

В 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(...); 
0 голосов
/ 17 апреля 2011

Я не думаю, что проводник может быть закрыт "Изящно". EnumProcesses -> сравнить путь -> TerminateProcess

Редактировать: Попробуйте отправить WM_CLOSE / WM_QUIT (http://support.microsoft.com/kb/178893) или EndTask

...