I would like to read console outputs from a console with my own unit:
unit uConsoleOutput;
interface
uses Classes,
StdCtrls,
SysUtils,
Messages,
Windows;
type
ConsoleThread = class(TThread)
private
OutputString : String;
procedure SetOutput;
protected
procedure Execute; override;
public
App : WideString;
Memo : TMemo;
Directory : WideString;
end;
type
PConsoleData = ^ConsoleData;
ConsoleData = record
OutputMemo : TMemo;
OutputApp : WideString;
OutputDirectory : WideString;
OutputThreadHandle : ConsoleThread;
end;
function StartConsoleOutput (App : WideString; Directory : WideString; Memo : TMemo) : PConsoleData;
procedure StopConsoleOutput (Data : PConsoleData);
implementation
procedure ConsoleThread.SetOutput;
begin
Memo.Lines.BeginUpdate;
Memo.Text := Memo.Text + OutputString;
Memo.Lines.EndUpdate;
end;
procedure ConsoleThread.Execute;
const
ReadBuffer = 20;
var
Security : TSecurityAttributes;
ReadPipe,
WritePipe : THandle;
start : TStartUpInfo;
ProcessInfo : TProcessInformation;
Buffer : Pchar;
BytesRead : DWord;
Apprunning : DWord;
begin
Security.nlength := SizeOf(TSecurityAttributes) ;
Security.lpsecuritydescriptor := nil;
Security.binherithandle := true;
if Createpipe (ReadPipe, WritePipe, @Security, 0) then begin
Buffer := AllocMem(ReadBuffer + 1) ;
FillChar(Start,Sizeof(Start),#0) ;
start.cb := SizeOf(start) ;
start.hStdOutput := WritePipe;
start.hStdError := WritePipe;
start.hStdInput := ReadPipe;
start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
start.wShowWindow := SW_HIDE;
if CreateProcessW(nil,pwidechar(APP),@Security,@Security,true,NORMAL_PRIORITY_CLASS,nil,pwidechar(Directory),start,ProcessInfo) then begin
while not(terminated) do begin
BytesRead := 0;
if Terminated then break;
ReadFile(ReadPipe,Buffer[0], ReadBuffer,BytesRead,nil);
if Terminated then break;
Buffer[BytesRead]:= #0;
if Terminated then break;
OemToAnsi(Buffer,Buffer);
if Terminated then break;
OutputString := Buffer;
if Terminated then break;
Synchronize(SetOutput);
end;
FreeMem(Buffer) ;
CloseHandle(ProcessInfo.hProcess) ;
CloseHandle(ProcessInfo.hThread) ;
CloseHandle(ReadPipe) ;
CloseHandle(WritePipe) ;
end;
end;
end;
function StartConsoleOutput (App : WideString; Directory : WideString; Memo : TMemo) : PConsoleData;
begin
result := VirtualAlloc(NIL, SizeOf(ConsoleData), MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
Memo.DoubleBuffered := TRUE;
with PConsoleData(result)^ do begin
OutputMemo := Memo;
OutputApp := App;
OutputDirectory := Directory;
OutputThreadHandle := ConsoleThread.Create(TRUE);
OutputThreadHandle.FreeOnTerminate := TRUE;
OutputThreadHandle.Memo := Memo;
OutputThreadHandle.App := App;
OutputThreadHandle.Directory := Directory;
OutputThreadHandle.Resume;
end;
end;
procedure StopConsoleOutput (Data : PConsoleData);
begin
with PConsoleData(Data)^ do begin
OutputThreadHandle.Terminate;
while not(OutputThreadHandle.Terminated) do sleep (100);
end;
VirtualFree (Data,0, MEM_RELEASE);
end;
end.
I use this console application to test it on (worldserver.exe): https://dl.dropboxusercontent.com/u/349314/Server.rar (compiled)
The source for the project is here: https://github.com/TrinityCore/TrinityCore
A tutorial in how to compile the project is here: http://archive.trinitycore.info/How-to:Win
To start the worldserver.exe I simply use my own unit like this:
StartConsoleOutput ('C:\worldserver.exe', 'C:\', Memo1);
The application starts fine just with a few problems/bugs, which I don't understand:
What do I do wrong?
The basic problem is that you have created a single pipe, and made the external process use both ends of the same pipe. The pipe is used to connect two distinct processes. So each process should only know about one end of it.
So imagine you want to app1 to send information to app2. Create a pipe with a write end and a read end. A typical configuration looks like this.
app1, stdout --> pipe write end --> pipe read end --> app2, stdin
This is what you would get if you wrote
app1 | app2
at the command interpretor.
But you have attached the read end of your pipe to app1, stdin. So in your case the diagram is like this
app1, stdout --> pipe write end ---
| |
| |
app1, stdin <-- pipe read end <--
That's a clear mistake in your program. When app1 writes to its stdout, whatever it writes appears in its own stdin! Absolutely not what you intended.
The extra twist in the tale is that your app is also trying to read the read end of the pipe. So both your app and the external process are reading that. Now, that's a race. Who's to say which one gets the content?
Perhaps all you need is to remove the line that assigns hStdInput
and leave it as 0 instead.
One final point. Writing Text := Text + ...
is very inefficient. The entire contents of the memo will be both read, and written.