Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting output from a shell/dos app into a Delphi app

I have a commandline application coded in delphi that I need to call from a normal desktop application (also coded in delphi). In short, I want to call the commandline app and display the text it outputs "live" in a listbox.

It's been ages since I have played around with the shell, but I distinctly remember that in order to grab the text from a commandline app - I have to use the pipe symbol ">". Like this:

C:/mycmdapp.exe >c:/result.txt

This will take any text printed to the shell (using writeLn) and dump it to a textfile called "result.txt".

But.. (and here comes the pickle), I want a live result rather than a backlog file. A typical example is the Delphi compiler itself - which manages to report back to the IDE what is going on. If my memory serves me correctly, I seem to recall that I must create a "pipe" channel (?), and then assign the pipe-name to the shell call.

I have tried to google this but I honestly was unsure of how to formulate it. Hopefully someone from the community can point me in the right direction.

Updated: This question might be identical to How do I run a command-line program in Delphi?. Some of the answers fit what I'm looking for, although the title and question itself is not identical.

like image 369
Jon Lennart Aasenden Avatar asked Feb 02 '12 21:02

Jon Lennart Aasenden


1 Answers

As ever so often Zarco Gajic has a solution: Capture the output from a DOS (command/console) Window. This is a copy from his article for future reference:

The example runs 'chkdsk.exe c:\' and displays the output to Memo1. Put a TMemo (Memo1) and a TButton (Button1) on your form. Put this code in the OnCLick event procedure for Button1:

procedure RunDosInMemo(DosApp: string; AMemo:TMemo); const     READ_BUFFER_SIZE = 2400; var     Security: TSecurityAttributes;     readableEndOfPipe, writeableEndOfPipe: THandle;     start: TStartUpInfo;     ProcessInfo: TProcessInformation;     Buffer: PAnsiChar;     BytesRead: DWORD;     AppRunning: DWORD; begin     Security.nLength := SizeOf(TSecurityAttributes);     Security.bInheritHandle := True;     Security.lpSecurityDescriptor := nil;      if CreatePipe({var}readableEndOfPipe, {var}writeableEndOfPipe, @Security, 0) then     begin         Buffer := AllocMem(READ_BUFFER_SIZE+1);         FillChar(Start, Sizeof(Start), #0);         start.cb := SizeOf(start);          // Set up members of the STARTUPINFO structure.         // This structure specifies the STDIN and STDOUT handles for redirection.         // - Redirect the output and error to the writeable end of our pipe.         // - We must still supply a valid StdInput handle (because we used STARTF_USESTDHANDLES to swear that all three handles will be valid)         start.dwFlags := start.dwFlags or STARTF_USESTDHANDLES;         start.hStdInput := GetStdHandle(STD_INPUT_HANDLE); //we're not redirecting stdInput; but we still have to give it a valid handle         start.hStdOutput := writeableEndOfPipe; //we give the writeable end of the pipe to the child process; we read from the readable end         start.hStdError := writeableEndOfPipe;          //We can also choose to say that the wShowWindow member contains a value.         //In our case we want to force the console window to be hidden.         start.dwFlags := start.dwFlags + STARTF_USESHOWWINDOW;         start.wShowWindow := SW_HIDE;          // Don't forget to set up members of the PROCESS_INFORMATION structure.         ProcessInfo := Default(TProcessInformation);          //WARNING: The unicode version of CreateProcess (CreateProcessW) can modify the command-line "DosApp" string.          //Therefore "DosApp" cannot be a pointer to read-only memory, or an ACCESS_VIOLATION will occur.         //We can ensure it's not read-only with the RTL function: UniqueString         UniqueString({var}DosApp);          if CreateProcess(nil, PChar(DosApp), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, nil, start, {var}ProcessInfo) then         begin             //Wait for the application to terminate, as it writes it's output to the pipe.             //WARNING: If the console app outputs more than 2400 bytes (ReadBuffer),             //it will block on writing to the pipe and *never* close.             repeat                 Apprunning := WaitForSingleObject(ProcessInfo.hProcess, 100);                 Application.ProcessMessages;             until (Apprunning <> WAIT_TIMEOUT);              //Read the contents of the pipe out of the readable end             //WARNING: if the console app never writes anything to the StdOutput, then ReadFile will block and never return             repeat                 BytesRead := 0;                 ReadFile(readableEndOfPipe, Buffer[0], READ_BUFFER_SIZE, {var}BytesRead, nil);                 Buffer[BytesRead]:= #0;                 OemToAnsi(Buffer,Buffer);                 AMemo.Text := AMemo.text + String(Buffer);             until (BytesRead < READ_BUFFER_SIZE);         end;         FreeMem(Buffer);         CloseHandle(ProcessInfo.hProcess);         CloseHandle(ProcessInfo.hThread);         CloseHandle(readableEndOfPipe);         CloseHandle(writeableEndOfPipe);     end; end;  procedure TForm1.Button1Click(Sender: TObject); begin {button 1 code}    RunDosInMemo('chkdsk.exe c:\',Memo1); end; 

Update: The above example reads the output in one step. Here is another example from DelphiDabbler showing how the output can be read while the process is still running:

function GetDosOutput(CommandLine: string; Work: string = 'C:\'): string; var   SA: TSecurityAttributes;   SI: TStartupInfo;   PI: TProcessInformation;   StdOutPipeRead, StdOutPipeWrite: THandle;   WasOK: Boolean;   Buffer: array[0..255] of AnsiChar;   BytesRead: Cardinal;   WorkDir: string;   Handle: Boolean; begin   Result := '';   with SA do begin     nLength := SizeOf(SA);     bInheritHandle := True;     lpSecurityDescriptor := nil;   end;   CreatePipe(StdOutPipeRead, StdOutPipeWrite, @SA, 0);   try     with SI do     begin       FillChar(SI, SizeOf(SI), 0);       cb := SizeOf(SI);       dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;       wShowWindow := SW_HIDE;       hStdInput := GetStdHandle(STD_INPUT_HANDLE); // don't redirect stdin       hStdOutput := StdOutPipeWrite;       hStdError := StdOutPipeWrite;     end;     WorkDir := Work;     Handle := CreateProcess(nil, PChar('cmd.exe /C ' + CommandLine),                             nil, nil, True, 0, nil,                             PChar(WorkDir), SI, PI);     CloseHandle(StdOutPipeWrite);     if Handle then       try         repeat           WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);           if BytesRead > 0 then           begin             Buffer[BytesRead] := #0;             Result := Result + Buffer;           end;         until not WasOK or (BytesRead = 0);         WaitForSingleObject(PI.hProcess, INFINITE);       finally         CloseHandle(PI.hThread);         CloseHandle(PI.hProcess);       end;   finally     CloseHandle(StdOutPipeRead);   end; end; 
like image 83
Uwe Raabe Avatar answered Oct 08 '22 08:10

Uwe Raabe