The process won't wait? - delphi

the code i use is as below:
begin
saSecurity.nLength := SizeOf(TSecurityAttributes);
saSecurity.bInheritHandle := True;
saSecurity.lpSecurityDescriptor := nil;
FillChar(suiStartup, SizeOf(TStartupInfo), #0);
suiStartup.cb := SizeOf(TStartupInfo);
suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
suiStartup.wShowWindow := SW_HIDE;
ccOk:=CreateProcess(nil, PChar(ExecutableFirst+' '+CommandsFirst),#saSecurity,#saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess);
if ccOk then
begin
CreateProcess(nil, PChar(ExecutableSecond + ' ' + CommandsSecond), #saSecurity,#saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess);
try
repeat Running:=MsgWaitForMultipleObjects(2,piProcess.hProcess,True,100,QS_ALLINPUT);
Application.ProcessMessages;
until Running <> WAIT_TIMEOUT
finally
CloseHandle(piProcess.hProcess);
CloseHandle(piProcess.hThread);
if (Running=WAIT_OBJECT_0) then BidsConversion; //run this when both process has finished
end;
end else
begin
raise.Exception(GetLastError.toString);
Exit
end;
end;
The Code working but sometimes it is firing BidsConversion however the First Process is still not completed hence it Exception raised.
Why the app is not waiting for both processes to finish then fire the procedure ?

You are not checking the return value of the 2nd CreateProcess() to see if it failed, but more importantly you are completely misusing MsgWaitForMultipleObjects():
you are not passing both process handles to MsgWaitForMultipleObjects() even though you are setting its nCount parameter to 2.
you are calling ProcessMessages() unconditionally, even when MsgWaitForMultipleObjects() does not tell you that messages are waiting to be processed.
your loop's until clause is checking for the wrong termination value, so your loop will break prematurely on ANY condition that is not a timeout, eg: when either process finishes, or when a message is pending in the queue.
there is an important caveat with setting the bWaitAll parameter to True that you need to be aware of - see MsgWaitForMultipleObjects is a very tricky API on MSDN for details about that.
With that said, try something more like this:
var
...
arrHandles: array[0..1] of THandle;
numHandles, i: Integer;
begin
...
ccOk := CreateProcess(nil, PChar(ExecutableFirst + ' ' + CommandsFirst), #saSecurity, #saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess);
if not ccOk then
RaiseLastOSError;
CloseHandle(piProcess.hThread);
arrHandles[0] := piProcess.hProcess;
numHandles := 1;
try
ccOk := CreateProcess(nil, PChar(ExecutableSecond + ' ' + CommandsSecond), #saSecurity, #saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess);
if not ccOk then
RaiseLastOSError;
CloseHandle(piProcess.hThread);
arrHandles[1] := piProcess.hProcess;
numHandles := 2;
// there is a caveat when setting bWaitAll=True that the wait will not be
// completely satisfied until both handles are signaled AND the calling thread
// receives an input event! That last caveat is not desirable, so setting
// bWaitAll=False instead to avoid that so the loop can break immediately when
// both handles are signaled...
repeat
Running := MsgWaitForMultipleObjects(numHandles, arrHandles, False, INFINTE, QS_ALLINPUT);
if {(Running >= WAIT_OBJECT_0) and} (Running < (WAIT_OBJECT_0 + DWORD(numHandles))) then
begin
i := Integer(Running - WAIT_OBJECT_0);
CloseHandle(arrHandles[i]);
if i = 0 then arrHandles[0] := arrHandles[1];
Dec(numHandles);
end
else if Running = (WAIT_OBJECT_0 + DWORD(numHandles)) then begin
Application.ProcessMessages;
end
else if Running = WAIT_FAILED then begin
RaiseLastOSError;
end;
until numHandles = 0;
except
for i := 0 to numHandles-1 do begin
TerminateProcess(arrHandles[i], 0);
CloseHandle(arrHandles[i]);
end;
raise;
end;
BidsConversion; //run this when both processes have finished without error
...
end;
That being said, consider doing the wait asynchronously in a separate worker thread so you are not blocking the main UI thread anymore. You can create your own thread that calls WaitForMultipleObjects() (not MsgWaitForMultipleObjects() since you would not need to wait on the message queue anymore), or you can use RegisterWaitForSingleObject() on each process handle individually. Either way, let the worker thread(s) notify the main UI thread when waiting is finished, and just don't call BidsConversion() until you have received notification that both processes have finished.

Related

Capture coloured console output from multiple sources

I wrote a console application that is able to execute multiple commands on the command line in parallel.
Primarily I did this out of interest and because the build processes of the software projects I am working on make excessive use of the command line.
Currently, before I create a child process in a worker thread, I create an anonymous pipe in order to capture all the output the child process creates during its lifetime.
After the child process terminates, the worker thread pushes the captured content to the waiting main process that then prints it out.
Here's my process creations and capturing:
procedure ReadPipe(const ReadHandle: THandle; const Output: TStream);
var
Buffer: TMemoryStream;
BytesRead, BytesToRead: DWord;
begin
Buffer := TMemoryStream.Create;
try
BytesRead := 0;
BytesToRead := 0;
if PeekNamedPipe(ReadHandle, nil, 0, nil, #BytesToRead, nil) then
begin
if BytesToRead > 0 then
begin
Buffer.Size := BytesToRead;
ReadFile(ReadHandle, Buffer.Memory^, Buffer.Size, BytesRead, nil);
if Buffer.Size <> BytesRead then
begin
Buffer.Size := BytesRead;
end;
if Buffer.Size > 0 then
begin
Output.Size := Output.Size + Buffer.Size;
Output.WriteBuffer(Buffer.Memory^, Buffer.Size);
end;
end;
end;
finally
Buffer.Free;
end;
end;
function CreateProcessWithRedirectedOutput(const AppName, CMD, DefaultDir: PChar; out CapturedOutput: String): Cardinal;
const
TIMEOUT_UNTIL_NEXT_PIPEREAD = 100;
var
SecurityAttributes: TSecurityAttributes;
ReadHandle, WriteHandle: THandle;
StartupInfo: TStartupInfo;
ProcessInformation: TProcessInformation;
ProcessStatus: Cardinal;
Output: TStringStream;
begin
Result := 0;
CapturedOutput := '';
Output := TStringStream.Create;
try
SecurityAttributes.nLength := SizeOf(SecurityAttributes);
SecurityAttributes.lpSecurityDescriptor := nil;
SecurityAttributes.bInheritHandle := True;
if CreatePipe(ReadHandle, WriteHandle, #SecurityAttributes, 0) then
begin
try
FillChar(StartupInfo, Sizeof(StartupInfo), 0);
StartupInfo.cb := SizeOf(StartupInfo);
StartupInfo.hStdOutput := WriteHandle;
StartupInfo.hStdError := WriteHandle;
StartupInfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
StartupInfo.dwFlags := STARTF_USESTDHANDLES;
if CreateProcess(AppName, CMD,
#SecurityAttributes, #SecurityAttributes,
True, NORMAL_PRIORITY_CLASS,
nil, DefaultDir,
StartupInfo, ProcessInformation)
then
begin
try
repeat
ProcessStatus := WaitForSingleObject(ProcessInformation.hProcess, TIMEOUT_UNTIL_NEXT_PIPEREAD);
ReadPipe(ReadHandle, Output);
until ProcessStatus <> WAIT_TIMEOUT;
if not Windows.GetExitCodeProcess(ProcessInformation.hProcess, Result) then
begin
Result := GetLastError;
end;
finally
Windows.CloseHandle(ProcessInformation.hProcess);
Windows.CloseHandle(ProcessInformation.hThread);
end;
end
else
begin
Result := GetLastError;
end;
finally
Windows.CloseHandle(ReadHandle);
Windows.CloseHandle(WriteHandle);
end;
end
else
begin
Result := GetLastError;
end;
CapturedOutput := Output.DataString;
finally
Output.Free;
end;
end;
My problem now:
This method doesn't preserve potential coloring of the captured output!
I came accross this topic Capture coloured console output into WPF application but that didn't help me out, as I don't receive any color data through the anonymous pipe, just plain old text.
I experimented with inheriting the console of the main process to the child processes via CreateFile with 'CONOUT$', but while the colors are indeed preserved, you probably can guess that its pure mayhem if more than one process prints out its contents into one and the same console.
My next approach was to create additional console buffers with CreateConsoleScreenBuffer for each child process and read the contents with ReadConsole, but that wasn't successful as ReadConsole returns with System Error 6 (ERROR_INVALID_HANDLE).
ConsoleHandle := CreateConsoleScreenBuffer(
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE,
#SecurityAttributes,
CONSOLE_TEXTMODE_BUFFER,
nil);
//...
StartupInfo.hStdOutput := ConsoleHandle;
StartupInfo.hStdError := ConsoleHandle;
//...
ConsoleOutput := TMemoryStream.Create
ConsoleOutput.Size := MAXWORD;
ConsoleOutput.Position := 0;
ReadConsole(ConsoleHandle, ConsoleOutput.Memory, ConsoleOutput.Size, CharsRead, nil) // Doesn't read anything and returns with System Error Code 6.
I also read up on virtual terminal sequences and AllocConsole, AttachConsole and FreeConsole, but can't quite wrap my head around it for my use case.
What is the right/best way to preserve/receive coloring information of the console output of a child process?
I was on the right track with CreateConsoleScreenBuffer and giving each thread its own console screen buffer.
The problem was ReadConsole which doesn't do what I expected.
I now got it working with ReadConsoleOutput.
It should be noted however, that this method is the legacy way of doing it.
If you want to do it the "new way" you should probably use Pseudo Console Sessions.
Its support starts with Windows 10 1809 and Windows Server 2019.
It should also be noted, that the method of reading the output of a process/program via console screen buffer has its flaws and two distinct disadvantages compared to anonymous pipes:
The console screen buffer can't get full and block the process/program, but if the end of it is reached, new lines will push the current first line out of the buffer.
Output from processes/programs that spam their std output in a fast fashion will most likely lead to loss of information, as you won't be able to read, clear and move the cursor in the console screen buffer fast enough.
I try to circumvent both by increasing the console screen buffers y size component to its maximum possible size (I found it to be MAXSHORT - 1) and just wait until the process/program has finished.
That's good enough for me, as I don't need to analyze or process the colored output, but just display it in a console window, which is itself limited to MAXSHORT - 1 lines.
In every other scenario I will be using pipes and advise everyone else to do so too!
Here is a short version without any error handling that can be executed in parallel without interference (provided the TStream object is owned by the thread or thread-safe):
procedure CreateProcessWithConsoleCapture(const aAppName, aCMD, aDefaultDir: PChar;
const CapturedOutput: TStream);
const
CONSOLE_SCREEN_BUFFER_SIZE_Y = MAXSHORT - 1;
var
SecurityAttributes: TSecurityAttributes;
ConsoleHandle: THandle;
StartupInfo: TStartupInfo;
ProcessInformation: TProcessInformation;
CharsRead: Cardinal;
BufferSize, Origin: TCoord;
ConsoleScreenBufferInfo: TConsoleScreenBufferInfo;
Buffer: array of TCharInfo;
ReadRec: TSmallRect;
begin
SecurityAttributes.nLength := SizeOf(SecurityAttributes);
SecurityAttributes.lpSecurityDescriptor := Nil;
SecurityAttributes.bInheritHandle := True;
ConsoleHandle := CreateConsoleScreenBuffer(
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE,
#SecurityAttributes,
CONSOLE_TEXTMODE_BUFFER,
nil);
try
GetConsoleScreenBufferInfo(ConsoleHandle, ConsoleScreenBufferInfo);
BufferSize.X := ConsoleScreenBufferInfo.dwSize.X;
BufferSize.Y := CONSOLE_SCREEN_BUFFER_SIZE_Y;
SetConsoleScreenBufferSize(ConsoleHandle, BufferSize);
Origin.X := 0;
Origin.Y := 0;
FillConsoleOutputCharacter(ConsoleHandle, #0, BufferSize.X * BufferSize.Y, Origin, CharsRead);
SetStdHandle(STD_OUTPUT_HANDLE, ConsoleHandle);
FillChar(StartupInfo, Sizeof(StartupInfo), 0);
StartupInfo.cb := SizeOf(StartupInfo);
StartupInfo.hStdOutput := ConsoleHandle;
StartupInfo.hStdError := ConsoleHandle;
StartupInfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
StartupInfo.dwFlags := STARTF_USESTDHANDLES or STARTF_FORCEOFFFEEDBACK;
CreateProcess(aAppName, aCMD,
#SecurityAttributes, #SecurityAttributes,
True, NORMAL_PRIORITY_CLASS,
nil, aDefaultDir,
StartupInfo, ProcessInformation);
try
WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
GetConsoleScreenBufferInfo(ConsoleHandle, ConsoleScreenBufferInfo);
BufferSize.X := ConsoleScreenBufferInfo.dwSize.X;
BufferSize.Y := ConsoleScreenBufferInfo.dwCursorPosition.Y;
if ConsoleScreenBufferInfo.dwCursorPosition.X > 0 then
begin
Inc(BufferSize.Y);
end;
ReadRec.Left := 0;
ReadRec.Top := 0;
ReadRec.Right := BufferSize.X - 1;
ReadRec.Bottom := BufferSize.Y - 1;
SetLength(Buffer, BufferSize.X * BufferSize.Y);
ReadConsoleOutput(ConsoleHandle, #Buffer[0], BufferSize, Origin, ReadRec);
CharsRead := SizeOf(TCharInfo) * (ReadRec.Right - ReadRec.Left + 1) * (ReadRec.Bottom - ReadRec.Top + 1);
if CharsRead > 0 then
begin
CapturedOutput.Size := CapturedOutput.Size + CharsRead;
CapturedOutput.WriteBuffer(Buffer[0], CharsRead);
end;
finally
CloseHandle(ProcessInformation.hProcess);
CloseHandle(ProcessInformation.hThread);
end;
finally
CloseHandle(ConsoleHandle);
end;
end;

CreateProcess, PowerShell and WaitForSingleObject

I'm working with pipes to get the cmd.exe output inside my program. Sometimes, I noted that if the cmd.exe ask for user input (I create hidden cmd window), the program hangs, because nobody will put the input in the window, and the cmd will just stay. So I implemented WaitForSingleObject to avoid hang on the cases where cmd asks for user input or just hang for another reason. The problem comes when I try to execute powershell commands, because it looks unresponsive for WaitForSingleObject, and I always reach the timeout. The function is:
function GetDosOutput(const Exe, Param: string): string;
const
InheritHandleSecurityAttributes: TSecurityAttributes =
(nLength: SizeOf(TSecurityAttributes); bInheritHandle: True);
var
hReadStdout, hWriteStdout: THandle;
si: TStartupInfo;
pi: TProcessInformation;
WaitTimeout, BytesRead: DWord;
lReadFile: boolean;
Buffer: array[0..255] of AnsiChar;
begin
Result:= '';
if CreatePipe(hReadStdout, hWriteStdout, #InheritHandleSecurityAttributes, 0) then
begin
try
si:= Default(TStartupInfo);
si.cb:= SizeOf(TStartupInfo);
si.dwFlags:= STARTF_USESTDHANDLES;
si.hStdOutput:= hWriteStdout;
si.hStdError:= hWriteStdout;
if CreateProcess(Nil, PChar(Exe + ' ' + Param), Nil, Nil, True, CREATE_NO_WINDOW,
Nil, PChar(ExtractFilePath(ParamStr(0))), si, pi) then
begin
CloseHandle(hWriteStdout);
while True do
begin
try
WaitTimeout:= WaitForSingleObject(pi.hProcess, 20000);
if WaitTimeout = WAIT_TIMEOUT then
begin
Result:= 'No result available';
break;
end
else
begin
repeat
lReadFile:= ReadFile(hReadStdout, Buffer, SizeOf(Buffer) - 1, BytesRead, nil);
if BytesRead > 0 then
begin
Buffer[BytesRead]:= #0;
OemToAnsi(Buffer, Buffer);
Result:= Result + String(Buffer);
end;
until not (lReadFile) or (BytesRead = 0);
end;
if WaitTimeout = WAIT_OBJECT_0 then
break;
finally
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
end;
end;
end;
finally
CloseHandle(hReadStdout);
end;
end;
end;
If I call this function passing:
cmd.exe /C dir c:\
It goes alright. But if I call using:
powershell dir c:\ or cmd.exe /C powershell dir c:\
The WaitForSingleObject reaches the timeout, and nothing happens. Any help on this one?
The pipe's buffer is probably full. The child process is blocked, waiting for your process to read from the pipe and make room for more output. However, your program is also blocked, waiting for the child process to complete. Thus, deadlock.
You need to keep reading from the pipe, but the problem is that if you call ReadFile and the process hangs for some other reason than a full pipe buffer, then your program hangs, too. ReadFile doesn't offer a timeout parameter.
ReadFile doesn't have a timeout parameter because asynchronous reads are done instead using overlapped I/O. You pass to ReadFile a TOverlapped record that includes a Windows event handle. ReadFile will return immediately, and it will signal the event when the read has finished. Use WaitForMultipleObjects to wait on not only the process handle but also this new event handle.
There's a snag, though. CreatePipe creates anonymous pipes, and anonymous pipes don't support overlapped I/O. Therefore, you'll have to use CreateNamedPipe instead. Generate a unique name for the pipe at run time so it won't interfere with any other programs (including additional instances of your program).
Here's a sketch of how the code could go:
var
Overlap: TOverlapped;
WaitHandles: array[0..1] of THandle;
begin
hReadStdout := CreateNamedPipe('\\.\pipe\unique-pipe-name-here',
Pipe_Access_Inbound, File_Flag_First_Pipe_Instance or File_Flag_Overlapped,
Pipe_Type_Byte or Pipe_Readmode_Byte, 1, x, y, 0, nil);
Win32Check(hReadStdout <> Invalid_Handle_Value);
try
hWriteStdout := CreateFile('\\.\pipe\unique-pipe-name-here', Generic_Write,
#InheritHandleSecurityAttributes, ...);
Win32Check(hWriteStdout <> Invalid_Handle_Value);
try
si.hStdOutput := hWriteStdout;
si.hStdError := hWriteStdout;
Win32Check(CreateProcess(...));
finally
CloseHandle(hWriteStdout);
end;
try
Overlap := Default(TOverlapped);
Overlap.hEvent := CreateEvent(nil, True, False, nil);
Win32Check(Overlap.hEvent <> 0);
try
WaitHandles[0] := Overlap.hEvent;
WaitHandles[1] := pi.hProcess;
repeat
ReadResult := ReadFile(hReadStdout, ..., #Overlap);
if ReadResult then begin
// We read some data without waiting. Process it and go around again.
SetString(NewResult, Buffer, BytesRead div SizeOf(Char));
Result := Result + NewResult;
continue;
end;
Win32Check(GetLastError = Error_IO_Pending);
// We're reading asynchronously.
WaitResult := WaitForMultipleObjects(Length(WaitHandles),
#WaitHandles[0], False, 20000);
case WaitResult of
Wait_Object_0: begin
// Something happened with the pipe.
ReadResult := GetOverlappedResult(hReadStdout, #Overlap, #BytesRead, True);
// May need to check for EOF or broken pipe here.
Win32Check(ReadResult);
SetString(NewResult, Buffer, BytesRead div SizeOf(Char));
Result := Result + NewBuffer;
ResetEvent(Overlap.hEvent);
end;
Wait_Object_0 + 1: begin
// The process terminated. Cancel the I/O request and move on,
// returning any data already in Result. (There's no further data
// in the pipe, because if there were, WaitForMultipleObjects would
// have returned Wait_Object_0 instead. The first signaled handle
// determines the return value.
CancelIO(hReadStdout);
break;
end;
Wait_Timeout: begin
// Timeout elapsed without receiving any more data.
Result := 'no result available';
break;
end;
Wait_Failed: Win32Check(False);
else Assert(False);
end;
until False;
finally
CloseHandle(Overlap.hEvent);
end;
finally
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
end;
finally
CloseHandle(hReadStdout);
end;
end;
Note that in the above code, any new output from the program will essentially reset the 20-second timeout you allotted for the process to finish. That might be acceptable behavior, but if not, then you'll have to keep track of how much time has already elapsed and adjust the timeout value prior to calling WaitForMultipleObjects (and perhaps prior to calling ReadFile, too, in case the OS opts to handle ReadFile non-overlapped, which it might do if there's already data available when you call it).

Make Chrome open on second monitor?

I'm currently trying to make an app that forces a Chrome window to open on my second monitor but I can't find anyway to do it using arguments, now I'm wondering if I can somehow use Delphi to force it to open on the second screen or a specific pixel? This is solely an app for myself and my PC so I can put the code in specific for my case.
I'm currently using this bit of code to start the app
procedure TForm1.BtnClick(Sender: TObject);
begin
ExecProcess(ChromePath,'',False);
end;
function ExecProcess(ProgramName, WorkDir: string; Wait: boolean): integer;
var
StartInfo: TStartupInfo;
ProcInfo: TProcessInformation;
CreateOK: boolean;
ExitCode: integer;
dwExitCode: DWORD;
begin
ExitCode := -1;
FillChar(StartInfo, SizeOf(TStartupInfo), #0);
FillChar(ProcInfo, SizeOf(TProcessInformation), #0);
StartInfo.cb := SizeOf(TStartupInfo);
if WorkDir <> '' then
begin
CreateOK := CreateProcess(nil, Addr(ProgramName[1]), nil, Addr(WorkDir[1]),
false, CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS, nil, nil,
StartInfo, ProcInfo);
end
else
begin
CreateOK := CreateProcess(nil, Addr(ProgramName[1]), nil, nil, false,
CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS, nil, Addr(WorkDir[1]),
StartInfo, ProcInfo);
end;
{ check to see if successful }
if CreateOK then
begin
// may or may not be needed. Usually wait for child processes
if Wait then
begin
WaitForSingleObject(ProcInfo.hProcess, INFINITE);
GetExitCodeProcess(ProcInfo.hProcess, dwExitCode);
ExitCode := dwExitCode;
end;
end
else
begin
ShowMessage('Unable to run ' + ProgramName);
end;
CloseHandle(ProcInfo.hProcess);
CloseHandle(ProcInfo.hThread);
Result := ExitCode;
end;
Can I somehow use something in StartInfo.wShowWindow maybe?
Chrome allows you to pass a position and size on the command line with --window-position and --window-size I believe. Check out this page for details.
Example:
:: Left screen is 1024x768
"C:\chrome.exe" "https://www.example.com/?a=0&b=1" --window-position=0,0 --window-size=1024,768 --user-data-dir="C:\my-chrome1"
:: Right screen is 1280x720
:: Now chrome.exe we need to open in the second screen then we do it as below:
:: we might want to use --kiosk but combination of --kiosk and --window-position wont work so in that case we can use --app
"C:\chrome.exe" --app="https://www.example.com/?a=0&b=1" --window-position=1025,0 --window-size=1280,720 --user-data-dir="C:\my-chrome2"

How to read error running Console Application from Delphi Code using PeekNamedPipe()

I have Delphi code which is used to run Java.exe from JAVA_HOME with a given set of parameters. This is achieved through CreateProcess() by passing Command Line command to it. For a particualr set of input parameters, this gives me an error which says "Could not create Java Virtual Machine". I need to get this through PeekNamedPipe() into Delphi code and display it in the application. How can I achieve this?
The Delphi code looks like this:
begin
securityAttr.nlength := SizeOf(TSecurityAttributes);
securityAttr.binherithandle := true;
securityAttr.lpsecuritydescriptor := nil;
if CreatePipe (readPipe, writePipe, #securityAttr, 0) then
begin
buffer := AllocMem(READ_BUFFER + 1);
FillChar(startInfo, Sizeof(startInfo), #0);
startInfo.cb := SizeOf(startInfo);
startInfo.hStdOutput := writePipe;
startInfo.hStdInput := readPipe;
startInfo.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
startInfo.wShowWindow := SW_HIDE;
if SearchPath(nil, PChar(consoleApp), '.exe', SizeOf(consoleAppBuf), consoleAppBuf, exeName) = 0 then
raise EInOutError.CreateFmt('Could not find file %s', [consoleApp]);
FmtStr(commandLine, '"%s" %s', [consoleAppBuf, parameters]);
if CreateProcess(nil, PChar(commandLine), nil, nil, true, CREATE_NO_WINDOW, nil, nil, startInfo, processInfo) then
begin
totalBytesRead := 0;
repeat
exitCode := WaitForSingleObject(ProcessInfo.hProcess, 100);
Application.ProcessMessages;
if (PeekNamedPipe(readPipe, #buffer[totalBytesRead],
READ_BUFFER, #bytesRead,
#totalBytesAvail, #bytesLeftThisMessage)) then
begin
if (bytesRead > 0) then
ReadFile(readPipe, buffer[totalBytesRead], bytesRead, bytesRead, nil);
totalBytesRead := totalBytesRead + bytesRead;
end;
until (exitCode <> WAIT_TIMEOUT);
GetExitCodeProcess(ProcessInfo.hProcess, result);
buffer[totalBytesRead]:= #0;
OemToChar(buffer, buffer);
output.Text := output.Text + StrPas(buffer);
FreeMem(buffer);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
CloseHandle(readPipe);
CloseHandle(writePipe);
end
else
RaiseLastWin32Error;
end;
How can I read the error thrown by java.exe?
It is a mistake to connect both ends of the same pipe to the input and output of the same process. You are feeding the output of the process back into its input. Leave hStdInput as NULL.
You also play fast and loose with error checking, and run the risk of leaking handles because you don't use finally blocks. And I'm not keen on the busy loop. Or the call to ProcessMessages.
Leaving that all aside, the likely reason for you not reading errors is that they go to stderr. Connect the write end of your pipe to stderr, as well as to stdout:
startInfo.hStdOutput := writePipe;
startInfo.hStdError := writePipe;

How to create a Child process depending on it's Parent?

My application (main.exe) is executing a Child process (child.exe) using ShellExecuteEx.
But when I close or kill (via Process-Explorer) main.exe the child process remains active.
How to gracefully handle that, when main.exe terminates child.exe terminates also?
You need to use jobs. Main executable should create a job object, then you'll need to set JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag to your job object.
uses
JobsApi;
//...
var
jLimit: TJobObjectExtendedLimitInformation;
hJob := CreateJobObject(nil, PChar('JobName');
if hJob <> 0 then
begin
jLimit.BasicLimitInformation.LimitFlags := JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, #jLimit,
SizeOf(TJobObjectExtendedLimitInformation));
end;
Then you need to execute another process with CreateProcess function where dwCreationFlags must be set to CREATE_BREAKAWAY_FROM_JOB. If this function succeeds call AssignProcessToJobObject.
function ExecuteProcess(const EXE : String; const AParams: string = ''; AJob: Boolean = True): THandle;
var
SI : TStartupInfo;
PI : TProcessInformation;
AFlag: Cardinal;
begin
Result := INVALID_HANDLE_VALUE;
FillChar(SI,SizeOf(SI),0);
SI.cb := SizeOf(SI);
if AJob then
AFlag := CREATE_BREAKAWAY_FROM_JOB
else
AFlag := 0;
if CreateProcess(
nil,
PChar(EXE + ' ' + AParams),
nil,
nil,
False,
AFlag,
nil,
nil,
SI,
PI
) then
begin
{ close thread handle }
CloseHandle(PI.hThread);
Result := PI.hProcess;
end;
end;
//...
hApp := ExecuteProcess('PathToExecutable');
if hApp <> INVALID_HANDLE_VALUE then
begin
AssignProcessToJobObject(hJob, hApp);
end;
When all of this done all the child processes will be automatically terminated even if the main executable has been killed. You can get the JobsApi unit here. Note: I've not tested it with Delphi 7.
EDIT: Here you can download working demo project.
Try using Job Objects , check these functions CreateJobObject and AssignProcessToJobObject.
A job object allows groups of processes to be managed as a unit. Job
objects are namable, securable, shareable objects that control
attributes of the processes associated with them. Operations performed
on a job object affect all processes associated with the job object.
Examples include enforcing limits such as working set size and process
priority or terminating all processes associated with a job.
I think, it's very cool code. It's working for me, but I add some changes to be able user to set show window flags for child processes like SW_SHOW/SW_HIDE.
...
function ExecuteProcess(const EXE : String; const AParams: string = '';
const nCmdShow: Integer = SW_SHOW; AJob: Boolean = True): THandle;
var
SI : TStartupInfo;
PI : TProcessInformation;
AFlag: Cardinal;
begin
Result := INVALID_HANDLE_VALUE;
FillChar(SI,SizeOf(SI),0);
SI.cb := SizeOf(SI);
SI.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
SI.wShowWindow := nCmdShow;
if AJob then
AFlag := CREATE_BREAKAWAY_FROM_JOB
else
AFlag := 0;
...

Resources