Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Perfecting a CreateProcess call

Status
Not open for further replies.

Glenn9999

Programmer
Jun 19, 2004
2,312
US
I've seen some things in what I've been doing that makes me think that how I'm doing my CreateProcess calls could be flawed in some way. I'm not really looking for how to do it, but maybe some ideas on perfecting it. This is perhaps good since I see as many different examples of this online as there are examples posted. Basically no one does it the same way.

Here's what I do. I typically put this stuff into units and do library calls, but I figure putting it all into a program would allow easier testing with respect to making any major changes.

Code:
program testnewcp; uses windows, messages;
    type
      DWord = Integer;

    function ProcessAMsg: Boolean;
      { equivalent to TApplication.ProcessMessages }
      var
        Msg: TMsg;
        msg_proc: boolean;
      begin
        msg_proc := False;
        if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
          begin
            msg_proc := True;
            if Msg.Message <> WM_QUIT then
              begin
                TranslateMessage(Msg);
                DispatchMessage(Msg);
              end;
          end;
        ProcessAMsg := msg_proc;
      end;

    procedure ProcessMessage;
      begin
        while ProcessAMsg do;
      end;

function execute_program(ExecuteFile, paramstring: string; var ProcID: DWord;
               wait: boolean): DWord;
  var
    StartInfo  : TStartupInfo;
    ProcInfo   : TProcessInformation;
    CreateOK   : Boolean;
    ErrorCode  : DWord;
    AppDone    : DWord;

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

    CreateOK := Windows.CreateProcess(nil,
                PChar(ExecuteFile + ' ' + paramstring),
                nil, nil, False,
                CREATE_NEW_PROCESS_GROUP+IDLE_PRIORITY_CLASS+SYNCHRONIZE,
                nil, nil, StartInfo, ProcInfo);
    WaitForInputIdle(ProcInfo.hProcess, INFINITE);
    if CreateOK then
      if wait then
        begin
          repeat
            AppDone := WaitForSingleObject(ProcInfo.hProcess, 10);
            ProcessMessage;
          until AppDone <> WAIT_TIMEOUT;
          CloseHandle(ProcInfo.hProcess);
          CloseHandle(ProcInfo.hThread);
        end
      else
        procid := ProcInfo.hProcess;
    GetExitCodeProcess(ProcInfo.hProcess, ErrorCode);
    Execute_Program := ErrorCode;
  end;

  var
    procid: DWord;
  begin
    execute_program('C:\Windows\Notepad.EXE', '', procid, true);
  end.

The primary concern I have is shepherding the called program after the fact. To that end, I have coded a boolean flag that is there if I want to wait on the program to complete in my main program. This is what I'm not 100% sure of.

If you notice what I try to do is to set a loop with a delay and try to determine if the program is completed. The problem is that I want to stay out of the way of the called program while I'm doing that.

My thought was the delay loop along with a ProcessMessage call. As the comments go, TApplication.ProcessMessages is the thought I had, but I wanted a VCL-free version of it, so I copied it and removed the VCL parts. The question is if there's a way to test it so I know that it really is working for the called program.

Then perhaps the biggest question that's making me revisit this is the idea of what to do with a hung program that is started by this code. I'm not sure of what would exactly cause the program to hang (or "not respond" in Task Manager speak).

It could be the issue in #1, it could be something else. Perhaps the question is if there's an idea on how best to detect that and deal with it? My first thought was a simple timer, but I'm thinking that there has to be some way that the Task Manager is determining relevant activity that would be more elegant (sending a certain message, looking for a response?).

Then there's the question of how to handle it once detected. The only thing I can think of is to forcibly terminate the process, but I'm not sure if that's best either (and whether it would take out the entire process tree or not, assuming this called program might call something else itself?). Maybe the question in general on this one is whether or not it would be possible to do a decent job of garbage collection in such a case?

And the last and probably best question: Is there another/better approach that could possibly be taken? I know there's some nitpicks in the code that I will likely fix, but beyond that, is there a better approach to take?

Measurement is not management.
 
Glenn, this is from memory, and I don't have access to it code from where I am at the moment. The process was a large software installation and patch project to 100's of remote PCs. The modules had to be installed in sequence. Following the download InstMain.EXE was launched.

Rather than making direct 'exec's to launch each piece (say Prog1.exe), the main program (InstMain.EXE) wrote a batch file to disk, say RUN.BAT:

RUN.BAT had 2 lines:
START Prog1.exe /WAIT
echo done >done.txt

(Type "help start" at a command prompt for details of using START within batch files.)

The main program (InstMain.EXE) then banged the batch file.

Next, the main program entered a repeat loop, waiting for the existence of file 'done.txt' before proceeding to the next stage.

I'm not sure if this approach is any more or less elegant, but it worked flawlessly for us. There were of course additional ERRORLEVEL checks involved within the batch and program.


Roo
Delphi Rules!
 
RUN.BAT had 2 lines:
START Prog1.exe /WAIT
echo done >done.txt

Next, the main program entered a repeat loop, waiting for the existence of file 'done.txt' before proceeding to the next stage.

This is the real issue that I posted about the first time - essentially the posted code is doing the same thing. What happens if "Prog1.exe" hangs? The main program sits there indeterminately waiting for Prog1 to complete, which it may never and require human intervention. That's my question, if there's a real good way to cut things off if that happens?

Measurement is not management.
 
additional ERRORLEVEL checks involved within the batch"
example:

The sub-progams (Prog1.exe above) generated the ERRORLEVEL for the batch, by calling:
Halt(1);

Expanded batch example:

START Prog1.exe /WAIT
ON ERRORLEVEL 1 goto Error
echo done >done.txt
goto end
:Error
ECHO Error occurred > error.txt
:end

RE:
Next, the main program entered a repeat loop, waiting for the existence of file 'done.txt'[red] or 'error.txt'[/red] before proceeding to the next stage.

I think what you are realy interested in was the "additional error trapping within the main program":

I started a timer, allowing adequate time for each process to complete.
Code:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  TimesUp:= true;
end;

..
..
  //start process
  execute_program('Run.bat', '', procid, true);
  Timer1.Enabled:= true;
  repeat
    application.ProcessMessages
  until TimesUp or FileExists('done.txt') or FileExists('error.txt');
My batch method is redundant for a single process and wasn't really a recommendation, just showing how we handled multiple processes.

I did load your code in the IDE and trace through it. I think your code is adequate, but I would add the timer to prevent any 'infinite wait' situation. Well done!


Roo
Delphi Rules!
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top