Solved

how to determine if a shelled process has terminated ?

Posted on 1998-07-30
13
343 Views
Last Modified: 2012-06-27
Hi experts,

with the help of Delphi 3.0, I would like to start an external program several times.
It is very important for me that this *.exe is launched a second time not before the first process is terminated. In other words: The *.exe should BY NO MEANS be launched
simultanously,but consecutively. How can I find out, if a shelled process is already
terminated?

In Visual Basic, I would do it like this:


Private Sub btnEndCommand_Click()
  Dim i%
  For i = 0 To File1.ListCount - 1
    If File1.Selected(i) = True Then
      If Right$(Dir1.path, 1) = "\" Then
        Parameter = Dir1.path + File1.filename
      Else
        Parameter = Dir1.path + "\" + File1.filename
      End If
      X% = Shell("E:\MYPROGRAM.EXE  " + Parameter, 4)  
      ' Modify the path as necessary.
      While GetModuleUsage(X%) > 0
    ' Has Shelled program finished?
        z% = DoEvents() ' If not, yield to Windows.
      Wend
    End If
  Next
  MsgBox "Shelled application just terminated", 64
  File1.Refresh
End Sub

How can this be done with Delphi 3.0 ?


With kind regards

Christian
0
Comment
Question by:mathes
  • 7
  • 6
13 Comments
 
LVL 8

Accepted Solution

by:
ZifNab earned 100 total points
ID: 1359145
Hi mathes,

In Delphi, we do it like this :

uses Wintypes,WinProcs,Toolhelp,Classes,Forms;

Function WinExecAndWait(Path : string; Visibility : word) : word;
var
  InstanceID : THandle;  PathLen : integer;

begin
 { inplace conversion of a String to a PChar }  
 PathLen := Length(Path);
 Move(Path[1],Path[0],PathLen);  Path[PathLen] := #0;
 { Try to run the application }  
 InstanceID := WinExec(@Path,Visibility);
 if InstanceID < 32 then { a value less than 32 indicates an Exec error }
   WinExecAndWait := InstanceID  
  else  begin    
   Repeat
    Application.ProcessMessages;
   until Application.Terminated or (GetModuleUsage(InstanceID) = 0);
   WinExecAndWait := 32;  
  end;
end;


Zif.
0
 

Author Comment

by:mathes
ID: 1359146

Hi,

thank you for your comments.

I tried to integrate your sample code in a test application.
However it does not work as I would like to see it, my test-application unfortunately is not launched several times consecutively.

Here is the source code if my testprogram, it simply creates a pause of 5 seconds:

unit shell;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);

begin
  sleep(5000);
end;
   
end.


And here is the source code of my sample program. I simply want to launch my testprogram (test.exe) three times. But this launching of test.exe does not happen, unfortunately.

unit shell;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }

Function WinExecAndWait(Path : string; Visibility : word) : word;
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);

var

index: integer;


begin
for index:=0 to 2
do
 WinExecAndWait('test.exe',1);
end;


Function WinExecAndWait(Path : string; Visibility : word) : word;
var
  InstanceID : THandle;  PathLen : integer;

begin
 { inplace conversion of a String to a PChar }  
 PathLen := Length(Path);
 Move(Path[1],Path[0],PathLen);
 Path[PathLen] := #0;
 { Try to run the application }  
 InstanceID := WinExec(@Path,Visibility);
 if InstanceID < 32 then { a value less than 32 indicates an Exec error }
   WinExecAndWait := InstanceID  
  else  begin    
   Repeat
    Application.ProcessMessages;
   until Application.Terminated or (GetModuleUsage(InstanceID) = 0);
   WinExecAndWait := 32;  
  end;
end;                      


end.


Can you please tell me, why my sample code does not work?

With kind regards

Christian


0
 
LVL 8

Expert Comment

by:ZifNab
ID: 1359147
mathes, please explain in another way what you 'd like to achive.

The code I provided you, will launch an exe. And when the exe is stopped you can return back to your code.

So 3 times launching should be like

1) f1 launch
2) f1 stops
3) f2 launch
4) f2 stops
5) f3 launch
6) f3 stops

How does test.exe stops?
0
 

Author Comment

by:mathes
ID: 1359148
Comment
From: ZifNab Date: Friday, July 31 1998 - 05:22AM PDT  
 
mathes, please explain in another way what you 'd like to achive.

Dear ZifNab,

thank you very much for your new comments.

The external *.exe file I want to start is a math software which does some calculations.
It reads the required input data from an ASCII text file and does some very time consuming
calculations (some hours, sometimes even some days) with this data which are stored in the
ASCII text file. The input file is sent as a commandline parameter to the math program.
During the calculation, the math program writes the result of the calculation process to
another ASCII text file. The name of the output file can be found in the text file which
contains all the data required for the calculation. The name of the output file has to be
specified by the user.

