I'm getting Chinese Text when using Windows 10 pipes in Delphi. How do I make it in English? - delphi

I'm getting Chinese Text when using Windows 10 pipes in trying to read and write to the command prompt in Delphi. How do I make it in English?
The information from Communicate With Command Prompt Through Delphi is outdated for Delphi 10.3 and I had to modify a few variables to meet the compiler's wants. Changed a few integers to cardinals, etc. It's just outputting gibberish!
Here is the code for the component I'm trying to create based on the above link:
unit gtCommandPrompt;
interface
uses
System.SysUtils, System.Classes, Windows;
type
TTmonitorUpdate = procedure(OutPut: String) of object;
TTmonitor = class(TThread) // pipe monitoring thread for console output
private
iThreadSleep: Cardinal;
TextString: String;
FTTmonitorUpdate: TTmonitorUpdate;
procedure UpdateComponent;
protected
procedure Execute; override;
public
property OnUpdateComponent: TTmonitorUpdate read FTTmonitorUpdate write FTTmonitorUpdate;
end;
TOnReadCommandPrompt = procedure(OutPut: String) of object;
TOnWriteCommandPrompt = procedure(OutPut: String) of object;
TOnError = procedure(OutPut: String) of object;
TCommandPrompt = class(TComponent)
private
{ Private declarations }
ThreadDone: Boolean;
FThreadSleep: Cardinal;
FComponentThread: TTmonitor;
FOnError: TOnError;
FOnReadCommandPrompt : TOnReadCommandPrompt;
FOnWriteCommandPrompt : TOnWriteCommandPrompt;
procedure OnThreadUpdate(OutPut: String);
protected
{ Protected declarations }
public
{ Public declarations }
procedure Start();
procedure Stop();
procedure cmdWriteln(text: String);
constructor Create(AOwner: TComponent); override;
published
{ Published declarations }
property ThreadSleep: Cardinal read FThreadSleep write FThreadSleep default 40;
property OnReadCommandPrompt: TOnReadCommandPrompt read FOnReadCommandPrompt write FOnReadCommandPrompt;
property OnWriteCommandPrompt: TOnWriteCommandPrompt read FOnWriteCommandPrompt write FOnWriteCommandPrompt;
property OnError: TOnError read FOnError write FOnError;
Destructor Destroy; override;
end;
procedure Register;
var
InputPipeRead, InputPipeWrite: THandle;
OutputPipeRead, OutputPipeWrite: THandle;
ErrorPipeRead, ErrorPipeWrite: THandle;
ProcessInfo : TProcessInformation;
implementation
procedure Register;
begin
RegisterComponents('gtDelphi', [TCommandPrompt]);
end;
constructor TCommandPrompt.Create(AOwner: TComponent);
begin
inherited;
ThreadDone := true;
FThreadSleep := 40;
end;
procedure WritePipeOut(OutputPipe: THandle; InString: string);
// writes Instring to the pipe handle described by OutputPipe
var
byteswritten: Cardinal;
begin
// most console programs require CR/LF after their input.
InString := InString + #13#10;
WriteFile(OutputPipe, Instring[1], Length(Instring), byteswritten, nil);
end;
function ReadPipeInput(InputPipe: THandle; var BytesRem: Cardinal): String;
{
reads console output from InputPipe. Returns the input in function
result. Returns bytes of remaining information to BytesRem
}
var
TextBuffer: array[1..32767] of char;
TextString: String;
BytesRead: Cardinal;
PipeSize: Cardinal;
begin
Result := '';
BytesRead := 0;
PipeSize := Sizeof(TextBuffer);
// check if there is something to read in pipe
PeekNamedPipe(InputPipe, nil, PipeSize, #BytesRead, #PipeSize, #BytesRem);
if bytesread > 0 then
begin
ReadFile(InputPipe, TextBuffer, pipesize, bytesread, nil);
// a requirement for Windows OS system components
OemToChar(#TextBuffer, #TextBuffer);
TextString := String(TextBuffer);
SetLength(TextString, BytesRead);
Result := TextString;
end;
end;
procedure TTmonitor.Execute;
{ monitor thread execution for console output. This must be threaded.
checks the error and output pipes for information every 40 ms, pulls the
data in and updates the memo on the form with the output }
var
BytesRem: Cardinal;
begin
while not Terminated do
begin
// read regular output stream and put on screen.
TextString := ReadPipeInput(OutputPipeRead, BytesRem);
if TextString <> '' then
Synchronize(UpdateComponent);
// now read error stream and put that on screen.
TextString := ReadPipeInput(ErrorPipeRead, BytesRem);
if TextString <> '' then
Synchronize(UpdateComponent);
sleep(iThreadSleep);
end;
end;
procedure TTmonitor.UpdateComponent;
// synchronize procedure for monitor thread
begin
if assigned(FTTmonitorUpdate) = true then FTTmonitorUpdate(TextString);
end;
procedure TCommandPrompt.OnThreadUpdate(OutPut: String);
// synchronize procedure for monitor thread
begin
if assigned(FOnReadCommandPrompt) = true then FOnReadCommandPrompt(OutPut);
end;
Destructor TCommandPrompt.Destroy;
begin
WritePipeOut(InputPipeWrite, 'EXIT'); // quit the CMD we started
FComponentThread.Terminate;
// close process handles
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
// close pipe handles
CloseHandle(InputPipeRead);
CloseHandle(InputPipeWrite);
CloseHandle(OutputPipeRead);
CloseHandle(OutputPipeWrite);
CloseHandle(ErrorPipeRead);
CloseHandle(ErrorPipeWrite);
// Always call the parent destructor after running your own code
inherited;
end;
procedure TCommandPrompt.cmdWriteln(text: String);
begin
WritePipeOut(InputPipeWrite, text);
if assigned(FOnWriteCommandPrompt) = true then FOnWriteCommandPrompt(text);
end;
procedure TCommandPrompt.Stop();
begin
FComponentThread.Terminate;
ThreadDone := true;
end;
procedure TCommandPrompt.Start();
{ upon form creation, this calls the command-interpreter, sets up the three
pipes to catch input and output, and starts a thread to monitor and show
the output of the command-interpreter }
var
DosApp: String;
DosSize: Byte; // was integer
Security : TSecurityAttributes;
start : TStartUpInfo;
begin
if ThreadDone = false then
begin
if assigned(FOnError) then FOnError('Start Error: Thread already running!');
exit;
end;
//CommandText.Clear;
// get COMSPEC variable, this is the path of the command-interpreter
SetLength(Dosapp, 255);
DosSize := GetEnvironmentVariable('COMSPEC', #DosApp[1], 255);
SetLength(Dosapp, DosSize);
// create pipes
With Security do
begin
nlength := SizeOf(TSecurityAttributes) ;
binherithandle := true;
lpsecuritydescriptor := nil;
end;
CreatePipe(InputPipeRead, InputPipeWrite, #Security, 0);
CreatePipe(OutputPipeRead, OutputPipeWrite, #Security, 0);
CreatePipe(ErrorPipeRead, ErrorPipeWrite, #Security, 0);
// start command-interpreter
FillChar(Start,Sizeof(Start),#0) ;
start.cb := SizeOf(start) ;
start.hStdInput := InputPipeRead;
start.hStdOutput := OutputPipeWrite;
start.hStdError := ErrorPipeWrite;
start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
start.wShowWindow := SW_HIDE;
if CreateProcess(nil, PChar(DosApp), #Security, #Security, true,
CREATE_NEW_CONSOLE or SYNCHRONIZE,
nil, nil, start, ProcessInfo) then
begin
FComponentThread := TTmonitor.Create(true); // don't start yet monitor thread
FComponentThread.Priority := tpHigher;
FComponentThread.iThreadSleep := 40;
FComponentThread.FreeOnTerminate := true;
FComponentThread.OnUpdateComponent := OnThreadUpdate;
ThreadDone := false;
FComponentThread.Start; // start thread;
end;
end;
end.

Thanks whosrdaddy!
Here is the updated code that now works:
unit gtCommandPrompt;
interface
uses
System.SysUtils, System.Classes, Windows;
type
TTmonitorUpdate = procedure(OutPut: AnsiString) of object;
TTmonitor = class(TThread) // pipe monitoring thread for console output
private
iThreadSleep: Cardinal;
TextString: AnsiString;
FTTmonitorUpdate: TTmonitorUpdate;
procedure UpdateComponent;
protected
procedure Execute; override;
public
property OnUpdateComponent: TTmonitorUpdate read FTTmonitorUpdate write FTTmonitorUpdate;
end;
TOnReadCommandPrompt = procedure(OutPut: AnsiString) of object;
TOnWriteCommandPrompt = procedure(OutPut: AnsiString) of object;
TOnError = procedure(OutPut: AnsiString) of object;
TCommandPrompt = class(TComponent)
private
{ Private declarations }
ThreadDone: Boolean;
FThreadSleep: Cardinal;
FComponentThread: TTmonitor;
FOnError: TOnError;
FOnReadCommandPrompt : TOnReadCommandPrompt;
FOnWriteCommandPrompt : TOnWriteCommandPrompt;
procedure OnThreadUpdate(OutPut: AnsiString);
protected
{ Protected declarations }
public
{ Public declarations }
procedure Start();
procedure Stop();
procedure cmdWriteln(text: AnsiString);
constructor Create(AOwner: TComponent); override;
published
{ Published declarations }
property ThreadSleep: Cardinal read FThreadSleep write FThreadSleep default 40;
property OnReadCommandPrompt: TOnReadCommandPrompt read FOnReadCommandPrompt write FOnReadCommandPrompt;
property OnWriteCommandPrompt: TOnWriteCommandPrompt read FOnWriteCommandPrompt write FOnWriteCommandPrompt;
property OnError: TOnError read FOnError write FOnError;
Destructor Destroy; override;
end;
procedure Register;
var
InputPipeRead, InputPipeWrite: THandle;
OutputPipeRead, OutputPipeWrite: THandle;
ErrorPipeRead, ErrorPipeWrite: THandle;
ProcessInfo : TProcessInformation;
implementation
procedure Register;
begin
RegisterComponents('gtDelphi', [TCommandPrompt]);
end;
constructor TCommandPrompt.Create(AOwner: TComponent);
begin
inherited;
ThreadDone := true;
FThreadSleep := 40;
end;
procedure WritePipeOut(OutputPipe: THandle; InString: AnsiString);
// writes Instring to the pipe handle described by OutputPipe
var
byteswritten: Cardinal;
begin
// most console programs require CR/LF after their input.
InString := InString + #13#10;
WriteFile(OutputPipe, Instring[1], Length(Instring), byteswritten, nil);
end;
function ReadPipeInput(InputPipe: THandle; var BytesRem: Cardinal): AnsiString;
{
reads console output from InputPipe. Returns the input in function
result. Returns bytes of remaining information to BytesRem
}
var
cTextBuffer: array[1..32767] of AnsiChar;
sTextString: AnsiString;
cBytesRead: Cardinal;
cPipeSize: Cardinal;
begin
Result := '';
cBytesRead := 0;
cPipeSize := Sizeof(cTextBuffer);
// check if there is something to read in pipe
PeekNamedPipe(InputPipe, nil, cPipeSize, #cBytesRead, #cPipeSize, #BytesRem);
if cBytesRead > 0 then
begin
ReadFile(InputPipe, cTextBuffer, cPipeSize, cBytesRead, nil);
// a requirement for Windows OS system components
OemToCharA(#cTextBuffer, #cTextBuffer);
sTextString := AnsiString(cTextBuffer);
SetLength(sTextString, cBytesRead);
Result := sTextString;
end;
end;
procedure TTmonitor.Execute;
{ monitor thread execution for console output. This must be threaded.
checks the error and output pipes for information every 40 ms, pulls the
data in and updates the memo on the form with the output }
var
BytesRem: Cardinal;
begin
while not Terminated do
begin
// read regular output stream and put on screen.
TextString := ReadPipeInput(OutputPipeRead, BytesRem);
if TextString <> '' then
Synchronize(UpdateComponent);
// now read error stream and put that on screen.
TextString := ReadPipeInput(ErrorPipeRead, BytesRem);
if TextString <> '' then
Synchronize(UpdateComponent);
sleep(iThreadSleep);
end;
end;
procedure TTmonitor.UpdateComponent;
// synchronize procedure for monitor thread
begin
if assigned(FTTmonitorUpdate) = true then
begin
try
FTTmonitorUpdate(TextString);
finally
end;
end;
end;
procedure TCommandPrompt.OnThreadUpdate(OutPut: AnsiString);
// synchronize procedure for monitor thread
begin
if assigned(FOnReadCommandPrompt) = true then
try
FOnReadCommandPrompt(OutPut);
finally
end;
end;
Destructor TCommandPrompt.Destroy;
begin
WritePipeOut(InputPipeWrite, 'EXIT'); // quit the CMD we started
FComponentThread.Terminate;
// close process handles
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
// close pipe handles
CloseHandle(InputPipeRead);
CloseHandle(InputPipeWrite);
CloseHandle(OutputPipeRead);
CloseHandle(OutputPipeWrite);
CloseHandle(ErrorPipeRead);
CloseHandle(ErrorPipeWrite);
// Always call the parent destructor after running your own code
inherited;
end;
procedure TCommandPrompt.cmdWriteln(text: AnsiString);
begin
WritePipeOut(InputPipeWrite, text);
if assigned(FOnWriteCommandPrompt) = true then
try
FOnWriteCommandPrompt(text);
finally
end;
end;
procedure TCommandPrompt.Stop();
begin
FComponentThread.Terminate;
ThreadDone := true;
end;
procedure TCommandPrompt.Start();
{ upon form creation, this calls the command-interpreter, sets up the three
pipes to catch input and output, and starts a thread to monitor and show
the output of the command-interpreter }
var
DosApp: String;
DosSize: Byte; // was integer
Security : TSecurityAttributes;
start : TStartUpInfo;
begin
if ThreadDone = false then
begin
if assigned(FOnError) then
try
FOnError('Start Error: Thread already running!');
finally
end;
exit;
end;
//CommandText.Clear;
// get COMSPEC variable, this is the path of the command-interpreter
SetLength(Dosapp, 255);
DosSize := GetEnvironmentVariable('COMSPEC', #DosApp[1], 255);
SetLength(Dosapp, DosSize);
// create pipes
With Security do
begin
nlength := SizeOf(TSecurityAttributes) ;
binherithandle := true;
lpsecuritydescriptor := nil;
end;
CreatePipe(InputPipeRead, InputPipeWrite, #Security, 0);
CreatePipe(OutputPipeRead, OutputPipeWrite, #Security, 0);
CreatePipe(ErrorPipeRead, ErrorPipeWrite, #Security, 0);
// start command-interpreter
FillChar(Start,Sizeof(Start),#0) ;
start.cb := SizeOf(start) ;
start.hStdInput := InputPipeRead;
start.hStdOutput := OutputPipeWrite;
start.hStdError := ErrorPipeWrite;
start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
start.wShowWindow := SW_HIDE;
if CreateProcess(nil, PChar(DosApp), #Security, #Security, true,
CREATE_NEW_CONSOLE or SYNCHRONIZE,
nil, nil, start, ProcessInfo) then
begin
FComponentThread := TTmonitor.Create(true); // don't start yet monitor thread
FComponentThread.Priority := tpHigher;
FComponentThread.iThreadSleep := 40;
FComponentThread.FreeOnTerminate := true;
FComponentThread.OnUpdateComponent := OnThreadUpdate;
ThreadDone := false;
FComponentThread.Start; // start thread;
end;
end;
end.

Related

Redirect stdout stream from console application (CreateProcess)

Recently I finally managed to redirect console application output to TMemo text field of another application using an example from Microsoft: https://learn.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
All the classical examples run a console executable, wait till it ends and then read its STDOUT. I would like to launch a long-running executable that is normally not intended to end, and get its STDOUT stream as soon as new characters become available.
I managed to modify this example so that a read-write part is a loop and runs in a thread (TProcessExecuterThread.Execute). Now I am in doubt whether I should use the thread at all.
Additionally, the host receives not the whole strings till CR-LF even if I get from a pipe one character after other (TProcessExecuterThread.ReadFromPipe).
Finally I am concerned what about ending the host. The guest should then receive a signal to terminate and after some timeout - should be killed. Where (not "how") is it better to organize this?
Here is the console guest application for the test:
{$APPTYPE CONSOLE}
program GuestApp;
uses System.SysUtils;
var i: Integer;
begin
Writeln('Ongoing console output:');
for i := 0 to 65535 do begin //while True do begin
if i mod 2 = 0 then Write('*');
Writeln(Format('Output line %d', [i]));
Sleep(500);
end;
end.
Here is the host application (sorry, it is not short):
unit Executer;
interface
uses Winapi.Windows, System.Classes, System.Generics.Collections;
type
TProcessExecuterThread = class(TThread)
private
FStdInQueue: TQueue<string>;
FhChildStdOutRd: THandle;
FhChildStdInWr: THandle;
FOnStdOutLog: TGetStrProc;
procedure ReadFromPipe();
procedure WriteToPipe();
procedure StdOutLog(msg: string);
protected
procedure Execute(); override;
property hChildStdOutRd: THandle read FhChildStdOutRd write FhChildStdOutRd;
property hChildStdInWr: THandle read FhChildStdInWr write FhChildStdInWr;
property OnstdOutLog: TGetStrProc read FOnStdOutLog write FOnStdOutLog;
end;
TProcessExecuter = class
private const
BUFSIZE = 4096;
private
FhChildStdInRd: THandle;
FhChildStdInWr: THandle;
FhChildStdOutRd: THandle;
FhChildStdOutWr: THandle;
FOnLog: TGetStrProc;
FOnStdOutLog: TGetStrProc;
FExThread: TProcessExecuterThread;
procedure CreateChildProcess(ACmdLine: string);
procedure ErrorExit(AFuncName: string);
procedure Log(msg: string);
procedure StdOutLog(const msg: string);
function KillProcess(dwProcID, Wait: DWORD): Integer;
public
constructor Create();
function RunRedirectedProcess(ACmdLine: string): Integer;
property OnLog: TGetStrProc read FOnLog write FOnLog;
property OnstdOutLog: TGetStrProc read FOnStdOutLog write FOnStdOutLog;
end;
implementation
uses System.SysUtils;
procedure TProcessExecuter.Log(msg: string);
begin
if Assigned(FOnLog) then FOnLog(msg);
end;
procedure TProcessExecuter.StdOutLog(const msg: string);
begin
if Assigned(FOnStdOutLog) then FOnStdOutLog(msg);
end;
// Format a readable error message, display a message box,
// and exit from the application.
procedure TProcessExecuter.ErrorExit(AFuncName: string);
var msg: string;
dw: DWORD;
begin
dw := GetLastError();
msg := Format('%s failed with error %d: %s', [AFuncName, dw, SysErrorMessage(dw)]);
Log(msg);
// ExitProcess(1);
end;
constructor TProcessExecuter.Create();
begin
FhChildStdInRd := 0;
FhChildStdInWr := 0;
FhChildStdOutRd := 0;
FhChildStdOutWr := 0;
FExThread := TProcessExecuterThread.Create();
FExThread.OnstdOutLog := StdOutLog;
end;
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
procedure TProcessExecuter.CreateChildProcess(ACmdLine: string);
var
piProcInfo: TProcessInformation;
siStartInfo: TStartupInfo;
bSuccess: Boolean;
begin
try
bSuccess := False;
FillChar(piProcInfo, SizeOf(TProcessInformation), 0);
FillChar(siStartInfo, SizeOf(TStartupInfo), 0);
siStartInfo.cb := SizeOf(TStartupInfo);
siStartInfo.hStdError := FhChildStdOutWr;
siStartInfo.hStdOutput := FhChildStdOutWr;
siStartInfo.hStdInput := FhChildStdInRd;
siStartInfo.dwFlags := siStartInfo.dwFlags or STARTF_USESTDHANDLES;
bSuccess := CreateProcess(nil, PWideChar(ACmdLine), nil, nil, True, 0, nil, nil, siStartInfo, piProcInfo);
if not bSuccess then begin
ErrorExit('CreateProcess');
Exit;
end
else begin
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
CloseHandle(FhChildStdOutWr);
CloseHandle(FhChildStdInRd);
end;
FExThread.hChildStdOutRd := FhChildStdOutRd;
FExThread.hChildStdInWr := FhChildStdInWr;
except
on ex: Exception do Log(ex.Message);
end;
end;
function TProcessExecuter.RunRedirectedProcess(ACmdLine: string): Integer;
var saAttr: SECURITY_ATTRIBUTES;
i: Integer;
begin
try
Log('->Start of parent execution.');
saAttr.nLength := SizeOf(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle := True;
saAttr.lpSecurityDescriptor := 0;
if not CreatePipe(FhChildStdOutRd, FhChildStdOutWr, #saAttr, 0) then begin
ErrorExit('StdoutRd CreatePipe');
Exit;
end;
if not SetHandleInformation(FhChildStdOutRd, HANDLE_FLAG_INHERIT, 0) then begin
ErrorExit('Stdout SetHandleInformation');
Exit;
end;
if not CreatePipe(FhChildStdInRd, FhChildStdInWr, #saAttr, 0) then begin
ErrorExit('Stdin CreatePipe');
Exit;
end;
if not SetHandleInformation(FhChildStdInWr, HANDLE_FLAG_INHERIT, 0) then begin
ErrorExit('Stdin SetHandleInformation');
Exit;
end;
CreateChildProcess(ACmdLine);
//Read/write loop was here
Log('->End of parent execution.');
if not CloseHandle(FhChildStdInWr) then begin
ErrorExit('StdInWr CloseHandle');
Exit;
end;
Result := 0;
except
on ex: Exception do Log(ex.Message);
end;
end;
procedure TProcessExecuterThread.WriteToPipe();
var dwRead, dwWritten: DWORD;
chBuf: Pointer;
bSuccess: Boolean;
line: string;
bs: Integer;
begin
bSuccess := False;
while FStdInQueue.Count > 0 do begin
line := FStdInQueue.Dequeue();
bs := (Length(line) + 1) * SizeOf(WideChar);
GetMem(chBuf, bs);
try
StrPCopy(PWideChar(chBuf), line);
if not WriteFile(FhChildStdInWr, chBuf^, dwRead, dwWritten, nil) then break;
finally
FreeMem(chBuf, bs);
end;
end;
end;
procedure TProcessExecuterThread.ReadFromPipe();
const BUFSIZE = 1; //4096
var dwRead: DWORD;
//chBuf: array [0 .. BUFSIZE] of CHAR;
chBuf: array [0 .. BUFSIZE] of AnsiChar; // Currently only ANSI is possible
ch: AnsiChar;
bSuccess: Boolean;
begin
bSuccess := False;
while True do begin
//bSuccess := ReadFile(FhChildStdOutRd, chBuf, BUFSIZE, dwRead, nil);
bSuccess := ReadFile(FhChildStdOutRd, ch, 1, dwRead, nil);
if (not bSuccess) or (dwRead = 0) then
break;
//StdOutLog(chBuf);
StdOutLog(ch);
end;
end;
procedure TProcessExecuterThread.StdOutLog(msg: string);
begin
if Assigned(FOnStdOutLog) then
Synchronize(
procedure()
begin
FOnStdOutLog(msg);
end
);
end;
procedure TProcessExecuterThread.Execute;
begin
inherited;
FStdInQueue := TQueue<string>.Create();
try
while not Terminated do begin
WriteToPipe();
ReadFromPipe();
end;
finally
FreeAndNil(FStdInQueue);
end;
end;
end.

Synchronize Method, can I use it to not main thread?

I rarely use threads and I have question about this class:
unit ExpectingThread;
interface
uses
System.Classes;
type
TExpectingThread = class(TThread)
private
_timeoutMs: Integer;
_buff: string;
_patterns: TArray<string>;
_result: Integer;
function Timeouted(startTime: Cardinal): Boolean;
function ExpectedDetected: Boolean;
protected
procedure Execute; override;
public
constructor Create(patterns: TArray<string>; buff: string; timeoutMs: Integer);
//this method is called from other NOT MAIN thread
procedure BuffUpdate(text: string);
end;
implementation
uses
Winapi.Windows,
System.RegularExpressions;
{ TExpectingThread }
constructor TExpectingThread.Create(patterns: TArray<string>; buff: string; timeoutMs: Integer);
begin
_patterns := patterns;
_timeoutMs := timeoutMs;
_buff := buff;
end;
//this method is called from other NOT MAIN thread
procedure TExpectingThread.BuffUpdate(text: string);
begin
// lock
TThread.Synchronize(Self, procedure
begin
_buff := _buff + text;
end);
// unlock
end;
procedure TExpectingThread.Execute;
var
startTime: Cardinal;
begin
inherited;
startTime := GetTickCount;
while true do
begin
if Timeouted(startTime) then
begin
Self.ReturnValue := 0; // timeouted
Exit;
end;
if ExpectedDetected then
begin
Self.ReturnValue := 1; // found
Exit;
end;
end;
end;
function TExpectingThread.ExpectedDetected: Boolean;
var
regex: TRegEx;
i: Integer;
begin
// lock
result := 0;
for i := 0 to High(_patterns) do
begin
regex.Create(_patterns[i]);
if regex.IsMatch(_buff) then
begin
_result := i;
Exit(true);
end;
end;
// unlock
end;
function TExpectingThread.Timeouted(startTime: Cardinal): Boolean;
var
currentTime: Cardinal;
begin
currentTime := GetTickCount;
result := currentTime - startTime > _timeoutMs;
end;
end.
Thread has to cheacking if any pattern is match to buffer over timeout. But other thread(NOT MAIN) can change buffer by using BuffUpdate method. Did I use Synchronization method correctly?
Synchronize() is specifically designed to work with the main UI thread. You can use it for inter-thread syncing, however ALL threads involved would have to use it. In your example, only the thread(s) that write to _buff are using it, but the thread that reads from _buff is not. So that is a hole in your logic.
That being said, if the main UI thread does not need to touch your data, then Synchronize() is not the best solution to use. You can just wrap the data access with a synchronization object instead, like a TCriticalSection, TMutex, TEvent, TMREWSync, Sytem.TMonitor, etc. For example:
unit ExpectingThread;
interface
uses
System.Classes, System.SyncObjs;
type
TExpectingThread = class(TThread)
private
_timeoutMs: Integer;
_buff: string;
_buffLock: TCriticalSection;
_buffChanged: Boolean;
_patterns: TArray<string>;
_result: Integer;
function Timeouted(startTime: Cardinal): Boolean;
function ExpectedDetected: Boolean;
protected
procedure Execute; override;
public
constructor Create(patterns: TArray<string>; buff: string; timeoutMs: Integer);
destructor Destroy; override;
//this method is called from other NOT MAIN thread
procedure BuffUpdate(text: string);
end;
implementation
uses
Winapi.Windows, System.RegularExpressions;
{ TExpectingThread }
constructor TExpectingThread.Create(patterns: TArray<string>; buff: string; timeoutMs: Integer);
begin
inherited Create(False);
_buffLock := TCriticalSection.Create;
_patterns := patterns;
_timeoutMs := timeoutMs;
_buff := buff;
_buffChanged := True;
end;
destructor TExpectingThread.Destroy;
begin
_buffLock.Free;
inherited;
end;
//this method is called from other NOT MAIN thread
procedure TExpectingThread.BuffUpdate(text: string);
begin
_buffLock.Enter;
try
_buff := _buff + text;
_buffChanged := True;
finally
_buffLock.Leave;
end;
end;
procedure TExpectingThread.Execute;
var
startTime: DWORD;
begin
startTime := GetTickCount;
while not Terminated do
begin
if Timeouted(startTime) then
begin
Self.ReturnValue := 0; // timeouted
Exit;
end;
if ExpectedDetected then
begin
Self.ReturnValue := 1; // found
Exit;
end;
end;
end;
function TExpectingThread.ExpectedDetected: Boolean;
var
i: Integer;
buff: string;
begin
Result := False;
_buffLock.Enter;
try
If not _buffChanged then Exit;
buff := _buff;
UniqueStr(buff);
_buffChanged := False;
finally
_buffLock.Leave;
end;
for i := Low(_patterns) to High(_patterns) do
begin
if TRegEx.IsMatch(buff, _patterns[i]) then
begin
_result := i;
Exit(True);
end;
end;
end;
function TExpectingThread.Timeouted(startTime: Cardinal): Boolean;
var
currentTime: DWORD;
begin
currentTime := GetTickCount;
result := currentTime - startTime > _timeoutMs;
end;
end.

How to correctly declare StreamLn [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
I am trying to compile my program but I am getting this error:
Undeclared indentifier 'StreamLn'
i even tried to download PSock.dcu and put it into the library but it doesnt compile, it looks like its compactible with delphi 5,
unit ResourceInfo;
interface
uses
Classes, SysUtils, Windows;
type
TResourceInfo = class;
TDfmMode = ( dfmData, dfmResource, dfmASCII, dfmBinary);
TDfm = class
private
FOwner: TResourceInfo;
FName: string;
FData: TStream;
procedure SetName(const Value: string);
procedure SetOwner(const Value: TResourceInfo);
public
constructor Create(AOwner: TResourceInfo);
destructor Destroy; override;
function SaveToFile(FileName: TFileName; Mode: TDfmMode): Boolean;
property Data: TStream read FData;
property Name: string read FName write SetName;
property Owner: TResourceInfo read FOwner write FOwner;
end; {TDfm}
TResourceInfo = class(TComponent)
private
FActive: Boolean;
FDfms: TList;
FExeFileName: TFileName;
FModule: THandle;
FOnActivate: TNotifyEvent;
FOnDeactivate: TNotifyEvent;
procedure SetExeFileName(const Value: TFileName);
procedure SetActive(const Value: Boolean);
function GetDfms(Index: Cardinal): TDfm;
function GetDfmCount: Cardinal;
protected
procedure Clear;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function AddDfm(const Name: string; AData: TMemoryStream): Integer;
procedure DeleteDfm(const Name: string);
property DfmCount: Cardinal read GetDfmCount;
property Dfms[Index: Cardinal]: TDfm read GetDfms;
procedure EnumDfmNames;
property Module: THandle read FModule;
published
property Active: Boolean read FActive write SetActive;
property ExeFileName: TFileName read FExeFileName write SetExeFileName;
property OnActivate: TNotifyEvent read FOnActivate write FOnActivate;
property OnDeactivate: TNotifyEvent read FOnDeactivate write FOnDeactivate;
end; {TResourceInfo}
procedure Register;
implementation
uses
Winsock;
resourcestring
rsErrorLoadingExeFile = 'An error ocurred loading file %s, it may not be an executable module';
procedure Register;
begin
RegisterComponents('+HCU', [TResourceInfo]);
end; {Register}
{ TResourceInfo }
function TResourceInfo.AddDfm(const Name: string; AData: TMemoryStream): Integer;
var
FDfm: TDfm;
begin
FDfm := TDfm.Create(Self);
FDfm.Name := Name;
FDfm.Data.Size := AData.Size;
FDfm.Data.Seek(0, 0);
AData.Seek(0, 0);
FDfm.Data.CopyFrom(AData, AData.Size);
Result := FDfms.Add(FDfm);
end; {TResourceInfo.AddDfm}
constructor TResourceInfo.Create(AOwner: TComponent);
begin
inherited;
FActive := False;
FDfms := TList.Create;
FModule := 0;
end; {TResourceInfo.Create}
destructor TResourceInfo.Destroy;
begin
Clear;
FDfms.Free;
inherited;
end; {TResourceInfo.Destroy}
function CB_EnumDfmNameProc(hModule: THandle; lpszType, lpszName: PChar; lParam: Integer): Boolean; stdcall;
var
ms: TMemoryStream;
rs: TResourceStream;
Buffer: array of Byte;
begin
with TResourceInfo(lParam) do
begin
rs := TResourceStream.Create(TResourceInfo(lParam).Module, lpszname, lpszType);
try
ms := TMemoryStream.Create;
try
try
SetLength(Buffer, 4);
rs.Read(Buffer[0], SizeOf(Buffer));
if string(Buffer) = 'TPF0' then
begin
rs.Seek(0, 0);
ObjectBinaryToText(rs, ms);
ms.Seek(0, 0);
AddDfm(StrPas(lpszName), ms);
end;
except
raise;
end;
finally
ms.Free;
end;
finally
rs.free;
end;
end;
Result := True;
end; {CB_EnumDfmNameProc}
procedure TResourceInfo.EnumDfmNames;
begin
if FModule > 0 then
EnumResourceNames(FModule, RT_RCDATA, #CB_EnumDfmNameProc, Integer(Self));
end; {TResourceInfo.EnumDfmNames}
procedure TResourceInfo.DeleteDfm(const Name: string);
var
i: Cardinal;
begin
if FDfms.Count > 0 then
for i := Pred(FDfms.Count) downto 0 do
if UpperCase(TDfm(FDfms[i]).Name) = UpperCase(Name) then
begin
FDfms.Delete(i);
Break;
end;
end; {TResourceInfo.DeleteDfm}
procedure TResourceInfo.SetActive(const Value: Boolean);
begin
if FActive <> Value then
begin
if Value then
begin
if FModule > 0 then
FreeLibrary(FModule);
(* LOAD_LIBRARY_AS_DATAFILE
If this value is given, the function does a simple mapping of the file into the
address space. Nothing is done relative to executing or preparing to execute the
code in the mapped file. The function loads the module as if it were a data file.
You can use the module handle that the function returns in this case with the Win32
functions that operate on resources. Use this flag when you want to load a DLL in
order to extract messages or resources from it, and have no intention of executing
its code.If this value is not given, the function maps the file into the address
space in the manner that is normal for an executable module. The behavior of the
function is then identical to that of LoadLibrary in this regard. *)
FModule := LoadLibraryEx(PChar(FExeFileName), 0, LOAD_LIBRARY_AS_DATAFILE);
if not (FModule >= 32) then
raise Exception.CreateFmt(rsErrorLoadingExeFile, [FExeFileName]);
if Assigned(FOnActivate) then
FOnActivate(Self);
end
else
begin
Clear;
if FModule > 0 then
begin
FreeLibrary(FModule);
FModule := 0;
end;
if Assigned(FOnDeactivate) then
FOnDeactivate(Self);
end;
FActive := Value;
end;
end; {TResourceInfo.SetActive}
procedure TResourceInfo.SetExeFileName(const Value: TFileName);
begin
if FExeFileName <> Value then
FExeFileName := Value;
end; {TResourceInfo.SetExeFileName}
function TResourceInfo.GetDfms(Index: Cardinal): TDfm;
begin
Result := TDfm(FDfms[Index]);
end; {TResourceInfo.GetDfms}
function TResourceInfo.GetDfmCount: Cardinal;
begin
Result := FDfms.Count;
end; {TResourceInfo.GetDfmCount}
procedure TResourceInfo.Clear;
begin
if FDfms.Count > 0 then
while FDfms.Count > 0 do
FDfms.Delete(0);
end; {TResourceInfo.Clear}
{ TDfm }
constructor TDfm.Create(AOwner: TResourceInfo);
begin
inherited Create;
FData := TMemoryStream.Create;
FName := '';
SetOwner(AOwner);
end; {TDfm.Create}
destructor TDfm.Destroy;
begin
FData.Free;
inherited;
end; {TDfm.Destroy}
function TDfm.SaveToFile(FileName: TFileName; Mode: TDfmMode): Boolean;
function EndOfStream(Stream: TStream): Boolean;
begin
with Stream do
Result := Position = Size;
end; {EndOfStream}
var
fs: TFileStream;
ms: TMemoryStream;
s: string;
i, j: Byte;
begin
fs := TFileStream.Create(FileName, fmCreate or fmShareExclusive);
try
FData.Seek(0, 0);
case Mode of
dfmASCII:
begin
ms := TMemoryStream.Create;
try
s := FName + ' RCDATA' + #13#10 + '{';
StreamLN(fs, s);
ObjectTextToBinary(FData, ms);
ms.Seek(0, 0);
while not EndOfStream(ms) do
begin
s := '''';
for i := 0 to 15 do
begin
if ms.Read(j, SizeOf(j)) = 0 then
Break;
s := Concat(s, Format('%2.2x', [j]));
if (i = 15) or EndOfStream(ms) then
s := Concat(s, '''')
else
s := Concat(s, ' ');
end;
if EndOfStream(ms) then
s := Concat(s, #13#10 + '}');
StreamLN(fs, s);
end;
finally
ms.Free;
end;
end;
dfmBinary:
ObjectTextToBinary(FData, fs);
end;
finally
fs.Free;
end;
end; {TDfm.SaveToFile}
procedure TDfm.SetName(const Value: string);
begin
if FName <> Value then
FName := Value;
end; {TDfm.SetName}
procedure TDfm.SetOwner(const Value: TResourceInfo);
begin
FOwner := Value;
end; {TDfm.SetOwner}
end.
How can I declare it successfully?
Appears to me that WinSock unit does not have an StreamLn function (as PowerSock's PSock.pas unit uses Winsock as imported unit).
The StreamLn function in PSock.pas just adds an CRLF sequence to the string passed as parameter before calling the TStream.WriteBuffer method of the passed TStream parameter.
Here's the google cache snapshot from the Powersock's source code of PSock.pas
You need to either implement this function, or add a unit where this function is declared to your uses section.

async file I/O in Delphi

in this article delphi.net(prism) support async file io.
Delphi(Native/VCL) has Async File IO Class too?
Have you seen this code? http://pastebin.com/A2EERtyW
It is a good start for ansynchronous file I/O, but personally I would write a wrapper around the standard TStream class to maintain compatibility with VCL/RTL.
EDIT 2: This one looks good, too. http://www.torry.net/vcl/filedrv/other/dstreams.zip
I am posting it here just in case it disappears from Pastebin:
unit xfile;
{$I cubix.inc}
interface
uses
Windows,
Messages,
WinSock,
SysUtils,
Classes;
const
MAX_BUFFER = 1024 * 32;
type
TFileReadEvent = procedure(Sender: TObject; const Buffer; Count: Integer) of object;
TAsyncFile = class
private
FHandle: THandle;
FPosition: Cardinal;
FReadPending: Boolean;
FOverlapped: TOverlapped;
FBuffer: Pointer;
FBufferSize: Integer;
FOnRead: TFileReadEvent;
FEof: Boolean;
FSize: Integer;
function ProcessIo: Boolean;
procedure DoOnRead(Count: Integer);
function GetOpen: Boolean;
public
constructor Create(Filename: string; BufferSize: Integer = MAX_BUFFER);
destructor Destroy; override;
procedure BeginRead;
procedure Seek(Position: Integer);
procedure Close;
property OnRead: TFileReadEvent read FOnRead write FOnRead;
property Eof: Boolean read FEof;
property IsOpen: Boolean read GetOpen;
property Size: Integer read FSize;
end;
function ProcessFiles: Boolean;
implementation
var
Files: TList;
function ProcessFiles: Boolean;
var
i: Integer;
AsyncFile: TAsyncFile;
begin
Result := False;
for i := Files.Count - 1 downto 0 do
begin
AsyncFile := TAsyncFile(Files[i]);
Result := AsyncFile.ProcessIo or Result;
end;
end;
procedure Cleanup;
var
i: Integer;
AsyncFile: TAsyncFile;
begin
for i := Files.Count - 1 downto 0 do
begin
AsyncFile := TAsyncFile(Files[i]);
AsyncFile.Free;
end;
Files.Free;
end;
{ TAsyncFile }
constructor TAsyncFile.Create(Filename: string; BufferSize: Integer);
begin
Files.Add(Self);
FReadPending := False;
FBufferSize := BufferSize;
GetMem(FBuffer, FBufferSize);
FillMemory(#FOverlapped, SizeOf(FOverlapped), 0);
Cardinal(FHandle) := CreateFile(
PChar(Filename), // file to open
GENERIC_READ, // open for reading
0, // do not share
nil, // default security
OPEN_EXISTING, // open existing
FILE_ATTRIBUTE_NORMAL, //or // normal file
//FILE_FLAG_OVERLAPPED, // asynchronous I/O
0); // no attr. template
FSize := FileSeek(FHandle, 0, soFromEnd);
FileSeek(FHandle, 0, soFromBeginning);
FPosition := 0;
end;
destructor TAsyncFile.Destroy;
begin
Files.Remove(Self);
CloseHandle(FHandle);
FreeMem(FBuffer);
inherited;
end;
function TAsyncFile.ProcessIo: Boolean;
var
ReadCount: Cardinal;
begin
Result := False; Exit;
if not FReadPending then
begin
Exit;
end;
if GetOverlappedResult(FHandle, FOverlapped, ReadCount, False) then
begin
FReadPending := False;
DoOnRead(ReadCount);
end
else
begin
case GetLastError() of
ERROR_HANDLE_EOF:
begin
FReadPending := False;
FEof := True;
end;
ERROR_IO_PENDING:
begin
FReadPending := True;
end;
0:
begin
Result := True;
end;
end;
end;
end;
procedure TAsyncFile.BeginRead;
var
ReadResult: Boolean;
ReadCount: Cardinal;
begin
ReadCount := 0;
Seek(FPosition);
ReadResult := ReadFile(FHandle, FBuffer^, FBufferSize, ReadCount, nil);//#FOverlapped);
if ReadResult then
begin
FEof := False;
FReadPending := False;
FPosition := FPosition + ReadCount;
DoOnRead(ReadCount);
end
else
begin
case GetLastError() of
ERROR_HANDLE_EOF:
begin
FReadPending := False;
FEof := True;
end;
ERROR_IO_PENDING:
begin
FReadPending := True;
end;
end;
end;
end;
procedure TAsyncFile.DoOnRead(Count: Integer);
begin
if Assigned(FOnRead) then
begin
FOnRead(Self, FBuffer^, Count);
end;
end;
function TAsyncFile.GetOpen: Boolean;
begin
Result := Integer(FHandle) >= 0;
end;
procedure TAsyncFile.Close;
begin
FileClose(FHandle);
end;
procedure TAsyncFile.Seek(Position: Integer);
begin
FPosition := Position;
FileSeek(FHandle, Position, soFromBeginning);
end;
initialization
Files := Tlist.Create;
finalization
Cleanup;
end.
There is nothing built in to the RTL/VCL that offers asynchronous I/O for files. Incidentally the support in Delphi Prism is down to the .net framework rather than being language based.
You can either code directly against the Win32 API (that's not much fun) or hunt around for a Delphi wrapper to that API. Off the top of my head, I don't know any Delphi wrappers of asynchronous file I/O but they must exist.

Communicate With Command Prompt Through Delphi

I have tried to use delphi to send commands to the command prompt.
However, i am not able to do so as i used CreateProcess method to do it.
I have tried to change the StdOutPipeWrite, however, the CreateProcess seems to not allow commands after the initial command from CreateProcess to be passed through.
Is there any way to make use of the handle to continue to send and receive commands and messages to and fro the command prompt and delphi?
My fellow member Glenn9999 from tek-tips.com wrote a nice FAQ on this subject.
I don't know if he's on SO, but he deserves all the credit for this one.
I copied the code from that page here for future reference. He uses pipes to do the communication between console and delphi.
unit mcunit;
{ written by Glenn9999 # tek-tips.com. Posted here 6/21/2011 }
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
monitor = class(TThread) // pipe monitoring thread for console output
private
TextString: String;
procedure UpdateCaption;
protected
procedure Execute; override;
end;
TForm1 = class(TForm)
CommandText: TMemo;
CommandRun: TComboBox;
Button2: TButton;
SaveDialog1: TSaveDialog;
procedure FormDestroy(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
cmdcount: integer;
end;
var
Form1: TForm1;
InputPipeRead, InputPipeWrite: THandle;
OutputPipeRead, OutputPipeWrite: THandle;
ErrorPipeRead, ErrorPipeWrite: THandle;
ProcessInfo : TProcessInformation;
myThread: monitor;
implementation
{$R *.DFM}
procedure WritePipeOut(OutputPipe: THandle; InString: string);
// writes Instring to the pipe handle described by OutputPipe
var
byteswritten: DWord;
begin
// most console programs require CR/LF after their input.
InString := InString + #13#10;
WriteFile(OutputPipe, Instring[1], Length(Instring), byteswritten, nil);
end;
function ReadPipeInput(InputPipe: THandle; var BytesRem: Integer): String;
{
reads console output from InputPipe. Returns the input in function
result. Returns bytes of remaining information to BytesRem
}
var
TextBuffer: array[1..32767] of char;
TextString: String;
BytesRead: Integer;
PipeSize: Integer;
begin
Result := '';
PipeSize := Sizeof(TextBuffer);
// check if there is something to read in pipe
PeekNamedPipe(InputPipe, nil, PipeSize, #BytesRead, #PipeSize, #BytesRem);
if bytesread > 0 then
begin
ReadFile(InputPipe, TextBuffer, pipesize, bytesread, nil);
// a requirement for Windows OS system components
OemToChar(#TextBuffer, #TextBuffer);
TextString := String(TextBuffer);
SetLength(TextString, BytesRead);
Result := TextString;
end;
end;
procedure monitor.Execute;
{ monitor thread execution for console output. This must be threaded.
checks the error and output pipes for information every 40 ms, pulls the
data in and updates the memo on the form with the output }
var
BytesRem: DWord;
begin
while not Terminated do
begin
// read regular output stream and put on screen.
TextString := ReadPipeInput(OutputPipeRead, BytesRem);
if TextString <> '' then
Synchronize(UpdateCaption);
// now read error stream and put that on screen.
TextString := ReadPipeInput(ErrorPipeRead, BytesRem);
if TextString <> '' then
Synchronize(UpdateCaption);
sleep(40);
end;
end;
procedure monitor.UpdateCaption;
// synchronize procedure for monitor thread - updates memo on form.
begin
With Form1.CommandText.Lines do
Add(TextString);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
WritePipeOut(InputPipeWrite, 'EXIT'); // quit the CMD we started
MyThread.Terminate;
// close process handles
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
// close pipe handles
CloseHandle(InputPipeRead);
CloseHandle(InputPipeWrite);
CloseHandle(OutputPipeRead);
CloseHandle(OutputPipeWrite);
CloseHandle(ErrorPipeRead);
CloseHandle(ErrorPipeWrite);
end;
procedure TForm1.Button2Click(Sender: TObject);
{ takes the input from the command edit box and processes it }
var
UpText: String;
begin
UpText := UpperCase(CommandRun.Text); // done to eliminate case-sensitivity
if UpText = 'CLR' then // clear the memo
begin
CommandText.Clear;
WritePipeOut(InputPipeWrite, #13);
end
else
if UpText = 'SAVELOG' then // save the memo box to a file.
begin
if SaveDialog1.Execute then
begin
CommandText.Lines.SaveToFile(SaveDialog1.FileName);
CommandText.Lines.Add('Log file saved.');
end
else
CommandText.Lines.Add('Log file not saved.');
end
// expand this, it needs to catch any variation where the command-interpreter
// is called. Any different ideas?
else
if UpText = 'CMD' then
inc(cmdcount)
else
if UpText = 'COMMAND' then
inc(cmdcount)
// terminate app if user types exit, else let alone
else
if UpText = 'EXIT' then
begin
if cmdcount = 1 then
Application.Terminate
else
dec(cmdcount);
end
else
WritePipeOut(InputPipeWrite, CommandRun.Text);
CommandRun.Items.Add(CommandRun.Text);
CommandRun.Text := '';
CommandRun.SetFocus;
end;
procedure TForm1.FormCreate(Sender: TObject);
{ upon form creation, this calls the command-interpreter, sets up the three
pipes to catch input and output, and starts a thread to monitor and show
the output of the command-interpreter }
var
DosApp: String;
DosSize: Integer;
Security : TSecurityAttributes;
start : TStartUpInfo;
begin
CommandText.Clear;
// get COMSPEC variable, this is the path of the command-interpreter
SetLength(Dosapp, 255);
DosSize := GetEnvironmentVariable('COMSPEC', #DosApp[1], 255);
SetLength(Dosapp, DosSize);
// create pipes
With Security do
begin
nlength := SizeOf(TSecurityAttributes) ;
binherithandle := true;
lpsecuritydescriptor := nil;
end;
CreatePipe(InputPipeRead, InputPipeWrite, #Security, 0);
CreatePipe(OutputPipeRead, OutputPipeWrite, #Security, 0);
CreatePipe(ErrorPipeRead, ErrorPipeWrite, #Security, 0);
// start command-interpreter
FillChar(Start,Sizeof(Start),#0) ;
start.cb := SizeOf(start) ;
start.hStdInput := InputPipeRead;
start.hStdOutput := OutputPipeWrite;
start.hStdError := ErrorPipeWrite;
start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
start.wShowWindow := SW_HIDE;
if CreateProcess(nil, PChar(DosApp), #Security, #Security, true,
CREATE_NEW_CONSOLE or SYNCHRONIZE,
nil, nil, start, ProcessInfo) then
begin
MyThread := monitor.Create(false); // start monitor thread
MyThread.Priority := tpHigher;
end;
Button2.Enabled := true;
cmdcount := 1;
end;
end.
UPDATE (05/01/2020)
This answer only works on non unicode aware Delphi versions.
You can find a working version here if you have a modern Delphi
First declare on uses:
ShellAPI
Then use this:
ShellExecute(0, nil, 'cmd.exe', '/c **YOUR_COMMAND_HERE**', nil, HIDE_WINDOW);

Resources