Search code examples
windowsdelphihwnd

How to find the HWND that is preventing shutdown?


Somewhere in my application (along with 3rd party libraries of code) is a window procedure that is preventing Windows from:

  • logging off
  • shutting down
  • restarting

I found one spot in my code where I made the extraordinarily common mistake of calling DefWindowProc, but calling it incorrectly:

Before:

void Grobber.BroadcastListenerWindowProc(ref TMessage msg)
{
   DefWindowProc(_broadcastListenerHwnd, msg.msg, msg.wparam, msg.lparam);
}

After:

void Grobber.BroadcastListenerWindowProc(ref TMessage msg)
{
   //20170207: Forgetting to set the result can, for example, prevent Windows from restarting
   msg.Result = DefWindowProc(_broadcastListenerHwnd, msg.msg, msg.wparam, msg.lparam);
}

I fixed that bug, and my test program no longer halted the shutdown.

But a full application does

I'm now faced with having to tear a program down to nothing, until my computer finally reboots.

Somewhere deep inside my application is a Window procedure attached to an HWND that is returning zero to WM_QUERYENDSESSION. If only i knew the HWND, i could use the Spy++ to find the Window.

But how can i find that hwnd?

The Windows Application event log notes the process that halt a shutdown:

enter image description here

And there very well be a more detailed log in the more detailed Applications and Services Logs. But those are undocumented.

How can i find my problematic hwnd?

Attempts

I tried to use EnumThreadWindows to get all the windows of my "main" thread, with the idea of manually sending WM_QUERYENDSESSION to them all to see who returns false:

var
   wnds: TList<HWND>;

function DoFindWindow(Window: HWnd; Param: LPARAM): Bool; stdcall;
var
   wnds: TList<HWND>;
begin
   wnds := TList<HWND>(Param);
   wnds.Add(Window);
   Result := True;
end;

wnds := TList<HWND>.Create;
enumProc := @DoFindWindow;
EnumThreadWindows(GetCurrentThreadId, EnumProc, LPARAM(wnds));

Now i have a list of twelve hwnds. Poke them:

var
   window: HWND;
   res: LRESULT;

for window in wnds do
begin
    res := SendMessage(window, WM_QUERYENDSESSION, 0, 0);
    if res = 0 then
    begin
        ShowMessage('Window: '+IntToHex(window, 8)+' returned false to WM_QUERYENDSESSION');
    end;
end;

But nobody did return zero.

So that's one tube down the drain.


Solution

  • EnumThreadWindows only enumerates the windows of one particular thread. It could be that the offending window was created in a thread. So I'd suggest that you use EnumWindows to enum all top level windows in your application for your test.

    It's enough to initialize COM in a thread and you'll have a window you don't know about. That way a call to WaitForSingleObject in a thread could be your culprit: Debugging an application that would not behave with WM_QUERYENDSESSION