Версия для печати
Перенаправление 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 ждет до бесконечности, буду рад если кто предложит более "прямой" вариант.