Solved

Need help with launching console app.

Posted on 2008-10-27
18
878 Views
Last Modified: 2012-05-05
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 ?
0
Comment
Question by:tadzio_blah
  • 11
  • 7
18 Comments
 
LVL 26

Expert Comment

by:Russell Libby
Comment Utility
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

0
 

Author Comment

by:tadzio_blah
Comment Utility
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 ??
0
 

Author Comment

by:tadzio_blah
Comment Utility
was this what you meant:
    CloseHandle(ProcessInfo.hThread);
    CloseHandle(ProcessInfo.hProcess);
?
0
 
LVL 26

Expert Comment

by:Russell Libby
Comment Utility
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




0
 

Author Comment

by:tadzio_blah
Comment Utility
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).
0
 

Author Comment

by:tadzio_blah
Comment Utility
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 ?
0
 

Author Comment

by:tadzio_blah
Comment Utility
delphi help system says something like this:

RaiseLastWin32Error is a deprecated Windows-only procedure. New applications should use RaiseLastOSError instead.
0
 
LVL 26

Expert Comment

by:Russell Libby
Comment Utility
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
0
 

Author Comment

by:tadzio_blah
Comment Utility
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.
0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 26

Accepted Solution

by:
Russell Libby earned 200 total points
Comment Utility
Ok,

1 - Re-download the pipes.zip from my site again. (which means you will need to replace the RaiseLastWin32Errors again... or just ignore the warning). I added code injection to have the console return its own HWND, vs enumerating the process windows. It will fall back to enum if needed.

2 - Recompile the component set

3 . Example source code below. Run it, and just paste your command into the command line to run. Then hit start.

4. Let me know how it goes.

Russell

--- source---
unit main;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Pipes, StdCtrls;

type
  TForm1            = class(TForm)
    btnStart:       TButton;
    btnStop:        TButton;
    btnCtrlbreak:   TButton;
    btnCtrlC:       TButton;
    txtOutput:      TMemo;
    PipeConsole1:   TPipeConsole;
    txtError:       TMemo;
    Label1:         TLabel;
    txtCommand:     TEdit;
    procedure       FormCreate(Sender: TObject);
    procedure       btnStartClick(Sender: TObject);
    procedure       PipeConsole1Stop(Sender: TObject; ExitValue: Cardinal);
    procedure       PipeConsole1Output(Sender: TObject; Stream: TStream);
    procedure       btnStopClick(Sender: TObject);
    procedure       btnCtrlbreakClick(Sender: TObject);
    procedure       btnCtrlCClick(Sender: TObject);
    procedure       PipeConsole1Error(Sender: TObject; Stream: TStream);
  private
     // Private declarations
  public
     // Public declarations
  end;

var
  Form1:            TForm1;

implementation
{$R *.DFM}

procedure TForm1.btnStartClick(Sender: TObject);
begin

  //
  // Example to demo switching visibility after the process has started. You
  // would normally leave this set to the desired value in order to have it
  // applied during process creating
  //
  PipeConsole1.Visible:=True;

  // Check command line to run with "cmd"
  if (Length(txtCommand.Text) > 0) then
  begin
     // Build command line
     if PipeConsole1.Start('', txtCommand.Text) then
     begin

        //
        // NOTE: For console apps that show themselves regardless of StartupInfo
        // settings, you can apply the visible at this time.
        //
        PipeConsole1.Visible:=False;
        //

        // Set button state
        btnStart.Enabled:=False;
        btnStop.Enabled:=True;
        btnCtrlbreak.Enabled:=True;
        btnCtrlC.Enabled:=True;

     end;
  end;

end;

procedure TForm1.FormCreate(Sender: TObject);
begin

  // Set initial button state
  btnStart.Enabled:=True;
  btnStop.Enabled:=False;
  btnCtrlbreak.Enabled:=False;
  btnCtrlC.Enabled:=False;

end;

procedure TForm1.PipeConsole1Stop(Sender: TObject; ExitValue: Cardinal);
begin

  // Reset button state
  btnStart.Enabled:=True;
  btnStop.Enabled:=False;
  btnCtrlbreak.Enabled:=False;
  btnCtrlC.Enabled:=False;

end;

procedure TForm1.PipeConsole1Output(Sender: TObject; Stream: TStream);
var  listTemp:      TStringList;
begin

  // Create string list for processing
  listTemp:=TStringList.Create;

  // Resource protection
  try
     // Rewind the stream
     Stream.Position:=0;
     // Load form stream
     listTemp.LoadFromStream(Stream);
     // Lock the list
     txtOutput.Lines.EndUpdate;
     // Resource protection
     try

        // Example: append the new lines to the memo (to get total result)
        //
        // txtOutput.Lines.AddStrings(listTemp);
        //

        //
        // FOR DEMO PURPOSES: limit the number of lines displayed. For example,
        // if running "dir c:\ /s" the output can be huge (not a problem), but the
        // displaying of this in a memo will get slower as the memo gets more data
        //
        txtOutput.Lines.Assign(listTemp);


     finally
        // Unlock the list
        txtOutput.Lines.EndUpdate;
     end;
  finally
     // Free the list
     listTemp.Free;
  end;

