After I create the process, I want to loop, reading the contents of the pipe
via a thread, display it to a memo (live) until the process is complete.
How do I test if the process is still running without stopping my
application with WaitForSingleObject?
Or, if someone knows of any code that will redirect the output live instead
of waiting until the process is complete that would be awesome.
Richard
> After I create the process, I want to loop, reading the contents of the
> pipe
> via a thread, display it to a memo (live) until the process is complete.
two things:
1. you can test if the pipe has data with the PeekNamedPipe call. it seems
strange, but in contradiction with its name, this API does work with
anonymous
pipes as well. you need that because reading the pipe waits indefinitely,
rendering your program dead.
2. WaitForSingleObject has a timeout parameter. set it to 10ms or 50ms, as
you
see fit. check the pipe, check any other stuff that needs to be checked,
and
wait again. in that loop, you will need to call Application.ProcessMessages
so WM_PAINT messages arrive, and user can interact. BEWARE! A.PM needs some
rethinking of program logic. as an alternative, you can simply update the
form with TForm.Update. if you omit these, you will not see the memo
updating
its content.
> you need that because reading the pipe waits indefinitely,
> rendering your program dead.
You can use overlapped I/O on the pipe to avoid that.
Gambit
> How do I test if the process is still running without stopping
> my application with WaitForSingleObject?
What's wrong with using WaitForSingleObject()? Simply give it a small
timeout so you can check the message queue frequently for new messages.
Better would be to use MsgWaitForMultipleObjects() instead. That way, you
can wait on both the spawned process and the message queue at the same time,
and react accordingly to whichever one generates a signal.
Gambit
Rather than writing your own message handling loop, you can also use a
thread to listen to the pipe. You'll need to make a separate function to
update the memo and call it with Synchronize(), as the VCL is not
thread-safe. The function will likely be on the form/frame that the
memo's on, and set to an event on the thread when the thread's created.
-Mike
Hello Richard
I can provide an example using threads to listen to the stdoutput, it
adds the output to a "Content" string, but you could also update your
memo component. As long as "ReadFile" don't return 0 bytes read, you
know that the pipe is still open and the process is running.
http://www.martinstoeckli.ch/delphi/delphi.html#AppRedirectOutput
Good luck:
Martin
--
Martin Stoeckli
http://www.martinstoeckli.ch/delphi
procedure RunDosInMemo(DosApp:String;AMemo:TMemo) ;
const
ReadBuffer = 2400;
var
Security : TSecurityAttributes;
ReadPipe,WritePipe : THandle;
start : TStartUpInfo;
ProcessInfo : TProcessInformation;
Buffer : Pchar;
BytesRead : DWord;
Apprunning : DWord;
x : Integer;
MyStr : String;
begin
x := 0;
With Security do
begin
nlength := SizeOf(TSecurityAttributes) ;
binherithandle := true;
lpsecuritydescriptor := nil;
end;
if Createpipe (ReadPipe, WritePipe,
@Security, 0) then
begin
Buffer := AllocMem(ReadBuffer + 1) ;
FillChar(Start,Sizeof(Start),#0) ;
start.cb := SizeOf(start) ;
start.hStdOutput := WritePipe;
start.hStdInput := ReadPipe;
start.dwFlags := STARTF_USESTDHANDLES +
STARTF_USESHOWWINDOW;
start.wShowWindow := SW_HIDE;
if CreateProcess(nil,
PChar(DosApp),
@Security,
@Security,
true,
NORMAL_PRIORITY_CLASS,
nil,
nil,
start,
ProcessInfo)
then
begin
repeat
Inc(x);
Apprunning := WaitForSingleObject
(ProcessInfo.hProcess,100) ;
Application.ProcessMessages;
BytesRead := 0;
ReadFile(ReadPipe,Buffer[0],ReadBuffer,BytesRead,nil) ;
Buffer[BytesRead]:= #0;
OemToAnsi(Buffer,Buffer) ;
MyStr := '';
for x := 0 to BytesRead do
Begin
If Buffer[x] = #13 then
MyStr := MyStr + #13#10 else
If Buffer[x] = #10 then
MyStr := MyStr + '' else
MyStr := MyStr + buffer[x];
End;
AMemo.Lines.Add(MyStr);
AMemo.Refresh;
Application.ProcessMessages;
until (Apprunning <> WAIT_TIMEOUT) ;
FreeMem(Buffer) ;
CloseHandle(ProcessInfo.hProcess) ;
CloseHandle(ProcessInfo.hThread) ;
CloseHandle(ReadPipe) ;
CloseHandle(WritePipe) ;
end;
end;
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
RunDosInMemo('chkdsk.exe c:',memo1) ;
end;
That's risky. If you don't read anything from the pipe until the process
terminates, then the pipe might fill up. Once the pipe fills up, the
writer will block, waiting for someone to read from the pipe and make
room for more data. If you're blocked waiting for the process to end,
and the process is blocked waiting for you to read, then you have deadlock.
If this has worked for you so far, then it's probably because the
process doesn't generate enough output to fill the pipe's buffer.
> But what I need is to update the memo or whatever live,
> or at least every 100 milliseconds or so while the process is running.
>
> After I create the process, I want to loop, reading the contents of the pipe
> via a thread, display it to a memo (live) until the process is complete.
>
> How do I test if the process is still running without stopping my
> application with WaitForSingleObject?
If the process has terminated, then the write end of the pipe will get
closed. Subsequent calls to ReadFile will fail with Error_Broken_Pipe.
--
Rob
please show me a reference to it, because as i know, it is not possible
type
PStoReadPipeThreadParam = ^TStoReadPipeThreadParam;
TStoReadPipeThreadParam = record
Pipe: THandle;
Content: String;
end;
function Sto_ReadPipeThreadProc(Parameter: PStoReadPipeThreadParam):
Integer;
const
BLOCK_SIZE = 4096;
var
iBytesRead: DWORD;
szBuffer: array[0..BLOCK_SIZE-1] of Char;
PBuffer : ^szBuffer;
begin
Result := 0;
repeat
if ReadFile(Parameter^.Pipe, szBuffer, BLOCK_SIZE, iBytesRead, nil) then
Begin
new(PBuffer);
PBuffer^ := szBuffer;
Postmessage(MainForm.Handle,MyThreadMsg,MyConsoleMsg,Integer(PBuffer));
End;
until (iBytesRead = 0);
end;
"Martin Stoeckli" <martinst...@gmx.ch> wrote in message
news:486a...@newsgroups.borland.com...
> I can provide an example using threads to listen to the stdoutput, it
> adds the output to a "Content" string, but you could also update your
> http://www.martinstoeckli.ch/delphi/delphi.html#AppRedirectOutput
The code in the example uses threads only to avoid a blocked pipe after
the "ReadFile" function, this is possible because there are two pipes
reading stdoutput and stderror separately. The function itself doesn't
return before the child process is closed.
If you want to collect all output into the same memo control, then you
can do without the second pipe (hPipeErrorRead, hPipeErrorWrite) and
without the second thread (hThreadErrorRead). Use the same pipe for both:
myStartupInfo.hStdInput := 0;
myStartupInfo.hStdOutput := hPipeOutputWrite;
myStartupInfo.hStdError := hPipeOutputWrite;
This way you won't have any threading problems, the main function
"Sto_RedirectedExecute" waits with "WaitForSingleObject" for the end of
the child process and the loop inside your thread procedure is
serialized anyway.
Instead of a "Content" string you can also place your component into the
parameter, so you can work directly with the memo control after each
call to ReadFile .
TStoReadPipeThreadParam = record
Pipe: THandle;
Memo: TMemo;
end;
Hope this helps:
Martin
Richard schrieb:
> myStartupInfo.hStdInput := 0;
> myStartupInfo.hStdOutput := hPipeOutputWrite;
> myStartupInfo.hStdError := hPipeOutputWrite;
little remark here. there are misbehaving programs that does not want to
use
the error output, so start with closing the error handle. no kidding, there
are such programs. since you give the same handle to both place, these
programs
cut themselves off of the pipe.
to work around that "wisdom", you can use DuplicateHandle to make a copy
of the
write-handle. this way, if the program prematurely closes the error handle,
the stdout handle will still be open, and pointing to the same pipe
entrance.
Good to know, i didn't think of that.
Thanks:
Martin