Wodzu
Wodzu

Reputation: 6979

Problems with closing main application window when having only the ID of its process

I have ID of the process. This process is an application which have a main window. I am trying to close this application by sending WM_CLOSE to its main window. I am searching its main window by using EnumWindows.

The problem is, that this application which I try to close, does not close always. It is multithreaded application. Notepad and Calc are always closing when I use the same method which is presented below. But I am not sure if it is working properly cause it returns me many handles to the same window, even for Calc.exe.

Is it possible that thread is taking a handle to window and then this handle somehow become corrupted? Or maybe I should not use GetWindowThreadProcessId(hHwnd,pPid) but some other function in the callback?

I am out of ideas, would be grateful for any help. Thanks.

Code snippet:

unit Unit22;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm22 = class(TForm)
    edtprocID: TEdit;
    lblEnterProcessID: TLabel;
    btnCloseProcessWindow: TButton;
    lblStatus: TLabel;
    procedure btnCloseProcessWindowClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type
  THandleAndHWND = record
    ProcID: THandle;
    WindowHandle: HWND;
  end;

var
  Form22: TForm22;

var
  HandleAndHWNDArray: array of THandleAndHWND;
  HandeIndex, lp: Integer;

implementation

{$R *.dfm}

function EnumProcess(hHwnd: HWND; lParam : integer): boolean; stdcall;
var
  pPid : DWORD;
begin
  //if the returned value in null the
  //callback has failed, so set to false and exit.
  if (hHwnd=0) then
  begin
    result := false;
  end else
  begin
    GetWindowThreadProcessId(hHwnd,pPid);
    Inc(HandeIndex);
    HandleAndHWNDArray[HandeIndex].ProcID := pPid;
    HandleAndHWNDArray[HandeIndex].WindowHandle := hHwnd;
    Result := true;
  end;

end;

procedure TForm22.btnCloseProcessWindowClick(Sender: TObject);
var
  ProcID: Cardinal;
  i, LastError: Integer;
begin
  HandeIndex := -1;
  ProcID := StrToInt(edtprocID.Text);

  SetLength(HandleAndHWNDArray, 3000);
  EnumWindows(@EnumProcess,lp);

  for i := 0 to HandeIndex do //After EnumWindows HandleIndex is above 500 despite the fact that I have like 10 openned windows max
  begin                       //That means that EnumWindows was called 500 times?
    if HandleAndHWNDArray[i].ProcID =  ProcID then //search for process equal to procces ID given by the user
    begin
      //if we have a processID then we have a handle to its main window
      if PostMessage(HandleAndHWNDArray[i].WindowHandle, WM_CLOSE, 0, 0) then
      begin
        lblStatus.Caption := 'message posted!';
      end else
      begin
        LastError := GetLastError;
        lblStatus.Caption := Format('Error: [%d] ' + SysErrorMessage(LastError), [LastError]);
      end;
      Exit;
    end;
  end;

end;

end.

Upvotes: 1

Views: 1316

Answers (1)

Heinrich Ulbricht
Heinrich Ulbricht

Reputation: 10372

Have a look in this Knowledge Base Article here on how to close another application as cleanly as possible. You are doing it right so far. The Article suggests that you

  • first post WM_CLOSE to all windows of the application (since you cannot know for sure which one is the main).
  • wait with a timeout and if the timeout elapses
  • kill the application using TerminateProcess

I agree.

Upvotes: 3

Related Questions