chrb
asked on
Read text from a dosshell
I wish to start a dosprogram in a dosshell, then I wish to read everything wich is display (Is it possible to read In21?)
I Also want to send keypress to the program.
Thanks !
I Also want to send keypress to the program.
Thanks !
ASKER
Win 95 and Delphi 2.01
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
How do I define StartupInfo, I only got this message :
undeclerad identifier : 'startupinfo'
May I get the source to the program you had made? Wich executed a dossession an let i go into a listbox. Please.
Anyway thanks.
undeclerad identifier : 'startupinfo'
May I get the source to the program you had made? Wich executed a dossession an let i go into a listbox. Please.
Anyway thanks.
StartupInfo : TStartupInfo;
ProcessInfo : TProcessInformation;
Both types declared in WIndows unit.
I can send you the source, but it won't work on 95, only on NT... I use the security features of NT to set the handles inheritable, whereas 95 needs to use DuplicateHandle because it doesn't support security.
Give me a few days or so and I'll have it work on 95 too, then I'll post it here.
Regards,
Erik.
ASKER
Thanks a lot.
Thanks for your extremly fast answars :)
I will download the source when you post it :) Thanks again.
Thanks for your extremly fast answars :)
I will download the source when you post it :) Thanks again.
Hmmm... Seems I had already implemented DuplicateHandle. Anyway, it works on 95, so here goes.
The unit "Redirect" contains a class/component which encapsulates a dos box. It's not commented, and I haven't finished testing it out, so there'll probably be errors.
The unit CATestMain is the unit for the test programs main form. Create a new blank project, and reconstruct the events/methods of this unit into your main forms code.
If you experiment a little, you'll see how it works. Feel free to use the Redirector class as you wish, but as said before, it most probably still contains errors.
-------------------------
unit Redirect;
interface
uses
Windows, SysUtils, Classes;
type
TRedirector = class;
TPriorityClass = (pcDefault, pcIdle, pcNormal, pcHigh, pcRealtime);
TDataEvent = procedure (Sender : TRedirector; buffer : POINTER; Size : INTEGER) of object;
TRedirector = class {$IFDEF COMPONENT} (TComponent) {$ENDIF}
private
FAvailable : INTEGER;
procedure ReadStdOutput;
procedure ReadStdError;
procedure ProcessTerminated;
protected
FProcessInfo : TProcessInformation;
FExitCode : INTEGER;
FExecutable : STRING;
FParameters : STRING;
FDefaultErrorMode : BOOLEAN;
FStartSuspended : BOOLEAN;
FKillOnDestroy : BOOLEAN;
FDirectory : STRING;
FEnvironment : POINTER;
FInitialPriority : TPriorityClass;
FPipeInput,
FPipeOutput,
FPipeError :
record
hRead,
hWrite : DWORD;
end;
FThread : TThread;
FOnData,
FOnErrorData : TDataEvent;
FOnTerminated : TNotifyEvent;
procedure Error (msg : STRING);
procedure WinError (msg : STRING);
procedure CreatePipes;
procedure ClosePipes;
function GetRunning : BOOLEAN;
function GetExitCode : INTEGER;
function GetProcessID : INTEGER;
function GetThreadID : INTEGER;
function GetProcessHandle : INTEGER;
function GetThreadHandle : INTEGER;
procedure SetExecutable (value : STRING);
procedure SetParameters (value : STRING);
function GetCommandLine : STRING;
procedure SetCommandLine (value : STRING);
procedure SetDefaultErrorMode (value : BOOLEAN);
procedure SetStartSuspended (value : BOOLEAN);
procedure SetInitialPriority (value : TPriorityClass);
procedure SetDirectory (value : STRING);
procedure SetEnvironment (value : POINTER);
property ProcessHandle : INTEGER
read GetProcessHandle;
property ThreadHandle : INTEGER
read GetThreadHandle;
public
destructor Destroy; override;
procedure Terminate (dwExitCode : INTEGER);
procedure Start;
procedure SendData (Buffer : POINTER; BufferSize : INTEGER);
procedure SendText (s : STRING);
property Running : BOOLEAN
read GetRunning;
property ExitCode : INTEGER
read GetExitCode;
property ProcessID : INTEGER
read GetProcessID;
property ThreadID : INTEGER
read GetThreadID;
property Environment : POINTER
read FEnvironment
write SetEnvironment;
published
property KillOnDestroy : BOOLEAN
read FKillOnDestroy
write FKillOnDestroy;
property Executable : STRING
read FExecutable
write SetExecutable;
property Parameters : STRING
read FParameters
write SetParameters;
property CommandLine : STRING
read GetCommandLine
write SetCommandLine;
property DefaultErrorMode : BOOLEAN
read FDefaultErrorMode
write SetDefaultErrorMode;
property StartSuspended : BOOLEAN
read FStartSuspended
write SetStartSuspended;
property InitialPriority : TPriorityClass
read FInitialPriority
write SetInitialPriority;
property Directory : STRING
read FDirectory
write SetDirectory;
property OnData : TDataEvent
read FOnData
write FOnData;
property OnErrorData : TDataEvent
read FOnErrorData
write FOnErrorData;
property OnTerminated : TNotifyEvent
read FOnTerminated
write FOnTerminated;
end;
implementation
const
DUPLICATE_CLOSE_SOURCE = 1;
DUPLICATE_SAME_ACCESS = 2;
type
TRedirectorThread = class (TThread)
protected
FRedirector : TRedirector;
procedure Execute; override;
constructor Create(ARedirector : TRedirector);
end;
////////////////////////// ////////// ////////// ////////// ////////// ////////// ////
// Misc. internal methods
////////////////////////// ////////// ////////// ////////// ////////// ////////// ////
procedure TRedirector.Error (msg : STRING);
begin
raise Exception.Create(msg);
end;
procedure TRedirector.WinError (msg : STRING);
begin
Error (msg + IntToStr(GetLastError));
end;
procedure TRedirector.CreatePipes;
var
SecAttr : TSecurityAttributes;
begin
SecAttr.nLength := SizeOf(SecAttr);
SecAttr.lpSecurityDescript or := nil;
SecAttr.bInheritHandle := TRUE;
with FPipeInput do begin
if not CreatePipe (hRead, hWrite, @SecAttr, 1024)
then WinError('Error on STDIN pipe creation : ');
if not DuplicateHandle (GetCurrentProcess, hRead, GetCurrentProcess, @hRead, 0, TRUE, DUPLICATE_CLOSE_SOURCE or DUPLICATE_SAME_ACCESS)
then WinError('Error on STDIN pipe duplication : ');
end;
with FPipeOutput do begin
if not CreatePipe (hRead, hWrite, @SecAttr, 1024)
then WinError('Error on STDOUT pipe creation : ');
if not DuplicateHandle (GetCurrentProcess, hWrite, GetCurrentProcess, @hWrite, 0, TRUE, DUPLICATE_CLOSE_SOURCE or DUPLICATE_SAME_ACCESS)
then WinError('Error on STDOUT pipe duplication : ');
end;
with FPipeError do begin
if not CreatePipe (hRead, hWrite, @SecAttr, 1024)
then WinError('Error on STDERR pipe creation : ');
if not DuplicateHandle (GetCurrentProcess, hWrite, GetCurrentProcess, @hWrite, 0, TRUE, DUPLICATE_CLOSE_SOURCE or DUPLICATE_SAME_ACCESS)
then WinError('Error on STDERR pipe duplication : ');
end;
end;
procedure TRedirector.ClosePipes;
begin
with FPipeInput do begin
if hRead <> 0 then CloseHandle (hRead);
if hWrite <> 0 then CloseHandle (hWrite);
hRead := 0;
hWrite := 0;
end;
with FPipeOutput do begin
if hRead <> 0 then CloseHandle (hRead);
if hWrite <> 0 then CloseHandle (hWrite);
hRead := 0;
hWrite := 0;
end;
with FPipeError do begin
if hRead <> 0 then CloseHandle (hRead);
if hWrite <> 0 then CloseHandle (hWrite);
hRead := 0;
hWrite := 0;
end;
end;
////////////////////////// ////////// ////////// ////////// ////////// ////////// ////
// Property implementations
////////////////////////// ////////// ////////// ////////// ////////// ////////// ////
function TRedirector.GetRunning : BOOLEAN;
begin
Result := ProcessHandle<>0;
end;
function TRedirector.GetExitCode : INTEGER;
begin
if Running
then Result := STILL_ACTIVE
else Result := FExitCode;
end;
function TRedirector.GetProcessID : INTEGER;
begin
Result := FProcessInfo.dwProcessID;
end;
function TRedirector.GetThreadID : INTEGER;
begin
Result := FProcessInfo.dwThreadID;
end;
function TRedirector.GetProcessHand le : INTEGER;
begin
Result := FProcessInfo.hProcess;
end;
function TRedirector.GetThreadHandl e : INTEGER;
begin
Result := FProcessInfo.hThread;
end;
procedure TRedirector.SetExecutable (value : STRING);
begin
if (ANSICompareText(value, Executable) = 0) or not Running
then FExecutable := value
else if Running
then Error('Cannot change Executable while process is active');
end;
procedure TRedirector.SetParameters (value : STRING);
begin
if (ANSICompareText(value, Parameters) = 0) or not Running
then FParameters := value
else if Running
then Error('Cannot change Parameters while process is active');
end;
function TRedirector.GetCommandLine : STRING;
begin
Result := FExecutable;
if Result = ''
then Result := FParameters
else Result := FExecutable + ' ' + FParameters;
end;
procedure TRedirector.SetCommandLine (value : STRING);
var
n1,
n2 : INTEGER;
begin
if (ANSICompareText (value, CommandLine) = 0) or (not Running) then begin
n1 := Length(value);
n2 := Pos(' ', value);
if (n2>0) and (n2<n1) then n1 := n2;
n2 := Pos('-', value);
if (n2>0) and (n2<n1) then n1 := n2;
n2 := Pos('/', value);
if (n2>0) and (n2<n1) then n1 := n2;
FExecutable := Copy (value, 1, n1-1);
FParameters := Trim(Copy (value, Length(FExecutable)+1, Length(value)));
end else if Running then Error('Cannot change CommandLine while process is active');
end;
procedure TRedirector.SetDefaultErro rMode (value : BOOLEAN);
begin
if (value = DefaultErrorMode) or not Running
then FDefaultErrorMode := value
else if Running
then Error('Cannot change DefaultErrorMode while process is active');
end;
procedure TRedirector.SetStartSuspen ded (value : BOOLEAN);
begin
if (value = DefaultErrorMode) or not Running
then FStartSuspended:= value
else if Running
then Error('Cannot change StartSuspended while process is active');
end;
procedure TRedirector.SetInitialPrio rity (value : TPriorityClass);
begin
if (value = InitialPriority) or not Running
then FInitialPriority := value
else if Running
then Error('Cannot change InititalPriority while process is active');
end;
procedure TRedirector.SetDirectory (value : STRING);
begin
if (ANSICompareText (value, Directory) = 0) or (not Running)
then FDirectory:= value
else if Running
then Error('Cannot change Directory while process is active');
end;
procedure TRedirector.SetEnvironment (value : POINTER);
begin
if (value = Environment) or not Running
then FEnvironment := value
else if Running
then Error('Cannot change Environment while process is active');
end;
procedure TRedirector.ReadStdOutput;
var
BytesRead : INTEGER;
buffer : POINTER;
begin
GetMem (buffer, FAvailable);
try
if not ReadFile (FPipeOutput.hRead, buffer^, FAvailable, BytesRead, nil) then begin
FThread.Terminate;
WinError('Error reading STDOUT pipe : ');
end;
if Assigned (FOnData) then begin
FOnData(Self, buffer, BytesRead);
end;
finally
FreeMem(buffer);
end;
end;
procedure TRedirector.ReadStdError;
var
BytesRead : INTEGER;
buffer : POINTER;
begin
GetMem (buffer, FAvailable);
try
if not ReadFile (FPipeError.hRead, buffer^, FAvailable, BytesRead, nil) then begin
FThread.Terminate;
WinError('Error reading STDERR pipe : ');
end;
if Assigned (FOnErrorData) then begin
FOnErrorData(Self, buffer, BytesRead);
end;
finally
FreeMem(buffer);
end;
end;
procedure TRedirector.ProcessTermina ted;
begin
FThread.Terminate;
if Assigned (FOnTerminated) then FOnTerminated(Self);
ClosePipes;
CloseHandle(FProcessInfo.h Process);
CloseHandle(FProcessInfo.h Thread);
FillChar(FProcessInfo, SizeOf(FProcessInfo), 0);
end;
////////////////////////// ////////// ////////// ////////// ////////// ////////// ////
// Public methods
////////////////////////// ////////// ////////// ////////// ////////// ////////// ////
procedure TRedirector.Terminate (dwExitCode : INTEGER);
begin
if Running
then TerminateProcess(ProcessHa ndle, dwExitCode)
else Error('Cannot Terminate an inactive process');
end;
procedure TRedirector.Start;
var
StartupInfo : TStartupInfo;
szExecutable,
szParameters,
szDirectory : PChar;
begin
if Running then Error ('Process is already active');
if Trim(CommandLine)='' then Error('No commandline to run');
try
CreatePipes;
FillChar(StartupInfo, SizeOf(StartupInfo), 0);
StartupInfo.cb := SizeOf(StartupInfo);
StartupInfo.wShowWindow := SW_HIDE;
StartupInfo.hStdInput := FPipeInput.hRead;
StartupInfo.hStdOutput := FPipeOutput.hWrite;
StartupInfo.hStdError := FPipeError.hWrite;
StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
if Trim(Executable) = ''
then szExecutable := nil
else szExecutable := PChar(FExecutable);
if Trim(Parameters) = ''
then szParameters := nil
else szParameters := PChar(FParameters);
if Trim(Directory)=''
then szDirectory := nil
else szDirectory := PChar(FDirectory);
if CreateProcess (
szExecutable,
szParameters,
nil,
nil,
TRUE,
(CREATE_DEFAULT_ERROR_MODE and INTEGER(FDefaultErrorMode) )
or (CREATE_SUSPENDED and INTEGER(FStartSuspended)),
Environment,
szDirectory,
StartupInfo,
FProcessInfo)
then begin
FThread := TRedirectorThread.Create(S elf);
end else WinError('Error creating process : ');
except
on Exception do begin
ClosePipes;
CloseHandle(FProcessInfo.h Process);
CloseHandle(FProcessInfo.h Thread);
FillChar(FProcessInfo, SizeOf(FProcessInfo), 0);
raise;
end;
end;
end;
procedure TRedirector.SendData (Buffer : POINTER; BufferSize : INTEGER);
var
BytesWritten : INTEGER;
begin
if not Running then Error ('Can''t send data to an inactive process');
if not WriteFile (FPipeInput.hWrite, Buffer^, BufferSize, BytesWritten, nil)
then WinError('Error writing to STDIN pipe : ');
end;
procedure TRedirector.SendText (s : STRING);
begin
SendData(PChar(s), Length(s));
end;
destructor TRedirector.Destroy;
begin
if Running and KillOnDestroy then begin
FOnTerminated := nil;
FThread.Terminate;
Terminate(0);
end;
inherited Destroy;
end;
constructor TRedirectorThread.Create(A Redirector : TRedirector);
begin
FRedirector := ARedirector;
inherited Create(FALSE);
end;
procedure TRedirectorThread.Execute;
var
Idle : BOOLEAN;
begin
FreeOnTerminate := TRUE;
while not Terminated do begin
Idle := TRUE;
if PeekNamedPipe(FRedirector. FPipeOutpu t.hRead, nil, 0, nil, @FRedirector.FAvailable, nil) and (FRedirector.FAvailable>0) then begin
Synchronize(FRedirector.Re adStdOutpu t);
Idle := FALSE;
end;
if PeekNamedPipe(FRedirector. FPipeError .hRead, nil, 0, nil, @FRedirector.FAvailable, nil) and (FRedirector.FAvailable>0) then begin
Synchronize(FRedirector.Re adStdError );
Idle := FALSE;
end;
if Idle and (WaitForSingleObject(FRedi rector.Pro cessHandle , 100)=WAIT_OBJECT_0) then begin
if not Terminated then Synchronize(FRedirector.Pr ocessTermi nated);
end;
end;
end;
end.
-------------------------
unit CATestMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Redirect;
type
TForm1 = class(TForm)
LB: TListBox;
Panel1: TPanel;
Edit1: TEdit;
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Edit1KeyPress(Sender: TObject; var Key: Char);
procedure FormDestroy(Sender: TObject);
private
FRedirector : TRedirector;
procedure AddText (Text : STRING);
procedure AppDone (Sender :TObject);
procedure NewData (Sender : TRedirector; Buffer : POINTER; BufferSize : INTEGER);
public
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.NewData (Sender : TRedirector; Buffer : POINTER; BufferSize : INTEGER);
var
temp : PChar;
begin
temp := StrAlloc(BufferSize+1);
StrLCopy(temp, Buffer, BufferSize);
temp[BufferSize] := #0;
AddText(string(temp));
StrDispose(temp);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FRedirector := TRedirector.Create;
FRedirector.KillOnDestroy := TRUE;
FRedirector.OnData := NewData;
FRedirector.OnTerminated := AppDone;
end;
procedure TForm1.AddText (Text : STRING);
// Ugly.... Fix.
const
IgnoreNewLine : BOOLEAN = FALSE;
var
n,
ndx : INTEGER;
s : STRING;
function FixTabs (s : STRING) : STRING;
var
n : INTEGER;
begin
n := 1;
while (n<=Length(s)) do begin
if s[n] = #9 then begin
while n MOD 8 <> 0 do begin
Insert (' ', s, n);
inc(n);
end;
s[n] := ' ';
end;
inc(n);
end;
Result := s;
end;
begin
if LB.Items.Count=0 then begin
LB.Items.Add('');
IgnoreNewLine := FALSE;
end;
ndx := 1;
while ndx<=Length(text) do begin
if text[ndx] in [#13, #10] then begin
if IgnoreNewLine then begin
IgnoreNewLine := FALSE;
end else begin
IgnoreNewLine := TRUE;
LB.Items.Add('');
end;
inc (ndx);
continue;
end else begin
s := LB.Items[LB.Items.Count-1] ;
n := ndx-1;
while (n < Length(text)) and not (text[n+1] in [#13, #10]) do inc(n);
LB.Items [LB.Items.Count-1] := FixTabs(s + Copy(text, ndx, n-ndx+1));
ndx := n;
end;
inc(ndx);
end;
LB.ItemIndex := LB.Items.Count-1;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
Edit1.Width := Panel1.Width;
end;
procedure TForm1.AppDone (Sender :TObject);
begin
Close;
end;
procedure TForm1.Edit1KeyPress(Sende r: TObject; var Key: Char);
begin
if Key=#13 then begin
if FRedirector.Running then begin
FRedirector.SendText(Edit1 .Text+#13# 10);
end else begin
FRedirector.CommandLine := Edit1.Text;
FRedirector.Start;
end;
Edit1.Clear;
Key := #0;
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FRedirector.Free;
end;
end.
------------------
Leave me a comment if you get trouble.
The unit "Redirect" contains a class/component which encapsulates a dos box. It's not commented, and I haven't finished testing it out, so there'll probably be errors.
The unit CATestMain is the unit for the test programs main form. Create a new blank project, and reconstruct the events/methods of this unit into your main forms code.
If you experiment a little, you'll see how it works. Feel free to use the Redirector class as you wish, but as said before, it most probably still contains errors.
-------------------------
unit Redirect;
interface
uses
Windows, SysUtils, Classes;
type
TRedirector = class;
TPriorityClass = (pcDefault, pcIdle, pcNormal, pcHigh, pcRealtime);
TDataEvent = procedure (Sender : TRedirector; buffer : POINTER; Size : INTEGER) of object;
TRedirector = class {$IFDEF COMPONENT} (TComponent) {$ENDIF}
private
FAvailable : INTEGER;
procedure ReadStdOutput;
procedure ReadStdError;
procedure ProcessTerminated;
protected
FProcessInfo : TProcessInformation;
FExitCode : INTEGER;
FExecutable : STRING;
FParameters : STRING;
FDefaultErrorMode : BOOLEAN;
FStartSuspended : BOOLEAN;
FKillOnDestroy : BOOLEAN;
FDirectory : STRING;
FEnvironment : POINTER;
FInitialPriority : TPriorityClass;
FPipeInput,
FPipeOutput,
FPipeError :
record
hRead,
hWrite : DWORD;
end;
FThread : TThread;
FOnData,
FOnErrorData : TDataEvent;
FOnTerminated : TNotifyEvent;
procedure Error (msg : STRING);
procedure WinError (msg : STRING);
procedure CreatePipes;
procedure ClosePipes;
function GetRunning : BOOLEAN;
function GetExitCode : INTEGER;
function GetProcessID : INTEGER;
function GetThreadID : INTEGER;
function GetProcessHandle : INTEGER;
function GetThreadHandle : INTEGER;
procedure SetExecutable (value : STRING);
procedure SetParameters (value : STRING);
function GetCommandLine : STRING;
procedure SetCommandLine (value : STRING);
procedure SetDefaultErrorMode (value : BOOLEAN);
procedure SetStartSuspended (value : BOOLEAN);
procedure SetInitialPriority (value : TPriorityClass);
procedure SetDirectory (value : STRING);
procedure SetEnvironment (value : POINTER);
property ProcessHandle : INTEGER
read GetProcessHandle;
property ThreadHandle : INTEGER
read GetThreadHandle;
public
destructor Destroy; override;
procedure Terminate (dwExitCode : INTEGER);
procedure Start;
procedure SendData (Buffer : POINTER; BufferSize : INTEGER);
procedure SendText (s : STRING);
property Running : BOOLEAN
read GetRunning;
property ExitCode : INTEGER
read GetExitCode;
property ProcessID : INTEGER
read GetProcessID;
property ThreadID : INTEGER
read GetThreadID;
property Environment : POINTER
read FEnvironment
write SetEnvironment;
published
property KillOnDestroy : BOOLEAN
read FKillOnDestroy
write FKillOnDestroy;
property Executable : STRING
read FExecutable
write SetExecutable;
property Parameters : STRING
read FParameters
write SetParameters;
property CommandLine : STRING
read GetCommandLine
write SetCommandLine;
property DefaultErrorMode : BOOLEAN
read FDefaultErrorMode
write SetDefaultErrorMode;
property StartSuspended : BOOLEAN
read FStartSuspended
write SetStartSuspended;
property InitialPriority : TPriorityClass
read FInitialPriority
write SetInitialPriority;
property Directory : STRING
read FDirectory
write SetDirectory;
property OnData : TDataEvent
read FOnData
write FOnData;
property OnErrorData : TDataEvent
read FOnErrorData
write FOnErrorData;
property OnTerminated : TNotifyEvent
read FOnTerminated
write FOnTerminated;
end;
implementation
const
DUPLICATE_CLOSE_SOURCE = 1;
DUPLICATE_SAME_ACCESS = 2;
type
TRedirectorThread = class (TThread)
protected
FRedirector : TRedirector;
procedure Execute; override;
constructor Create(ARedirector : TRedirector);
end;
//////////////////////////
// Misc. internal methods
//////////////////////////
procedure TRedirector.Error (msg : STRING);
begin
raise Exception.Create(msg);
end;
procedure TRedirector.WinError (msg : STRING);
begin
Error (msg + IntToStr(GetLastError));
end;
procedure TRedirector.CreatePipes;
var
SecAttr : TSecurityAttributes;
begin
SecAttr.nLength := SizeOf(SecAttr);
SecAttr.lpSecurityDescript
SecAttr.bInheritHandle := TRUE;
with FPipeInput do begin
if not CreatePipe (hRead, hWrite, @SecAttr, 1024)
then WinError('Error on STDIN pipe creation : ');
if not DuplicateHandle (GetCurrentProcess, hRead, GetCurrentProcess, @hRead, 0, TRUE, DUPLICATE_CLOSE_SOURCE or DUPLICATE_SAME_ACCESS)
then WinError('Error on STDIN pipe duplication : ');
end;
with FPipeOutput do begin
if not CreatePipe (hRead, hWrite, @SecAttr, 1024)
then WinError('Error on STDOUT pipe creation : ');
if not DuplicateHandle (GetCurrentProcess, hWrite, GetCurrentProcess, @hWrite, 0, TRUE, DUPLICATE_CLOSE_SOURCE or DUPLICATE_SAME_ACCESS)
then WinError('Error on STDOUT pipe duplication : ');
end;
with FPipeError do begin
if not CreatePipe (hRead, hWrite, @SecAttr, 1024)
then WinError('Error on STDERR pipe creation : ');
if not DuplicateHandle (GetCurrentProcess, hWrite, GetCurrentProcess, @hWrite, 0, TRUE, DUPLICATE_CLOSE_SOURCE or DUPLICATE_SAME_ACCESS)
then WinError('Error on STDERR pipe duplication : ');
end;
end;
procedure TRedirector.ClosePipes;
begin
with FPipeInput do begin
if hRead <> 0 then CloseHandle (hRead);
if hWrite <> 0 then CloseHandle (hWrite);
hRead := 0;
hWrite := 0;
end;
with FPipeOutput do begin
if hRead <> 0 then CloseHandle (hRead);
if hWrite <> 0 then CloseHandle (hWrite);
hRead := 0;
hWrite := 0;
end;
with FPipeError do begin
if hRead <> 0 then CloseHandle (hRead);
if hWrite <> 0 then CloseHandle (hWrite);
hRead := 0;
hWrite := 0;
end;
end;
//////////////////////////
// Property implementations
//////////////////////////
function TRedirector.GetRunning : BOOLEAN;
begin
Result := ProcessHandle<>0;
end;
function TRedirector.GetExitCode : INTEGER;
begin
if Running
then Result := STILL_ACTIVE
else Result := FExitCode;
end;
function TRedirector.GetProcessID : INTEGER;
begin
Result := FProcessInfo.dwProcessID;
end;
function TRedirector.GetThreadID : INTEGER;
begin
Result := FProcessInfo.dwThreadID;
end;
function TRedirector.GetProcessHand
begin
Result := FProcessInfo.hProcess;
end;
function TRedirector.GetThreadHandl
begin
Result := FProcessInfo.hThread;
end;
procedure TRedirector.SetExecutable (value : STRING);
begin
if (ANSICompareText(value, Executable) = 0) or not Running
then FExecutable := value
else if Running
then Error('Cannot change Executable while process is active');
end;
procedure TRedirector.SetParameters (value : STRING);
begin
if (ANSICompareText(value, Parameters) = 0) or not Running
then FParameters := value
else if Running
then Error('Cannot change Parameters while process is active');
end;
function TRedirector.GetCommandLine
begin
Result := FExecutable;
if Result = ''
then Result := FParameters
else Result := FExecutable + ' ' + FParameters;
end;
procedure TRedirector.SetCommandLine
var
n1,
n2 : INTEGER;
begin
if (ANSICompareText (value, CommandLine) = 0) or (not Running) then begin
n1 := Length(value);
n2 := Pos(' ', value);
if (n2>0) and (n2<n1) then n1 := n2;
n2 := Pos('-', value);
if (n2>0) and (n2<n1) then n1 := n2;
n2 := Pos('/', value);
if (n2>0) and (n2<n1) then n1 := n2;
FExecutable := Copy (value, 1, n1-1);
FParameters := Trim(Copy (value, Length(FExecutable)+1, Length(value)));
end else if Running then Error('Cannot change CommandLine while process is active');
end;
procedure TRedirector.SetDefaultErro
begin
if (value = DefaultErrorMode) or not Running
then FDefaultErrorMode := value
else if Running
then Error('Cannot change DefaultErrorMode while process is active');
end;
procedure TRedirector.SetStartSuspen
begin
if (value = DefaultErrorMode) or not Running
then FStartSuspended:= value
else if Running
then Error('Cannot change StartSuspended while process is active');
end;
procedure TRedirector.SetInitialPrio
begin
if (value = InitialPriority) or not Running
then FInitialPriority := value
else if Running
then Error('Cannot change InititalPriority while process is active');
end;
procedure TRedirector.SetDirectory (value : STRING);
begin
if (ANSICompareText (value, Directory) = 0) or (not Running)
then FDirectory:= value
else if Running
then Error('Cannot change Directory while process is active');
end;
procedure TRedirector.SetEnvironment
begin
if (value = Environment) or not Running
then FEnvironment := value
else if Running
then Error('Cannot change Environment while process is active');
end;
procedure TRedirector.ReadStdOutput;
var
BytesRead : INTEGER;
buffer : POINTER;
begin
GetMem (buffer, FAvailable);
try
if not ReadFile (FPipeOutput.hRead, buffer^, FAvailable, BytesRead, nil) then begin
FThread.Terminate;
WinError('Error reading STDOUT pipe : ');
end;
if Assigned (FOnData) then begin
FOnData(Self, buffer, BytesRead);
end;
finally
FreeMem(buffer);
end;
end;
procedure TRedirector.ReadStdError;
var
BytesRead : INTEGER;
buffer : POINTER;
begin
GetMem (buffer, FAvailable);
try
if not ReadFile (FPipeError.hRead, buffer^, FAvailable, BytesRead, nil) then begin
FThread.Terminate;
WinError('Error reading STDERR pipe : ');
end;
if Assigned (FOnErrorData) then begin
FOnErrorData(Self, buffer, BytesRead);
end;
finally
FreeMem(buffer);
end;
end;
procedure TRedirector.ProcessTermina
begin
FThread.Terminate;
if Assigned (FOnTerminated) then FOnTerminated(Self);
ClosePipes;
CloseHandle(FProcessInfo.h
CloseHandle(FProcessInfo.h
FillChar(FProcessInfo, SizeOf(FProcessInfo), 0);
end;
//////////////////////////
// Public methods
//////////////////////////
procedure TRedirector.Terminate (dwExitCode : INTEGER);
begin
if Running
then TerminateProcess(ProcessHa
else Error('Cannot Terminate an inactive process');
end;
procedure TRedirector.Start;
var
StartupInfo : TStartupInfo;
szExecutable,
szParameters,
szDirectory : PChar;
begin
if Running then Error ('Process is already active');
if Trim(CommandLine)='' then Error('No commandline to run');
try
CreatePipes;
FillChar(StartupInfo, SizeOf(StartupInfo), 0);
StartupInfo.cb := SizeOf(StartupInfo);
StartupInfo.wShowWindow := SW_HIDE;
StartupInfo.hStdInput := FPipeInput.hRead;
StartupInfo.hStdOutput := FPipeOutput.hWrite;
StartupInfo.hStdError := FPipeError.hWrite;
StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
if Trim(Executable) = ''
then szExecutable := nil
else szExecutable := PChar(FExecutable);
if Trim(Parameters) = ''
then szParameters := nil
else szParameters := PChar(FParameters);
if Trim(Directory)=''
then szDirectory := nil
else szDirectory := PChar(FDirectory);
if CreateProcess (
szExecutable,
szParameters,
nil,
nil,
TRUE,
(CREATE_DEFAULT_ERROR_MODE
or (CREATE_SUSPENDED and INTEGER(FStartSuspended)),
Environment,
szDirectory,
StartupInfo,
FProcessInfo)
then begin
FThread := TRedirectorThread.Create(S
end else WinError('Error creating process : ');
except
on Exception do begin
ClosePipes;
CloseHandle(FProcessInfo.h
CloseHandle(FProcessInfo.h
FillChar(FProcessInfo, SizeOf(FProcessInfo), 0);
raise;
end;
end;
end;
procedure TRedirector.SendData (Buffer : POINTER; BufferSize : INTEGER);
var
BytesWritten : INTEGER;
begin
if not Running then Error ('Can''t send data to an inactive process');
if not WriteFile (FPipeInput.hWrite, Buffer^, BufferSize, BytesWritten, nil)
then WinError('Error writing to STDIN pipe : ');
end;
procedure TRedirector.SendText (s : STRING);
begin
SendData(PChar(s), Length(s));
end;
destructor TRedirector.Destroy;
begin
if Running and KillOnDestroy then begin
FOnTerminated := nil;
FThread.Terminate;
Terminate(0);
end;
inherited Destroy;
end;
constructor TRedirectorThread.Create(A
begin
FRedirector := ARedirector;
inherited Create(FALSE);
end;
procedure TRedirectorThread.Execute;
var
Idle : BOOLEAN;
begin
FreeOnTerminate := TRUE;
while not Terminated do begin
Idle := TRUE;
if PeekNamedPipe(FRedirector.
Synchronize(FRedirector.Re
Idle := FALSE;
end;
if PeekNamedPipe(FRedirector.
Synchronize(FRedirector.Re
Idle := FALSE;
end;
if Idle and (WaitForSingleObject(FRedi
if not Terminated then Synchronize(FRedirector.Pr
end;
end;
end;
end.
-------------------------
unit CATestMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Redirect;
type
TForm1 = class(TForm)
LB: TListBox;
Panel1: TPanel;
Edit1: TEdit;
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure Edit1KeyPress(Sender: TObject; var Key: Char);
procedure FormDestroy(Sender: TObject);
private
FRedirector : TRedirector;
procedure AddText (Text : STRING);
procedure AppDone (Sender :TObject);
procedure NewData (Sender : TRedirector; Buffer : POINTER; BufferSize : INTEGER);
public
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.NewData (Sender : TRedirector; Buffer : POINTER; BufferSize : INTEGER);
var
temp : PChar;
begin
temp := StrAlloc(BufferSize+1);
StrLCopy(temp, Buffer, BufferSize);
temp[BufferSize] := #0;
AddText(string(temp));
StrDispose(temp);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FRedirector := TRedirector.Create;
FRedirector.KillOnDestroy := TRUE;
FRedirector.OnData := NewData;
FRedirector.OnTerminated := AppDone;
end;
procedure TForm1.AddText (Text : STRING);
// Ugly.... Fix.
const
IgnoreNewLine : BOOLEAN = FALSE;
var
n,
ndx : INTEGER;
s : STRING;
function FixTabs (s : STRING) : STRING;
var
n : INTEGER;
begin
n := 1;
while (n<=Length(s)) do begin
if s[n] = #9 then begin
while n MOD 8 <> 0 do begin
Insert (' ', s, n);
inc(n);
end;
s[n] := ' ';
end;
inc(n);
end;
Result := s;
end;
begin
if LB.Items.Count=0 then begin
LB.Items.Add('');
IgnoreNewLine := FALSE;
end;
ndx := 1;
while ndx<=Length(text) do begin
if text[ndx] in [#13, #10] then begin
if IgnoreNewLine then begin
IgnoreNewLine := FALSE;
end else begin
IgnoreNewLine := TRUE;
LB.Items.Add('');
end;
inc (ndx);
continue;
end else begin
s := LB.Items[LB.Items.Count-1]
n := ndx-1;
while (n < Length(text)) and not (text[n+1] in [#13, #10]) do inc(n);
LB.Items [LB.Items.Count-1] := FixTabs(s + Copy(text, ndx, n-ndx+1));
ndx := n;
end;
inc(ndx);
end;
LB.ItemIndex := LB.Items.Count-1;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
Edit1.Width := Panel1.Width;
end;
procedure TForm1.AppDone (Sender :TObject);
begin
Close;
end;
procedure TForm1.Edit1KeyPress(Sende
begin
if Key=#13 then begin
if FRedirector.Running then begin
FRedirector.SendText(Edit1
end else begin
FRedirector.CommandLine := Edit1.Text;
FRedirector.Start;
end;
Edit1.Clear;
Key := #0;
end;
end;
procedure TForm1.FormDestroy(Sender:
begin
FRedirector.Free;
end;
end.
------------------
Leave me a comment if you get trouble.
BTW.
Use the program by first typing complete path, filename and extension of command.com or the app you wan't to execute into the edit, and press enter. Then, type whatever like dir, attrib or something and press enter to send the data to the dos-box.
Don't try running e.g. dos edit, the redirector only handles standard line-by-line output programs. Arj, pkzip and so on works fine.
Regards,
Erik.
Use the program by first typing complete path, filename and extension of command.com or the app you wan't to execute into the edit, and press enter. Then, type whatever like dir, attrib or something and press enter to send the data to the dos-box.
Don't try running e.g. dos edit, the redirector only handles standard line-by-line output programs. Arj, pkzip and so on works fine.
Regards,
Erik.
ASKER
Thanks a lot for excellent help :)
To all : remember the "/c" parameter after command.com
To all : remember the "/c" parameter after command.com
Regards,
Erik.