end;

procedure TForm1.btnStopClick(Sender: TObject);
begin

  // Force the process to STOP now. You can also pass a desired exit code
  PipeConsole1.Stop(0);

end;

procedure TForm1.btnCtrlbreakClick(Sender: TObject);
begin

  // Submit a control - break to the process
  PipeConsole1.SendCtrlBreak;

end;

procedure TForm1.btnCtrlCClick(Sender: TObject);
begin

  // Submit a control - c to the process
  PipeConsole1.SendCtrlC;

end;

procedure TForm1.PipeConsole1Error(Sender: TObject; Stream: TStream);
var  listTemp:      TStringList;
begin

  // Create string list for processing
  listTemp:=TStringList.Create;

  // Resource protection
  try
     // Rewind the stream
     Stream.Position:=0;
     // Load form stream
     listTemp.LoadFromStream(Stream);
     // Lock the list
     txtError.Lines.EndUpdate;
     // Resource protection
     try

        // Example: append the new lines to the memo (to get total result)
        //
        // txtError.Lines.AddStrings(listTemp);
        //

        //
        // FOR DEMO PURPOSES: limit the number of lines displayed. For example,
        // if running "dir c:\ /s" the output can be huge (not a problem), but the
        // displaying of this in a memo will get slower as the memo gets more data
        //
        txtError.Lines.Assign(listTemp);


     finally
        // Unlock the list
        txtError.Lines.EndUpdate;
     end;
  finally
     // Free the list
     listTemp.Free;
  end;

end;

end.

--- dfm ---

object Form1: TForm1
  Left = 302
  Top = 285
  Width = 725
  Height = 536
  Caption = 'Console Runner'
  Color = clBtnFace
  Font.Charset = ANSI_CHARSET
  Font.Color = clWindowText
  Font.Height = -13
  Font.Name = 'Courier New'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 16
  object Label1: TLabel
    Left = 112
    Top = 12
    Width = 152
    Height = 16
    Caption = 'Command Line To Run'
  end
  object btnStart: TButton
    Left = 8
    Top = 12
    Width = 93
    Height = 25
    Caption = 'Start'
    TabOrder = 0
    OnClick = btnStartClick
  end
  object btnStop: TButton
    Left = 8
    Top = 44
    Width = 93
    Height = 25
    Caption = 'Stop'
    TabOrder = 1
    OnClick = btnStopClick
  end
  object txtOutput: TMemo
    Left = 112
    Top = 64
    Width = 597
    Height = 273
    ScrollBars = ssBoth
    TabOrder = 2
  end
  object txtCommand: TEdit
    Left = 112
    Top = 36
    Width = 597
    Height = 24
    TabOrder = 3
    Text = 'cmd /c dir c:\ /s'
  end
  object txtError: TMemo
    Left = 112
    Top = 344
    Width = 597
    Height = 141
    ScrollBars = ssBoth
    TabOrder = 4
  end
  object btnCtrlbreak: TButton
    Left = 8
    Top = 76
    Width = 93
    Height = 25
    Caption = 'Ctrl-Break'
    TabOrder = 5
    OnClick = btnCtrlbreakClick
  end
  object btnCtrlC: TButton
    Left = 8
    Top = 108
    Width = 93
    Height = 25
    Caption = 'Ctrl-C'
    TabOrder = 6
    OnClick = btnCtrlCClick
  end
  object PipeConsole1: TPipeConsole
    LastError = 0
    OnError = PipeConsole1Error
    OnOutput = PipeConsole1Output
    OnStop = PipeConsole1Stop
    Visible = True
    Left = 8
    Top = 148
  end
end

0
 
LVL 26

Expert Comment

by:Russell Libby
Comment Utility
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;
0
 

Author Comment

by:tadzio_blah
Comment Utility
works fine... thank you.
0
 

Author Comment

by:tadzio_blah
Comment Utility
thanks for the info, i'll try to change them and see if it works ok.
0
 
LVL 26

Expert Comment

by:Russell Libby
Comment Utility
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
0
 

Author Comment

by:tadzio_blah
Comment Utility
Ok.
I'll be greatfull if you could tell me one more thing... how can I set the priority of the launched app ?
0
 
LVL 26

Expert Comment

by:Russell Libby
Comment Utility
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
0
 

Author Comment

by:tadzio_blah
Comment Utility
thanks, but the priority of the launched app isn't changing after setting the .Priority to for eg. tpHighest
0
 

Author Comment

by:tadzio_blah
Comment Utility
is it possible to change the console apps priority class to HIGH_PRIORITY_CLASS and priority to tpHighest ?
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

763 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

9 Experts available now in Live!

Get 1:1 Help Now