我的应用程序中的某个地方(以及第三方代码库)是一个阻止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 应用程序事件日志记录了停止关闭的进程:
更详细的应用程序和服务日志中有更详细的日志。但那些没有证件。
如何找到有问题的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;
但是没有人确实返回零。
这样就可以将一根水管排到下水道。
答案 0 :(得分:3)
EnumThreadWindows
仅枚举一个特定线程的窗口。可能是在一个线程中创建了违规窗口。因此,我建议您使用EnumWindows
枚举应用程序中的所有顶级窗口进行测试。
足以在一个帖子中初始化COM,你将拥有一个你不了解的窗口。这样,在线程中调用WaitForSingleObject可能是你的罪魁祸首: Debugging an application that would not behave with WM_QUERYENDSESSION
答案 1 :(得分:1)
这听起来有点像矫枉过正,但是这里有。我会使用AllocateHWnd
和DeallocateHWnd
的代码挂钩来解决这个问题。我们必须解决与句柄相关的不同问题,它对我们来说效果很好。
您的替换例程将只是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, SetWindowLongA
和SetWindowLongW
。