如何找到阻止关机的HWND?

时间:2017-02-07 16:08:45

标签: windows delphi hwnd

我的应用程序中的某个地方(以及第三方代码库)是一个阻止Windows的窗口过程:

  • 注销
  • 关闭
  • 重新启动

我在代码中找到了一个地方,我在调用DefWindowProc时犯了一个非常常见的错误,但是调用错误:

之前

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

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);
}

我修复了这个错误,我的test program不再停止关机。

但完整的应用程序

我现在面临着不得不撕掉一个程序,直到我的计算机终于重新启动。

我应用程序内部的某个地方是附加到HWND的Window过程,该过程返回零到WM_QUERYENDSESSION。如果我只知道HWND,我可以使用Spy ++找到Window。

但我怎样才能找到hwnd

Windows 应用程序事件日志记录了停止关闭的进程

enter image description here

更详细的应用程序和服务日志中有更详细的日志。但那些没有证件。

如何找到有问题的hwnd

尝试

我尝试使用EnumThreadWindows获取我的" main"的所有窗口线程,想要手动向其发送WM_QUERYENDSESSION以查看谁返回 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));

现在我有十二个hwnds的清单。戳他们:

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;

但是没有人确实返回零。

这样就可以将一根水管排到下水道。

2 个答案:

答案 0 :(得分:3)

EnumThreadWindows仅枚举一个特定线程的窗口。可能是在一个线程中创建了违规窗口。因此,我建议您使用EnumWindows枚举应用程序中的所有顶级窗口进行测试。

足以在一个帖子中初始化COM,你将拥有一个你不了解的窗口。这样,在线程中调用WaitForSingleObject可能是你的罪魁祸首: Debugging an application that would not behave with WM_QUERYENDSESSION

答案 1 :(得分:1)

这听起来有点像矫枉过正,但是这里有。我会使用AllocateHWndDeallocateHWnd的代码挂钩来解决这个问题。我们必须解决与句柄相关的不同问题,它对我们来说效果很好。

您的替换例程将只是System.Classes中版本的副本。您还需要从该单元复制所有依赖项(PObjectInstance, TObjectInstance, CodeBytes, PInstanceBlock, TInstanceBlock, InstBlockList, InstFreeList, StdWndProc, CalcJmpOffset, MakeObjectInstance, FreeObjectInstance, CleanupInstFreeList, GetFreeInstBlockItemCount, ReleaseObjectInstanceBlocks, UtilWindowClass)。唯一的区别是您在替换例程中记录所有已分配和已释放的句柄。它也有助于包含堆栈跟踪。

这将为您提供关闭时分配的所有句柄及其调用堆栈跟踪的列表。

基本结构是这样的。我无法发布完整的代码,因为除了代码挂钩和日志记录之外,它主要是VCL代码。

const
{$IF Defined(CPUX86)}
  CodeBytes = 2;
{$ELSEIF Defined(CPUX64)}
  CodeBytes = 8;
{$ENDIF CPU}
  InstanceCount = (4096 - SizeOf(Pointer) * 2 - CodeBytes) div SizeOf(TObjectInstance) - 1;

type
  PInstanceBlock = ^TInstanceBlock;
  TInstanceBlock = packed record
    ...
  end;

var
  InstBlockList: PInstanceBlock;
  InstFreeList: PObjectInstance;

{ Standard window procedure }
function StdWndProc(Window: HWND; Message: UINT; WParam: WPARAM; LParam: WPARAM): LRESULT; stdcall;
...

function CalcJmpOffset(Src, Dest: Pointer): Longint;
...

function MakeObjectInstance(const AMethod: TWndMethod): Pointer;
...

procedure FreeObjectInstance(ObjectInstance: Pointer);
...

procedure CleanupInstFreeList(BlockStart, BlockEnd: PByte);
...

function GetFreeInstBlockItemCount(Item: PObjectInstance; Block: PInstanceBlock): Integer;
...

procedure ReleaseObjectInstanceBlocks;
...

var
  UtilWindowClass: TWndClass = (
    ... );

function AllocateHWnd(const AMethod: TWndMethod): HWND;
begin
  < Logging/Stack trace code here >
  ...
end;

procedure DeallocateHWnd(Wnd: HWND);
begin
  < Logging/Stack trace code here >
  ...
end;

也可能需要挂钩并记录SetWindowLong, SetWindowLongASetWindowLongW