Solved

Closing Multiple Open Delphi exe.

Posted on 2006-07-17
15
283 Views
Last Modified: 2010-04-04
Hi

I have a Main Form that acts like a Main Menu. This main menu call a program  .exe when a user click on the menu item. The program are then run in a seperate window. This mean the user can have ONE MAIN menu and several other windows  exe OPEN at the same time.

** NOW what I want to do is.. If a user close the MAIN MENU... all the other open windows must also be closed at the same time.

How do I keep track of what windows (.exe) are open from my MAIN MENU, and how CAN I closed them ALL AT ONCE...IS their a easy way to identify open windows
created by a application and then to close them..

Thanx
Henry
0
Comment
Question by:henryreynolds
  • 6
  • 5
  • 4
15 Comments
 
LVL 17

Expert Comment

by:Wim ten Brink
Comment Utility
If you use ShellExecuteEx or CreateProcess to start the Exe's then you should have a process ID or process handle which you can use to control the child processes. All you would need to do is call TerminateProcess() with the Process HANDLE of these child processes. Keep in mind that there's a difference between process ID and Process Handle.

There are ways to check for any process (including the current one) to see what his child processes are. This is some deeper API stuff which you won't need if you just keep track of all process handles yourself.
0
 

Author Comment

by:henryreynolds
Comment Utility
Hi how can I get all the processes, I am Using the following code to create a new process.

