Версия для печати


Перенаправление StdOut в TStream
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1124

Денис
дата публикации 21-03-2005 06:58

Перенаправление StdOut в TStream

Статья является продолжением темы о перехвате StdOut'а дочернего приложения, в частности дополнением к статье Черевко Сергея: "Перенаправление вывода консольной программы"

В исходной статье имеется недостаток - при добавлении текста в TString весь считанный блок добавляется как одна строка, что не очень удобно. Ну и к тому же мне не понравилось что процесс чтения данных из "трубы" в такой развитой среде как Delphi требует столько телодвижений, и вызовов функций Win32. Это сподвигло меня искать более другой способ чтения данных из пайпа. В конце концов я наткнулся на метод TStrings.LoadFromStream(Stream: TStream), и я подумал вот было бы неплохо одним махом залить все содержимое "трубы" сразу в Memo (ну или в любой другой TString-подобный обьект), копая дальше наткнулся на класс THandleStream, являющийся родителем класса TFileStream, и потомком TSream. Этот класс предоставляет доступ ко многим ресурсам (files, sockets, named pipes, mailslots, or other communications resources), которые предусматривают хендлы (handle).

Но не все так просто, как казалось бы. Дело в том, что дочерний процесс помещает в "трубу" не все данные, а ровно столько, сколько под "трубу" выделено, и смиренно ждет пока труба освободится. Поэтому LoadFromStream помещает в TStrings только часть данных, а добавление методом LoadFromStream не подразумевается.

И мне все-таки пришлось использовать буфер, а также для слежения "не пуст ли pipe" функцию Win32 PeekNamedPipe().

И еще есть минус — при чтении блоками, поледняя строка обрывается на середине, и приходится ее "склеивать" вручную с первой строкой следующего блока. Но все равно код получился более компактный, и прозрачный, т.к. основная часть операций с перебросом данных возложена на компоненты Borland'а (не зря же они их так тщательно разрабатывали ;)

Procedure RunAny(CommandLine: string; Str: TStrings);
var
  tRead, cWrite, dwAvail: cardinal;
  SA: TSecurityAttributes;
  PI: TProcessInformation;
  SI: TStartupInfo;
  sBuff: THandleStream;
  StringBuf: TStringList;
begin
  //Инициализация
  SA.nLength:=SizeOf(SECURITY_ATTRIBUTES);
  SA.bInheritHandle:=True;
  SA.lpSecurityDescriptor:=nil;
  if not CreatePipe(tRead, cWrite, @SA, 0) then Exit;
  ZeroMemory(@SI, SizeOf(TStartupInfo));
  SI.cb:=SizeOf(TStartupInfo);
  SI.dwFlags:=STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
  SI.wShowWindow:=SW_HIDE;
  SI.hStdOutput:=cWrite;
  //Стартуем процесс...
  if CreateProcess(nil, PChar(CommandLine), nil, nil, True, 0, nil, nil, SI, PI)
  then begin
      Str.Clear();
      sBuff := THandleStream.Create(tRead);
      StringBuf := TStringList.Create();
      repeat
        StringBuf.Clear();
        StringBuf.LoadFromStream(sBuff); //Помещаем блок в буфер
        if StringBuf.Count > 0 then
        begin
          //Склеиваем разорванную строку
          StringBuf.Strings[0] := Str.Strings[Str.Count-1]+StringBuf.Strings[0];
          Str.Delete(Str.Count-1);
        end;
        //Добавляем блок из буфера
        Str.AddStrings(StringBuf);
        //Ждем N-дцать минут
        WaitForSingleObject(PI.hProcess, 10);
        //не пуст ли pipe ?
        PeekNamedPipe(tRead, nil, 0, nil, @dwAvail, nil);
      until (dwAvail = 0);
      CloseHandle(PI.hProcess);
      CloseHandle(PI.hThread);
  end;       // if CreateProcess
  CloseHandle(tRead);
  CloseHandle(cWrite);
end;

PS: В WaitForSingleObject магическое число 10, избавиться от него не удалось с INFINITE ждет до бесконечности, буду рад если кто предложит более "прямой" вариант.