Here is a detailed description, why I want to launch the math program consecutively,
instead of simultaneously:

If the math program were started at the same time several times, the following szenario
could happen: The user creates 3 different input files and in each input file, he
unfortunately specifies always exactly the same name for the output file. If now the math
program were started simultaneously several times, the following catastrophy would happen:
After the first program launch, the math program would start to calculate, open the output
file, and write the result found so far to the output file. Now meanwhile the second task
is active, the math program wants to write to the same output file. Now it tries to open a
file for writing which is already opened, and this will cause a severe runtime-error.
It is clear that it will cause problems if 2 tasks try to write to the same output file.
In order to avoid this  problem, I prefer to launch the math program consecutively. If the
first task has terminated, it is no problem at all, if the second task now tries to write
to the same output file. The new output could be easily appended to the already existing
output file.
Now I searching for a reasonable way, how I could avoid this runtime error, mentioned above.
Unfortunately one doesn't know the needed calculation time before, so it makes no sense to
launch the single calulation sessions in a certain time interval.
And - after the first task is finished- it is no good idea to wait for a keystroke or
mouseclick, before the math program is started a second time.
I would like let us say 3 calculations being done during the night while I am sleeping.
So I don't want to get up after each session and finish each session by mouse click or
key stroke.
So, it is vital, that each calculation session is started and finished AUTOMATICALLY,
without my user interaction.

Do you know a solution, how I could achieve my goal?

With kind regards

Christian

0
 
LVL 8

Expert Comment

by:ZifNab
ID: 1359149
mathes,

with the code I provided you above, does the program runs?
does it runs only the first time or doesn't run it at all?

1. try it with parameters.
2. try it without.

Zif.
0
 

Author Comment

by:mathes
ID: 1359150
Dear Zif,

thank you for your mail

Your code does not run.

The line

Move(Path[1],Path[0],PathLen);

has an error.

Delphi says that it can't access to element 0.

With kind regards

Christian


0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 8

Expert Comment

by:ZifNab
ID: 1359151
Hi mathes,

well seems that Delphi versions aren't totally compatible... for 32bit applications we better use CreateProces instead of WinExec. So try it this way :

