delphiconsolepipeconsole-applicationtrinitycore

Delphi Console pipes switched?


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:

  1. It seems like the time to output the application (worldserver.exe) takes longer as if I would open it by myself (theres like 3 second delay).
  2. The pipes seems to be switched or something cause on my delphi app it outputs the wrong way. (see Screenshot 2)
  3. I have the server (worldserver.exe) complete running with mysql (which works fine) and let it output in my delphi app. It seems like some parts are missing and then all of a sudden it outputs that something is writing INTO the console.

Screenshot1 Screenshot2

What do I do wrong?


Solution

  • 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.