windows - Sådan finder du HWND, der forhindrer shutdown?

Indlæg af Hanne Mølgaard Plasc

Problem



Et eller andet sted i min ansøgning (sammen med tredjeparts biblioteker af kode) er en vindueprocedure, der forhindrer Windows fra:



  • logger af

  • lukker

  • genstart



Jeg fandt en plads i min kode, hvor jeg lavede den usædvanligt almindelige fejl at ringe DefWindowProc, men kalder det forkert:


FBroadcastListenerHwnd := AllocateHWnd(BroadcastListenerWindowProc);

procedure TGrobber.BroadcastListenerWindowProc(var msg: TMessage);
begin
   DefWindowProc(FBroadcastListenerHwnd, msg.msg, msg.wparam, msg.lparam);
end;


Jeg fik den fejl, og mit testprogram stoppede ikke længere afbrydelsen. [18]


Men en fuld applikation gør



Jeg står nu over for at skulle rive et program til ingenting, indtil min computer genstarter igen.


Et eller andet sted dybt inde i min ansøgning er en Window procedure knyttet til en HWND, der vender tilbage nul til WM\_QUERYENDSESSION. Hvis jeg kun kendte HWND, kunne jeg bruge Spy ++ til at finde vinduet. [19]


Men hvordan kan jeg finde det hwnd?


Windows Program logfilen noterer processen , der stopper en shutdown:


Indtast billedbeskrivelse her [20]


Og det er meget godt at være en mere detaljeret logfil i de mere detaljerede applikationer og tjenester logfiler . Men de er ikke-dokumenterede.


Hvordan kan jeg finde mit problematiske hwnd?


Forsøg



Jeg forsøgte at bruge EnumThreadWindows for at få alle vinduerne i min 'hoved' tråd med ideen om manuelt at sende WM\_QUERYENDSESSION til dem alle for at se, hvem der returnerer 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));


Nu har jeg en liste over tolv hwnds. Poke dem:


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;


Men ingen returnerede nul.


Så det er et rør ned i afløbet.

Bedste reference


EnumThreadWindows opregner kun vinduerne i en bestemt tråd. Det kunne være, at det voldelige vindue blev skabt i en tråd. Så jeg foreslår at du bruger EnumWindows til at bruge alle topniveau vinduer i din ansøgning til din test.


Det er nok til at initialisere COM i en tråd, og du vil have et vindue, du ikke ved. På den måde kan et opkald til WaitForSingleObject i en tråd være din synder:
Fejlfinding af et program, der ikke ville opføre sig med WM\_QUERYENDSESSION [21]

Andre referencer 1


Det kan måske lyde som overkill, men her går det. Jeg ville løse dette ved hjælp af kodehooks til AllocateHWnd og DeallocateHWnd. Vi var nødt til at løse et andet problem i forbindelse med håndtag, og det fungerede godt for os.


Dine udskiftningsrutiner vil kun være kopier af versionerne i System.Classes. Du skal også kopiere alle afhængigheder (PObjectInstance, TObjectInstance, CodeBytes, PInstanceBlock, TInstanceBlock, InstBlockList, InstFreeList, StdWndProc, CalcJmpOffset, MakeObjectInstance, FreeObjectInstance, CleanupInstFreeList, GetFreeInstBlockItemCount, ReleaseObjectInstanceBlocks, UtilWindowClass) fra den pågældende enhed. Den eneste forskel er, at du logger alle tildelte og deallokerede håndtag i dine udskiftningsrutiner. Det ville medvirke til også at omfatte stakspor.


Det vil give dig en liste over alle de håndtag, der er tildelt på tidspunktet for din afbrydelse sammen med deres kaldestabler.


Den grundlæggende struktur er sådan noget. Jeg kan ikke sende fuld kode, fordi det er mest VCL kode med undtagelse af kode kroge og logning.


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;


Det kan også være nødvendigt at krog og logge SetWindowLong, SetWindowLongA og SetWindowLongW også.