0

I am using the following code to open another application from within my app.

type TShellState = class
  SEInfo: TShellExecuteInfo;
  AppName: String;
  Constructor Create(pSEInfo: TShellExecuteInfo; pAppName: String);
end;

Var
  ShellStates: TObjectStack<TShellState>;

Constructor TShellstate.Create(pSEInfo: TShellExecuteInfo; pAppName: string);
begin
  inherited Create;
  SEInfo := pSEInfo;
  AppName := pAppName;
end;

//In Form Create
ShellStates := TObjectStack<TShellState>.create;
ShellStates.OwnsObjects := true;

function DoExecute(Const ExecuteFile: string;
                   Const ParamString: string;
                   Modal: Boolean;
                   UseLocalDir: Boolean = false;
                   NoCloseProc: Boolean = true): Boolean;

Var SEInfo: TShellExecuteInfo;
    ExitCode: DWORD;

begin
  FillChar(SEInfo, SizeOf(SEInfo), 0) ;
  SEInfo.cbSize := SizeOf(TShellExecuteInfo) ;
  with SEInfo do begin
    fMask := SEE_MASK_DEFAULT;
    Wnd := Application.Handle;
    lpFile := PChar(ExecuteFile) ;
    lpParameters := PChar(ParamString) ;
    if UseLocalDir then
      lpDirectory := PChar(ExtractFilePath(ExecuteFile));
    nShow := SW_SHOWNORMAL;
  end;

  Result := ShellExecuteEx(@SEInfo);
  if result then begin
    if Modal then begin
      if SEInfo.hProcess <> 0 then begin
        WaitForSingleObject(SEInfo.hProcess, INFINITE);
        CloseHandle(SEInfo.hProcess);
      end;
      Result := true;
    end
    else if NoCloseProc then
      ShellStates.Push(TShellState.Create(SEInfo, ExecuteFile));
  end;
end;

If NoCloseProc is true I place the SEInfo record into stack.

When I come to close the main program, I was expecting to be able to use fields in the SEInfo to identify the window to receive the WM_CLOSE message. e.g.

  while ShellStates.Count > 0 do begin
    ShellState := ShellStates.Peek;
    SendMessage(ShellState.SEInfo.Wnd, WM_CLOSE,0,0);
    ShellStates.Pop;
  end

But both hProcess and Wnd are 0 so it looks like I will have to locate the window using the file name. Is there a more straightforward way? I tried using the FindWindow API call with the SEInfo.lpFile but that returns 0.

Tom Brunberg
  • 20,312
  • 8
  • 37
  • 54
John Barrat
  • 810
  • 4
  • 15
  • 3
    What do you expect by putting a variable name freely chosen by you into a question title (instead of its type)? Likewise none of us knows how `ShellState` is declared. After 10 years you should have learnt what **reproducible code** means. – AmigoJack Mar 15 '23 at 08:52
  • 2
    @AmigoJack has a very valid point. If I read the Stack Overflow new questions list and find "Can I use the SEInfo record created when starting an app/process to close the same app/process", I might say to myself, "This is not a question I can answer, because I have never even heard of the `SEInfo` record". But if I stumble upon "Can I use the `SHELLEXECUTEINFO` record created when starting an app/process to close the same app/process", I realise it is about the Win32 `ShellExecuteEx` function. – Andreas Rejbrand Mar 15 '23 at 12:17
  • 1
    Point taken, I will try and be more specific in my titles – John Barrat Mar 15 '23 at 12:32

1 Answers1

3

When I come to close the main program, I was expecting to be able to use fields in the SEInfo to identify the window to receive the WM_CLOSE message.

There is no single field provided by ShellExecuteEx() for that purpose, so you are going to have to hunt down the window(s) you want to interact with.

Note also that if you are trying to make a spawned program exit, sending WM_CLOSE may not always cut it. Think of a text editor that prompts the user to save pending changes, and if the user cancels the prompt then the editor keeps running. So, if you want to force a closure, consider sending WM_QUIT instead of WM_CLOSE. There are several questions on StackOverflow related to closing external processes, such as:

How to gracefully terminate a process?

Win32 API For Shutting Down Another Process Elegantly?

How do I gracefully close another application?

Just to name a few.

But both hProcess and Wnd are 0

The Wnd field is an input-only field, it specifies the owner window for any UI dialog that ShellExecute() may display while setting up/executing the specified action. The field does not receive any window belonging to the process that is being started.

hwnd

Type: HWND

Optional. A handle to the owner window, used to display and position any UI that the system might produce while executing this function.

The hProcess field is 0 because you are not specifying the SEE_MASK_NOCLOSEPROCESS flag in the fMask field:

fMask

Type: ULONG

A combination of one or more of the following values that indicate the content and validity of the other structure members:

Value Meaning
SEE_MASK_NOCLOSEPROCESS
(0x00000040)
Use to indicate that the hProcess member receives the process handle. This handle is typically used to allow an application to find out when a process created with ShellExecuteEx terminates. In some cases, such as when execution is satisfied through a DDE conversation, no handle will be returned. The calling application is responsible for closing the handle when it is no longer needed.

so it looks like I will have to locate the window using the file name. Is there a more straightforward way?

You can use EnumWindows() to look for windows that belong to the process that is represented by the returned hProcess field when using the SEE_MASK_NOCLOSEPROCESS flag. You can use GetProcessId() to get a ProcessID from the hProcess field, and then have your enumeration callback use GetWindowThreadProcessId() to compare window ProcessIDs.

I tried using the FindWindow API call with the SEInfo.lpFile but that returns 0.

Of course, because most apps don't display filenames in their window titles to begin with, and apps that do display filenames tend to display other information as well, such as their app name.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • @John A process can have zero to many windows, not exactly one. Just like your Delphi projects can. Programs are not bound to having windows. And vice versa: finding no window does not mean the process no longer exists - it can still run windowless. – AmigoJack Mar 15 '23 at 17:07