procedure ExecNewProcess(ProgramName : String);
 var
   StartInfo  : TStartupInfo;
   ProcInfo   : TProcessInformation;
   CreateOK   : Boolean;
 begin

   { fill with known state }
   FillChar(StartInfo,SizeOf(TStartupInfo),#0);
   FillChar(ProcInfo,SizeOf(TProcessInformation),#0);
   StartInfo.cb := SizeOf(TStartupInfo);

   CreateOK := CreateProcess(nil, PChar(ProgramName), nil, nil,False,
               CREATE_NEW_PROCESS_GROUP+NORMAL_PRIORITY_CLASS,
               nil, nil, StartInfo, ProcInfo);

   { check to see if successful }
   if CreateOK then
     //may or may not be needed. Usually wait for child processes
     WaitForSingleObject(ProcInfo.hProcess, INFINITE);
 end;

{From Brendan Delumpa}

PS. If you want to know more about this 32bit CreateProcess, I advice you to look this :

How can I properly use CreateProcess to instantiate a new process?

What's a Process

Before I give you the code to execute a program in Windows with CreateProcess, I feel we should delve a bit into the concept of a what a process is. With Win32,
Microsoft changed nomenclature to help make the distinction of new concepts more clear for developers. Unfortunately, not everyone understood it - including myself
at first. In Win16 a process was the equivalent to an application. That was just fine because Windows 3.1 was (and still is) a non-preemptive multitasking system -
there's no such thing as threads.

But with the move to Win32 (Win95 and NT), many people have made the mistake of equating a thread to a process. It's not an unusual thing considering the
familiarity with an older concept. However, threads and processes are both distinct concepts and entities. Threads are children of processes; while processes, on the
other hand, are inert system entities that essentially do absolutely nothing but define a space in memory for threads to run - threads are the execution portion of a
process and a process can have many threads attached to it. That's it. I won't go into the esoteric particulars of memory locations and addressable space and the like.
Suffice it to say that processes are merely memory spaces for threads.

That said, executing a program in Win32 really means loading a process and its child thread(s) in memory. And the way you do that in Win32 is with
CreateProcess. Mind you, for backward compatibility, the Win16 calls for executing programs, WinExec and ShellExecute are still supported in the Windows API, and
still work. But for 32-bit programs, they're considered obsolete. Okay, let's dive into some code.

The following code utilizes the CreateProcess API call, and will execute any program, DOS or Windows.

{Supply a fully qualified path name in ProgramName}
procedure ExecNewProcess(ProgramName : String);
var
  StartInfo  : TStartupInfo;
  ProcInfo   : TProcessInformation;
  CreateOK   : Boolean;
begin

  { fill with known state }
  FillChar(StartInfo,SizeOf(TStartupInfo),#0);
  FillChar(ProcInfo,SizeOf(TProcessInformation),#0);
  StartInfo.cb := SizeOf(TStartupInfo);

  CreateOK := CreateProcess(PChar(ProgramName),nil, nil, nil,False,
              CREATE_NEW_PROCESS_GROUP+NORMAL_PRIORITY_CLASS,
              nil, nil, StartInfo, ProcInfo);

  { check to see if successful }
  if CreateOK then
    //may or may not be needed. Usually wait for child processes
    WaitForSingleObject(ProcInfo.hProcess, INFINITE);
end;

 Okay, while the code above works just fine for executing an application, one my readers pointed out that it doesn't work with programs that
 include a command line argument. Why? Because CreateProcess' first parameter expects a fully qualified program name (path\executable) and
 nothing else! In fact, if you include a command line in that parameter, CreateProcess will do nothing. Yikes! In that case, you have to use the
 second argument. In fact, you can use the second parameter even for just executing a program with no command line. Given that,
 ExecNewprocess would be changed as follows:

 {Supply a fully qualified path name in ProgramName
  and any arguments on the command line. As the help file
  states: "If lpApplicationName is NULL, the first white space-delimited
  token of the command line specifies the module name..." In English,
  the characters before the first space encountered (or if no space is
  encountered as in a single program call) is interpreted as the
  EXE to execute. The rest of the string is the argument line.}
 procedure ExecNewProcess(ProgramName : String);
 var
   StartInfo  : TStartupInfo;
   ProcInfo   : TProcessInformation;
   CreateOK   : Boolean;
 begin

   { fill with known state }
   FillChar(StartInfo,SizeOf(TStartupInfo),#0);
   FillChar(ProcInfo,SizeOf(TProcessInformation),#0);
   StartInfo.cb := SizeOf(TStartupInfo);

   CreateOK := CreateProcess(nil, PChar(ProgramName), nil, nil,False,
               CREATE_NEW_PROCESS_GROUP+NORMAL_PRIORITY_CLASS,
               nil, nil, StartInfo, ProcInfo);

   { check to see if successful }
   if CreateOK then
     //may or may not be needed. Usually wait for child processes
     WaitForSingleObject(ProcInfo.hProcess, INFINITE);
 end;



I know, it's a bit of complex call. And the documentation and online help aren't much help in getting information on it. I think the biggest problem people have working
with the WinAPI through Delphi is that the help topics are directed towards C/C++ programmers, not Delphi programmers. So on the fly, Delphi programmers have to
translate the C/C++ conventions to Delphi. This has caused a lot of confusion for me and others who have been exploring threads and processes. With luck, we'll see
better documentation emerge from either Borland or a third-party source.

Copyright ) 1995, 1996, 1997 Brendan V. Delumpa All Rights Reserved

I believe this will work fine. Sorry for the inconvenience.

Zif.


0
 

Author Comment

by:mathes
ID: 1359152


Dear Zif,

thank you for your hint. Will I have to add an additional unit to my "uses"
statement?

When I try my code below, Delpghi complains about an insufficient
external or forward declaration of TForm1.ExecNewProcess.

With kind regards

Christian


unit shell;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }

       
  public


procedure ExecNewProcess(ProgramName : String);
{ Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}


procedure ExecNewProcess(ProgramName : String);
 var
   StartInfo  : TStartupInfo;
   ProcInfo   : TProcessInformation;
   CreateOK   : Boolean;
 begin

   { fill with known state }
   FillChar(StartInfo,SizeOf(TStartupInfo),#0);
   FillChar(ProcInfo,SizeOf(TProcessInformation),#0);
   StartInfo.cb := SizeOf(TStartupInfo);

   CreateOK := CreateProcess(nil, PChar(ProgramName), nil, nil,False,
               CREATE_NEW_PROCESS_GROUP+NORMAL_PRIORITY_CLASS,
               nil, nil, StartInfo, ProcInfo);

   { check to see if successful }
   if CreateOK then
     //may or may not be needed. Usually wait for child processes
     WaitForSingleObject(ProcInfo.hProcess, INFINITE);
 end;

procedure TForm1.Button1Click(Sender: TObject);

var

index: integer;


begin
for index:=0 to 2
do
 ExecNewProcess('test.exe');
ShowMessage('klick me');
end;

 



end.
0
 
LVL 8

Expert Comment

by:ZifNab
ID: 1359153
Hi mathes,

You've put the

 procedure ExecNewProcess(ProgramName : String);

in the Tform- object

On the otherhand you've implemented the procedure as a global one.

Now delphi searches for an implementation of ExecNewProces in Tform and it can't find it, because you've implemented it as global procedure.

So what to do :

1. Either declare the function as global AND implement it as global :

2. Or declare the function as part of TForm AND implement it as part of TForm... (only reachable by form-object)

So Solution :

1. Global :

interface

 uses ...
         
       type
         TForm1 = class(TForm)
           Button1: TButton;
           procedure Button1Click(Sender: TObject);
         private
           { Private-Deklarationen }
         public
       { Public-Deklarationen }
         end;




   procedure ExecNewProcess(ProgramName : String);

       var
         Form1: TForm1;

   implementation

   {$R *.DFM}

procedure ExecNewProcess(ProgramName : String);
 begin
 ...
 end;

2. Or as part of TForm1 :

interface

 uses ...
         
       type
         TForm1 = class(TForm)
           Button1: TButton;
           procedure Button1Click(Sender: TObject);
         private
           { Private-Deklarationen }
         public
       { Public-Deklarationen }
         procedure ExecNewProcess(ProgramName : String); {can also be place in private declaration}

         end;






       var
         Form1: TForm1;

   implementation

   {$R *.DFM}

procedure TForm1.ExecNewProcess(ProgramName : String);
 begin
 ...
 end;

It depends on what you want.

Zif.
0
 

Author Comment

by:mathes
ID: 1359154
Hi,

thank you very much for your answer. I am very happy to inform you that now all
works perfectly! There is only one small detail I would like to know: How can I control,
if the external program is started in a full screen window, or iconized? Is there a special
parameter in the 'ExecNewProcess' routine in order to achive this?
As soon as I recieve your reply, I will rate your answer and give you the maximum of attainable
points for your brillant comments.

With kindest regards

Christian

0
 
LVL 8

Expert Comment

by:ZifNab
ID: 1359155
Hi mathes,

 that's stored in the TStartupInfo :

 TStartupInfo = record
    cb: DWORD;
    lpReserved: Pointer;
    lpDesktop: Pointer;
    lpTitle: Pointer;
    dwX: DWORD;
    dwY: DWORD;
    dwXSize: DWORD;
    dwYSize: DWORD;
    dwXCountChars: DWORD;
    dwYCountChars: DWORD;
    dwFillAttribute: DWORD;
    dwFlags: DWORD;
    wShowWindow: Word; <--- here!
    cbReserved2: Word;
    lpReserved2: PByte;
    hStdInput: THandle;
    hStdOutput: THandle;
    hStdError: THandle;
  end;

Value      Meaning
SW_HIDE      Hides the window and activates another window.
SW_MAXIMIZE      Maximizes the specified window.
SW_MINIMIZE      Minimizes the specified window and activates the next top-level window in the Z order.
SW_RESTORE      Activates and displays the window. If the window is minimized or maximized, Windows restores it to its original size and position. An application should specify this flag when restoring a minimized window.
SW_SHOW      Activates the window and displays it in its current size and position.
SW_SHOWDEFAULT      Sets the show state based on the SW_ flag specified in the STARTUPINFO structure passed to the CreateProcess function by the program that started the application. An application should call ShowWindow with this flag to set the initial show state of its main window.
SW_SHOWMAXIMIZED      Activates the window and displays it as a maximized window.
SW_SHOWMINIMIZED      Activates the window and displays it as a minimized window.
SW_SHOWMINNOACTIVE      Displays the window as a minimized window. The active window remains active.
SW_SHOWNA      Displays the window in its current state. The active window remains active.
SW_SHOWNOACTIVATE      Displays a window in its most recent size and position. The active window remains active.
SW_SHOWNORMAL      Activates and displays a window. If the window is minimized or maximized, Windows restores it to its original size and position. An application should specify this flag when displaying the window for the first time.


So for iconized or maximized I think you should do the following :


   { fill with known state }
   FillChar(StartInfo,SizeOf(TStartupInfo),#0);
   StartInfo.wShowWindow := SHOW_MINIMIZED {or SHOW_MAXIMIZED}

   FillChar(ProcInfo,SizeOf(TProcessInformation),#0);
   StartInfo.cb := SizeOf(TStartupInfo);

I wanted to paste the hole explenation of TStartupInfo, but it's a little bit much...

Regards, Zif!
0
 

Author Comment

by:mathes
ID: 1359156
Dear ZifNab,

thank you very much for your profiecient help. Now I am well informed about this subject.

With kind regards

Christian



0
 
LVL 8

Expert Comment

by:ZifNab
ID: 1359157
No problem, we are here to help each other out! c.u. in another thread!
0

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…
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…

762 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

22 Experts available now in Live!

Get 1:1 Help Now