Link to home
Start Free TrialLog in
Avatar of tadzio_blah
tadzio_blah

asked on

Need help with launching console app.

Hi

I'm launching a console app using this code:

procedure TmainForm.ExecWithPipe(FName : 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.lpSecurityDescriptor := 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,Sizeof(StartupInfo),#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_HANDLE);
    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(ProcessInfo.hProcess, 0) <> WAIT_OBJECT_0) do
          begin
            licznik := 0;
            repeat
              Application.ProcessMessages;
              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(Wynik)^, OutSt.Size);
            OutPut.Clear;
            OutPut.Add(Wynik);
//            OutPut.LoadFromStream(OutSt);
            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 ?
Avatar of Russell Libby
Russell Libby
Flag of United States of America image

You have a couple of bugs going on. First, you need to tell the CreateProcess to use the wShowWindow param by passing the flags:

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: TObject);
var  strmOut:       TMemoryStream;
     listData:      TStringList;
begin

  strmOut:=TMemoryStream.Create;
  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.Create;
        try
           strmOut.Position:=0;
           listData.LoadFromStream(strmOut);
           ShowMessage(listData.Text);
        finally
           listData.Free;
        end;
     end;
  finally
     strmOut.Free;
  end;

end;


Regards,
Russell

Avatar of tadzio_blah
tadzio_blah

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 ??
was this what you meant:
    CloseHandle(ProcessInfo.hThread);
    CloseHandle(ProcessInfo.hProcess);
?
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.hThread);
    CloseHandle(ProcessInfo.hProcess);

--

Russell




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).
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 ?
delphi help system says something like this:

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
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
Avatar of Russell Libby
Russell Libby
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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(RaiseList)^.ExceptObject;
                 // Clear frame exception object
                 PRaiseFrame(RaiseList)^.ExceptObject:=nil;
              end;
              {$ELSE}
              FSyncRaise:=AcquireExceptionObject;
              {$ENDIF}
           end;
        end;
     end;
     // Thead destroying
     CM_DESTROYWINDOW  :
     begin
        // Get instance of sync manager
        TSyncManager.Instance.DoDestroyWindow(TSyncInfo(lParam));
        // Set message result
        result:=0;
     end;
  else
     // Call the default window procedure
     result:=DefWindowProc(Window, Message, wParam, lParam);
  end;

end;
works fine... thank you.
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
Ok.
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
thanks, but the priority of the launched app isn't changing after setting the .Priority to for eg. tpHighest
is it possible to change the console apps priority class to HIGH_PRIORITY_CLASS and priority to tpHighest ?