if (not CreateProcess(nil, PChar(RunPath + sExeName), nil, nil, False,
     NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo)) then
         MessageDlg(MsgMenuExec + #13 + '(' + sProgName + ')', mtWarning, [mbOK], 0);

SORRY but i really dont no how to get all the proceeses when a call a fuction on the close event of my main form.

HEnry
0
 
LVL 17

Expert Comment

by:Wim ten Brink
Comment Utility
Let's see. The user clicks on a menu-item. That menuitem calls CreateProcess. CreateProcess returns you a process ID and process handle in lpProcessInformation. You store the process handle in an array. Once your application closes, you go through this array and terminate every running process. This way you keep track of all the processes.
0
 
LVL 17

Expert Comment

by:TheRealLoki
Comment Utility
here is a demo I wrote for you on 1 way to do it using a list of process ids from your CreateProcess, and then finding their form's and closing them when your main menu application closes

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
    MyProcessList: TList;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//This function finds the windows and closes them
function EnumWindowsProc(Handle: THandle; List: TList): boolean; stdcall;
    var
        i: integer;
        found: boolean;
        wndPid: THandle;
    begin
        Result := True;
        found := false;
        i := 0;
        GetWindowThreadProcessId(Handle, @wndPid);
        while (not found) and (i < List.Count) do
        begin
           if (wndPid = Integer(List[i]) ) then
           begin
                found := true;
                PostMessage(Handle, WM_Close, 0, 0);
// or  PostMessage(Handle, WM_CancelMode, 0, 0); first in case it has a dialog, or wm_quit, etc..
           end
           else inc(i);
        end;
    end;

procedure TForm1.Button1Click(Sender: TObject);
    const // some test data for this demo
        runpath = 'c:\windowst\'; // or 'c:\winnt\' etc. just a demo
        sExename = 'notepad.exe';
        sProgName = 'Notepad';
        MsgMenuExec = 'MsgMenuExec';
    var
        StartupInfo: TStartupInfo;
        ProcessInfo: TProcessInformation;
    begin
        fillChar( startupInfo, sizeof( startupInfo ), 0 );
        StartupInfo.cb := sizeof( startupInfo );
        StartupInfo.wShowWindow := SW_SHOW;

        if (CreateProcess(nil, PChar(RunPath + sExeName), nil, nil, False,
         NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo)) then
         begin
            MyProcessList.Add(pointer(ProcessInfo.dwProcessId));
         end
         else
           MessageDlg(MsgMenuExec + #13 + '(' + sProgName + ')', mtWarning, [mbOK], 0)

    end;

procedure TForm1.FormCreate(Sender: TObject);
    begin
        MyProcessList := TList.Create;
    end;

procedure TForm1.FormDestroy(Sender: TObject);
    begin
        MyProcessList.Clear;
        MyProcessList.Free;
    end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
        EnumWindows(@EnumWindowsProc, integer(MyProcessList));
    end;

end.
0
 
LVL 17

Expert Comment

by:TheRealLoki
Comment Utility
small typo
  runpath = 'c:\windowst\';
should be
  runpath = 'c:\windows\';
but you'll be using your own code for those variables anyway
0
 

Author Comment

by:henryreynolds
Comment Utility
Hi
thanx i have a look at it now, and will let you know..thanx for helping me with the example

Henry
0
 

Author Comment

by:henryreynolds
Comment Utility
Hi

THANK YOU THIS IS EXACTLY what I want to DO...
If it is possible can I ask you one more thing, because I really dont UNDERSTAND THIS...

Is it possible to restrict a user to open a number of certain windows, let me expalain with a example..

I got a menu with 3 categories NL.

SALES
STOCK
EMPLOYEES

each category has a number of sub programs, THIS SUBPROGRAM ARE THE EXE I AM CALLING.
NOW I want to restrict a user to open only one exe of each category, this mean a user can have the following open windows
1.Main Menu
2.Categorie 1 -> Add sales Transaction
3.Categorie 2 -> View Stock Levels
4.Categorie 3 -> View Employees Details

SORRY but I dont know WHY they want it like this, i think te reason is to RESTRICT the number of open windows on the windows taskbar.

PLEASE If you understand what I want to do, can you help me, BECAUSE THE CLOSING OF ALL OPEN EXE ARE 100000%.

If you cant help me then its 100% I will close this question and give you all the points..

Thank You

Henry
0
Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

 
LVL 17

Expert Comment

by:TheRealLoki
Comment Utility
The simplest solution would be to disable the buttons when the user clicks on them, and you start the application

e.g.
procedure TForm1.bAddSalesTransaction(sender; tobject);
  begin
// do createprocess bit...
    bAddSalesTransaction.Enabled := false;
  end;
0
 

Author Comment

by:henryreynolds
Comment Utility
Hi

I also thought about it. BUT what I dont understand is.. HOW DO I DETERMINE which exe the user have close. Because i am confused now..
When you create a process, you add the process id to MyProcessList. HOW do you determine that the user have close the window he has open a few minutes ago.

sorry but i dont think i make my self clear enough. I dont know how the process work, if i call a sub program and it create a process, how will my main menu know if the user have close that screen and there are no more exe open, AND I can ENABLE that menu item again.

Thanx for your help.
0
 
LVL 17

Expert Comment

by:TheRealLoki
Comment Utility
oh, if you wish to keep track of the spawned applications, then I'd recommend using a thread.
I'll rewrite the above for you to demonstrate this
0
 
LVL 17

Accepted Solution

by:
TheRealLoki earned 500 total points
Comment Utility
actually, with a small change, you can use the existing code, I wont scare you with the thread method just yet :-)

unit Unit1;

interface

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

type
    TMyProcessObject = class
    public
        MsgMenuExec, ProgName, RunPath, Exename: string;
        ProcessID: cardinal;
        StillRunning: boolean;
        constructor Create(MsgMenuExec_, ProgName_, RunPath_, Exename_: string; ProcessID_: cardinal);
    end;

type
  TForm1 = class(TForm)
    bStartNotepad: TButton;
    bStartCalc: TButton;
    Timer1: TTimer;
    procedure bStartNotepadClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure bStartCalcClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
    function SpawnProcess(MsgMenuExec_, ProgName_, RunPath_, Exename_: string): boolean;
  public
    { Public declarations }
    MyProcessList: TList;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//This function finds the windows and closes them
function EnumWindowsProcClose(Handle: THandle; List: TList): boolean; stdcall;
    var
        i: integer;
        found: boolean;
        wndPid: THandle;
    begin
        Result := True;
        found := false;
        i := 0;
        GetWindowThreadProcessId(Handle, @wndPid);
        while (not found) and (i < List.Count) do
        begin
           if (wndPid = TMyProcessObject(List[i]).ProcessID) then
           begin
                found := true;
                PostMessage(Handle, WM_Close, 0, 0);
// or  PostMessage(Handle, WM_CancelMode, 0, 0); first in case it has a dialog, or wm_quit, etc..
           end
           else inc(i);
        end;
    end;

//This function finds the windows and sets the StillRunning flag
function EnumWindowsProcCheckRunning(Handle: THandle; List: TList): boolean; stdcall;
    var
        i: integer;
        found: boolean;
        wndPid: THandle;
    begin
        Result := True;
        found := false;
        i := 0;
        GetWindowThreadProcessId(Handle, @wndPid);
        while (not found) and (i < List.Count) do
        begin
           if (wndPid = TMyProcessObject(List[i]).ProcessID) then
           begin
                found := true;
                TMyProcessObject(List[i]).StillRunning := True;
           end
           else inc(i);
        end;
    end;

procedure TForm1.bStartNotepadClick(Sender: TObject);
    begin
        SpawnProcess('MsgMenuExec', 'Notepad', 'c:\windows\', 'notepad.exe');
//        SpawnProcess('MsgMenuExec', 'Notepad', 'c:\winnt\', 'notepad.exe');
        (sender as TButton).Enabled := False;
    end;

procedure TForm1.bStartCalcClick(Sender: TObject);
    begin
        SpawnProcess('MsgMenuExec', 'Calc', 'c:\windows\system32\', 'calc.exe');
//        SpawnProcess('MsgMenuExec', 'Calc', 'c:\winnt\system32\', 'calc.exe');
        (sender as TButton).Enabled := False;
    end;

function TForm1.SpawnProcess(MsgMenuExec_, ProgName_, RunPath_, Exename_: string): boolean;
    var
        StartupInfo: TStartupInfo;
        ProcessInfo: TProcessInformation;
    begin
        result := false;
        Timer1.Enabled := False;
        try
            fillChar( startupInfo, sizeof( startupInfo ), 0 );
            StartupInfo.cb := sizeof( startupInfo );
            StartupInfo.wShowWindow := SW_SHOW;

            if (CreateProcess(nil, PChar(RunPath_ + ExeName_), nil, nil, False,
             NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo)) then
             begin
                MyProcessList.Add(TMyProcessObject.Create(MsgMenuExec_, ProgName_, RunPath_,
                  Exename_,ProcessInfo.dwProcessId));
                result := true;
             end
             else
               MessageDlg(MsgMenuExec_ + #13 + '(' + ProgName_ + ')', mtWarning, [mbOK], 0)
        finally
            Timer1.Enabled := True;
        end;
    end;

procedure TForm1.FormCreate(Sender: TObject);
    begin
        MyProcessList := TList.Create;
        Timer1.Enabled := True;
    end;

procedure TForm1.FormDestroy(Sender: TObject);
    begin
        MyProcessList.Clear;
        MyProcessList.Free;
    end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
        Timer1.Enabled := False;
        EnumWindows(@EnumWindowsProcClose, integer(MyProcessList));
    end;

procedure TForm1.Timer1Timer(Sender: TObject);
    var
        i: integer;
    begin
        Timer1.Enabled := False;
        try
            for i:= 0 to pred(MyProcessList.Count) do
              TMyProcessObject(MyProcessList[i]).StillRunning := False;
// this next part marks any processes as "StillRunning"
            EnumWindows(@EnumWindowsProcCheckRunning, integer(MyProcessList));
            for i:= pred(MyProcessList.Count) downto 0 do
            begin
                if not TMyProcessObject(MyProcessList[i]).StillRunning then
                begin // this process is no longer running, re-enable the button for it
                    if (
                         (TMyProcessObject(MyProcessList[i]).MsgMenuExec = 'MsgMenuExec') and
                         (TMyProcessObject(MyProcessList[i]).ProgName = 'Notepad')
                       ) then bStartNotepad.Enabled := True
                    else if (
                         (TMyProcessObject(MyProcessList[i]).MsgMenuExec = 'MsgMenuExec') and
                         (TMyProcessObject(MyProcessList[i]).ProgName = 'Calc')
                       ) then bStartCalc.Enabled := True;
// Delete this process from our list since it is no longer running
                    MyProcessList.Delete(i);
                end;
            end;

        finally
            Timer1.Enabled := True;
        end;
    end;

{ TMyProcessObject }

constructor TMyProcessObject.Create(MsgMenuExec_, ProgName_, RunPath_,
  Exename_: string; ProcessID_: cardinal);
begin
    inherited Create;
    MsgMenuExec := MsgMenuExec_;
    ProgName := ProgName_;
    RunPath := RunPath_;
    Exename := Exename_;
    ProcessID := ProcessID_;
end;


end.



******** FORM FOLLOWS *********


object Form1: TForm1
  Left = 367
  Top = 247
  Width = 249
  Height = 141
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  OnClose = FormClose
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  PixelsPerInch = 96
  TextHeight = 13
  object bStartNotepad: TButton
    Left = 72
    Top = 24
    Width = 91
    Height = 25
    Caption = 'bStartNotepad'
    TabOrder = 0
    OnClick = bStartNotepadClick
  end
  object bStartCalc: TButton
    Left = 72
    Top = 56
    Width = 91
    Height = 25
    Caption = 'bStartCalc'
    TabOrder = 1
    OnClick = bStartCalcClick
  end
  object Timer1: TTimer
    Enabled = False
    OnTimer = Timer1Timer
    Left = 192
    Top = 32
  end
end
0
 
LVL 17

Expert Comment

by:Wim ten Brink
Comment Utility
Actually, it's even simpler than this. When you have a process handle then you can do a WaitForSingleObject API call to wait for the process to finish. And if you give it a timeout value of 0 instead of INFINITE then all it will do is quickly check if the process is still running before allowing you to continue your own process. So basically to check which of the processes are finished, you just call this wait function instead.
0
 
LVL 17

Expert Comment

by:Wim ten Brink
Comment Utility
Okay, very simple console application showing a good way to keep track of processes:

program Process;

{$APPTYPE CONSOLE}

uses
  Windows,
  SysUtils,
  ShellAPI;

var
  ExecInfo: TShellExecuteInfo;
  I: Integer;
  Processes: array[1..10] of THandle;
begin
  if not (ParamStr(1) = 'SKIP') then
  begin
    ZeroMemory(@Processes, SizeOf(Processes));
    for I := Low(Processes) to High(Processes) do
    begin
      WriteLn('Creating process ', I, '.');
      ZeroMemory(@ExecInfo, SizeOf(ExecInfo));
      ExecInfo.cbSize := SizeOf(ExecInfo);
      ExecInfo.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_CONNECTNETDRV or SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
      ExecInfo.Wnd := GetDesktopWindow;
      ExecInfo.lpVerb := nil;
      ExecInfo.lpFile := PChar(ParamStr(0));
      ExecInfo.lpParameters := 'SKIP';
      ExecInfo.lpDirectory := PChar(ExtractFilePath(ParamStr(0)));
      ExecInfo.nShow := SW_SHOWNORMAL;
      if ShellExecuteEx(@ExecInfo) then Processes[I] := ExecInfo.hProcess;
    end;
    Write('Press <Enter> to close all child processes process.');
    ReadLn;
    for I := Low(Processes) to High(Processes) do
    begin
      if (Processes[I] = 0) then
      begin
        WriteLn('Process ', I, ' is never started');
      end
      else if (WaitForSingleObject(Processes[I], 0) = WAIT_TIMEOUT) then
      begin
        WriteLn('Process ', I, ' is still running. KILL!!!');
        TerminateProcess(Processes[I], 0);
        CloseHandle(Processes[I]);
      end
      else
      begin
        WriteLn('Process ', I, ' has terminated already');
        CloseHandle(Processes[I]);
      end;
    end;
  end;
  Write('Press <Enter> to close this process.');
  ReadLn;
end.

Of course, you would not do ReadLns to wait for the next 'event' so this code would be divided over multiple event handlers. One event that calls CreateProcess to create the new process and add it to the list. And in the end an event that walks through the list and detects which processes are still running. Of course, you could also use a timer to do regular clean-ups of your process list by removing processes that have finished. Or maybe use threads for every process that have to wait for the process to finish and then trigger some event. There are so many options to use here.
Plenty of options to kill a process, btw. The solution used here was simply because console applications don't have a form that can respond to any window messages.

Basically, this demo will create 10 new instances when started. These child processes are started with a special commandline parameter to prevent them from spawning even more processes. This way I could keep it all in one sourcefile without dependencies to other applications. :-) It's a fully working demo.
0
 

Author Comment

by:henryreynolds
Comment Utility
Hi I am out of the office I have a look at it tonight THANX EVERYONE.............
0
 

Author Comment

by:henryreynolds
Comment Utility
Hi TheRealLoki

Thanx Its perfect, I am closing this question now and except your answer, because I want to continue still with this topic and I dont think its fair
to keep this question open. I am going to create a new question after I accept your anwser, IF YOU can help me will you please,
because it is still on the same topic but I want to simplified the whole story.

Thank you for your help

Henry
0

Featured Post

Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

Join & Write a Comment

Suggested Solutions

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
Internet Business Fax to Email Made Easy - With eFax Corporate (http://www.enterprise.efax.com), you'll receive a dedicated online fax number, which is used the same way as a typical analog fax number. You'll receive secure faxes in your email, fr…
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…

763 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now