tadzio_blah
asked on
Need help with launching console app.
Hi
I'm launching a console app using this code:
procedure TmainForm.ExecWithPipe(FNa me : PChar; OutPut : TStrings);
var
StartupInfo : TStartupInfo;
ProcessInfo : TProcessInformation;
Buffer : array[0..255] of char;
Wynik : string;
bRead : cardinal;
licznik : integer;
hRead, hWrite : THandle;
saAttr : TSECURITYATTRIBUTES;
OutSt : TMemoryStream;
begin
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength := sizeof(TSECURITYATTRIBUTES );
saAttr.bInheritHandle := true;
saAttr.lpSecurityDescripto r := nil;
if not(CreatePipe(hRead, hWrite, @saAttr, 0)) then
begin
ShowMessage('Can not create the pipe!');
Exit;
end;
if not(AllocConsole) then
begin
ShowMessage('Can not allocate console for the child!');
Exit;
end;
try
FillChar(StartupInfo,Sizeo f(StartupI nfo),#0);
StartupInfo.cb := Sizeof(StartupInfo);
StartupInfo.dwFlags := STARTF_USESTDHANDLES;
StartupInfo.wShowWindow := SW_HIDE; //SW_SHOWMINIMIZED;
//Associate our handles with our child process
StartupInfo.hStdInput := GetStdHandle(STD_INPUT_HAN DLE);
StartupInfo.hStdOutput:= hWrite; //we catch output
StartupInfo.hStdError := hWrite; //and also error
if not(CreateProcess(nil, FName, nil, nil, true, CREATE_NO_WINDOW or HIGH_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo)) then
ShowMessage('Can not create process')
else
begin
Sleep(300);
//loop until terminated
while (WaitforSingleObject(Proce ssInfo.hPr ocess, 0) <> WAIT_OBJECT_0) do
begin
licznik := 0;
repeat
Application.ProcessMessage s;
Sleep(10);
licznik := licznik + 10;
until (licznik >= 1000);
//now read all the output of the child and put it to a memo
OutSt := TMemoryStream.Create;
repeat
if ReadFile(hRead, Buffer, 80, bRead, nil) then
OutSt.WriteBuffer(Buffer, bRead)
else
Break;
until bRead <> 80;
OutSt.Seek(0,0); //seek to begining
//read memo from stream
SetLength(Wynik, OutSt.Size);
OutSt.ReadBuffer(PChar(Wyn ik)^, OutSt.Size);
OutPut.Clear;
OutPut.Add(Wynik);
// OutPut.LoadFromStream(OutS t);
Sleep(100);
OutSt.Free;
end;
end;
finally
//close read and write handles of our pipe
Sleep(300);
CloseHandle(hRead);
CloseHandle(hWrite);
if not(FreeConsole) then MessageBeep(0);
end;
end;
it works, but I need to hide the console window (the line StartupInfo.wShowWindow := SW_HIDE; doesn't do the trick); and also while i'm using var wynik to pass the results the console window sometimes doesn't close properly (it works fine when i use the OutPut.LoadFromStream - the thing is I need to copy the results to a string for later use); Can anyone help ?
I'm launching a console app using this code:
procedure TmainForm.ExecWithPipe(FNa
var
StartupInfo : TStartupInfo;
ProcessInfo : TProcessInformation;
Buffer : array[0..255] of char;
Wynik : string;
bRead : cardinal;
licznik : integer;
hRead, hWrite : THandle;
saAttr : TSECURITYATTRIBUTES;
OutSt : TMemoryStream;
begin
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength := sizeof(TSECURITYATTRIBUTES
saAttr.bInheritHandle := true;
saAttr.lpSecurityDescripto
if not(CreatePipe(hRead, hWrite, @saAttr, 0)) then
begin
ShowMessage('Can not create the pipe!');
Exit;
end;
if not(AllocConsole) then
begin
ShowMessage('Can not allocate console for the child!');
Exit;
end;
try
FillChar(StartupInfo,Sizeo
StartupInfo.cb := Sizeof(StartupInfo);
StartupInfo.dwFlags := STARTF_USESTDHANDLES;
StartupInfo.wShowWindow := SW_HIDE; //SW_SHOWMINIMIZED;
//Associate our handles with our child process
StartupInfo.hStdInput := GetStdHandle(STD_INPUT_HAN
StartupInfo.hStdOutput:= hWrite; //we catch output
StartupInfo.hStdError := hWrite; //and also error
if not(CreateProcess(nil, FName, nil, nil, true, CREATE_NO_WINDOW or HIGH_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo)) then
ShowMessage('Can not create process')
else
begin
Sleep(300);
//loop until terminated
while (WaitforSingleObject(Proce
begin
licznik := 0;
repeat
Application.ProcessMessage
Sleep(10);
licznik := licznik + 10;
until (licznik >= 1000);
//now read all the output of the child and put it to a memo
OutSt := TMemoryStream.Create;
repeat
if ReadFile(hRead, Buffer, 80, bRead, nil) then
OutSt.WriteBuffer(Buffer, bRead)
else
Break;
until bRead <> 80;
OutSt.Seek(0,0); //seek to begining
//read memo from stream
SetLength(Wynik, OutSt.Size);
OutSt.ReadBuffer(PChar(Wyn
OutPut.Clear;
OutPut.Add(Wynik);
// OutPut.LoadFromStream(OutS
Sleep(100);
OutSt.Free;
end;
end;
finally
//close read and write handles of our pipe
Sleep(300);
CloseHandle(hRead);
CloseHandle(hWrite);
if not(FreeConsole) then MessageBeep(0);
end;
end;
it works, but I need to hide the console window (the line StartupInfo.wShowWindow := SW_HIDE; doesn't do the trick); and also while i'm using var wynik to pass the results the console window sometimes doesn't close properly (it works fine when i use the OutPut.LoadFromStream - the thing is I need to copy the results to a string for later use); Can anyone help ?
ASKER
Hi. Since I need to read the progress from the app I don't think that the code you wrote applies here. And I've tried adding STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES to my code, and it didn't help - the console window still shows itself (note: i need to hide it, and not minimize it).
"you never close the process or thread handles returned from CreateProcess" - can't really find what this is regarding in my code (the code that i've found here on EE, that was supposed to work perfectly). But what i can see is that the 2 created hadnles are closed, and the console is freed - what else needs to be freed ??
"you never close the process or thread handles returned from CreateProcess" - can't really find what this is regarding in my code (the code that i've found here on EE, that was supposed to work perfectly). But what i can see is that the 2 created hadnles are closed, and the console is freed - what else needs to be freed ??
ASKER
was this what you meant:
CloseHandle(ProcessInfo.hT hread);
CloseHandle(ProcessInfo.hP rocess);
?
CloseHandle(ProcessInfo.hT
CloseHandle(ProcessInfo.hP
?
I realize you are trying to start it hidden.
I'm just saying that the way you specified the flags originally, the CreateProcess would not acknowledge the flag that you set. If it is still showing up visible, then it is possible that the console app has been written to show itself. (makes its own call to ShowWindow). In that case, you could attempt to call ShowWindow(handle, SW_HIDE) after the console window has been created. The console app will appear briefly, but there won't be much way around it.
As to the progress... you obviously didn't even check the free component offered to you. It allows for a number of things NOT offered in other redirect packages. eg:
- Sync handling (as exampled above)
- Threaded handling with events to capture OnOutput, OnError, and OnStop. To handle your "progress", you would just handle the stream passed in the OnOutput event. If the console closes itself, you will receive an OnStop event. If you need to close it manually, just call .Stop
- Ctrl-Break and Ctrl-C sending to the process
- Proper cleanup of pipes and process/thread handles.
- Visibility property set before calling Start will be applied to the CreateProcess. Setting it after will call ShowWindow(...) for you.
And yes, when I said thread and process handles that was what I meant.
>> was this what you meant:
CloseHandle(ProcessInfo.hT hread);
CloseHandle(ProcessInfo.hP rocess);
--
Russell
I'm just saying that the way you specified the flags originally, the CreateProcess would not acknowledge the flag that you set. If it is still showing up visible, then it is possible that the console app has been written to show itself. (makes its own call to ShowWindow). In that case, you could attempt to call ShowWindow(handle, SW_HIDE) after the console window has been created. The console app will appear briefly, but there won't be much way around it.
As to the progress... you obviously didn't even check the free component offered to you. It allows for a number of things NOT offered in other redirect packages. eg:
- Sync handling (as exampled above)
- Threaded handling with events to capture OnOutput, OnError, and OnStop. To handle your "progress", you would just handle the stream passed in the OnOutput event. If the console closes itself, you will receive an OnStop event. If you need to close it manually, just call .Stop
- Ctrl-Break and Ctrl-C sending to the process
- Proper cleanup of pipes and process/thread handles.
- Visibility property set before calling Start will be applied to the CreateProcess. Setting it after will call ShowWindow(...) for you.
And yes, when I said thread and process handles that was what I meant.
>> was this what you meant:
CloseHandle(ProcessInfo.hT
CloseHandle(ProcessInfo.hP
--
Russell
ASKER
can you post a sample code for using your pipes unit to launch an app in the background and read messages from it ? (my app will display only one line of progress, or an error msg).
ASKER
some warnings came up while i was building my project with the pipes.pas included:
[DCC Warning] Pipes.pas(3179): W1000 Symbol 'RaiseLastWin32Error' is deprecated
[DCC Warning] Pipes.pas(3187): W1000 Symbol 'RaiseLastWin32Error' is deprecated
[DCC Warning] Pipes.pas(3193): W1000 Symbol 'RaiseLastWin32Error' is deprecated
[DCC Warning] Pipes.pas(3656): W1000 Symbol 'RaiseList' is deprecated
[DCC Warning] Pipes.pas(3659): W1000 Symbol 'RaiseList' is deprecated
[DCC Warning] Pipes.pas(3661): W1000 Symbol 'RaiseList' is deprecated
[DCC Warning] Pipes.pas(4384): W1000 Symbol 'RaiseLastWin32Error' is deprecated
[DCC Warning] Pipes.pas(4388): W1000 Symbol 'RaiseLastWin32Error' is deprecated
is this ok ?
[DCC Warning] Pipes.pas(3179): W1000 Symbol 'RaiseLastWin32Error' is deprecated
[DCC Warning] Pipes.pas(3187): W1000 Symbol 'RaiseLastWin32Error' is deprecated
[DCC Warning] Pipes.pas(3193): W1000 Symbol 'RaiseLastWin32Error' is deprecated
[DCC Warning] Pipes.pas(3656): W1000 Symbol 'RaiseList' is deprecated
[DCC Warning] Pipes.pas(3659): W1000 Symbol 'RaiseList' is deprecated
[DCC Warning] Pipes.pas(3661): W1000 Symbol 'RaiseList' is deprecated
[DCC Warning] Pipes.pas(4384): W1000 Symbol 'RaiseLastWin32Error' is deprecated
[DCC Warning] Pipes.pas(4388): W1000 Symbol 'RaiseLastWin32Error' is deprecated
is this ok ?
ASKER
delphi help system says something like this:
RaiseLastWin32Error is a deprecated Windows-only procedure. New applications should use RaiseLastOSError instead.
RaiseLastWin32Error is a deprecated Windows-only procedure. New applications should use RaiseLastOSError instead.
Give me an hour, and I will provide sample code. As to the deprecated warnings; they are ok to ignore. Keep in mind that this source was written on D5, but has been made compatible with later versions.
Russell
Russell
ASKER
I've changed the RaiseLastWin32Error to RaiseLastOSError, don't really know what's the substitute for RaiseList, so I've left it alone. I'd like to attach the console app that I'll be working with, but I can't due to extension restrictions.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Btw,
RaiseList = AcquireExceptionObject. You will find the following in the pipes.pas. If I get time, I will try and take a pass at the defines once I get a list of the compiler versions together.
//// Thread window procedure ////////////////////////// ////////// ////////// /////
function ThreadWndProc(Window: HWND; Message, wParam, lParam: Longint): Longint; stdcall;
begin
{$IFDEF VER140} { Borland Delphi 6.0 }
{$DEFINE DELPHI6_OR_7}
{$ENDIF}
{$IFDEF VER150} { Borland Delphi 7.0 }
{$DEFINE DELPHI6_OR_7}
{$ENDIF}
// Handle the window message
case Message of
// Exceute the method in thread
CM_EXECPROC :
begin
// The lParam constains the thread sync information
with TThreadSync(lParam) do
begin
// Set message result
result:=0;
// Exception trap
try
// Clear the exception
FSyncRaise:=nil;
// Call the method
FMethod;
except
{$IFNDEF DELPHI6_OR_7}
if not(RaiseList = nil) then
begin
// Get exception object from frame
FSyncRaise:=PRaiseFrame(Ra iseList)^. ExceptObje ct;
// Clear frame exception object
PRaiseFrame(RaiseList)^.Ex ceptObject :=nil;
end;
{$ELSE}
FSyncRaise:=AcquireExcepti onObject;
{$ENDIF}
end;
end;
end;
// Thead destroying
CM_DESTROYWINDOW :
begin
// Get instance of sync manager
TSyncManager.Instance.DoDe stroyWindo w(TSyncInf o(lParam)) ;
// Set message result
result:=0;
end;
else
// Call the default window procedure
result:=DefWindowProc(Wind ow, Message, wParam, lParam);
end;
end;
RaiseList = AcquireExceptionObject. You will find the following in the pipes.pas. If I get time, I will try and take a pass at the defines once I get a list of the compiler versions together.
//// Thread window procedure //////////////////////////
function ThreadWndProc(Window: HWND; Message, wParam, lParam: Longint): Longint; stdcall;
begin
{$IFDEF VER140} { Borland Delphi 6.0 }
{$DEFINE DELPHI6_OR_7}
{$ENDIF}
{$IFDEF VER150} { Borland Delphi 7.0 }
{$DEFINE DELPHI6_OR_7}
{$ENDIF}
// Handle the window message
case Message of
// Exceute the method in thread
CM_EXECPROC :
begin
// The lParam constains the thread sync information
with TThreadSync(lParam) do
begin
// Set message result
result:=0;
// Exception trap
try
// Clear the exception
FSyncRaise:=nil;
// Call the method
FMethod;
except
{$IFNDEF DELPHI6_OR_7}
if not(RaiseList = nil) then
begin
// Get exception object from frame
FSyncRaise:=PRaiseFrame(Ra
// Clear frame exception object
PRaiseFrame(RaiseList)^.Ex
end;
{$ELSE}
FSyncRaise:=AcquireExcepti
{$ENDIF}
end;
end;
end;
// Thead destroying
CM_DESTROYWINDOW :
begin
// Get instance of sync manager
TSyncManager.Instance.DoDe
// Set message result
result:=0;
end;
else
// Call the default window procedure
result:=DefWindowProc(Wind
end;
end;
ASKER
works fine... thank you.
ASKER
thanks for the info, i'll try to change them and see if it works ok.
Your welcome. I will let you know when I have the pipes.pas updated to handle the deprecated warnings (should be in the morning).
Russell
Russell
ASKER
Ok.
I'll be greatfull if you could tell me one more thing... how can I set the priority of the launched app ?
I'll be greatfull if you could tell me one more thing... how can I set the priority of the launched app ?
Code on my site has been updated. Defines for D6 and above to handle deprecated warnings and a .Priority property of the console component. The values are the same as the TThreadPriority. Check the ForcePriority(...) method of the component for an example of calling SetThreadPriority.
Russell
Russell
ASKER
thanks, but the priority of the launched app isn't changing after setting the .Priority to for eg. tpHighest
ASKER
is it possible to change the console apps priority class to HIGH_PRIORITY_CLASS and priority to tpHighest ?
StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES
Also, you never close the process or thread handles returned from CreateProcess. Either fix the code up, or download my Pipes wrapper from:
http://home.roadrunner.com/~rllibby/downloads/pipes.zip
You will find a TPipeConsole component that simplifies what you are trying to do (as well as offering additional functionality). An example of usage:
procedure TForm1.Button1Click(Sender
var strmOut: TMemoryStream;
listData: TStringList;
begin
strmOut:=TMemoryStream.Cre
try
// Synchronized execute (application, command line, output stream, error stream, time out {optional})
if (PipeConsole1.Execute('', 'cmd.exe /c dir c:', strmOut, nil, 5000) = ERROR_SUCCESS) then
begin
listData:=TStringList.Crea
try
strmOut.Position:=0;
listData.LoadFromStream(st
ShowMessage(listData.Text)
finally
listData.Free;
end;
end;
finally
strmOut.Free;
end;
end;
Regards,
Russell