Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "WinAPI";
Текущий архив: 2005.09.11;
Скачать: [xml.tar.bz2];

Вниз

Редирект консольного вывода   Найти похожие ветки 

 
бывший   (2005-07-18 22:22) [0]

Есть клиент-серверное приложение. сервер сделан в виде консоли. необходим редирект вывода информации из консоли в мое приложение в "реальном масштабе времени" ) понимаю что необходим отдельный поток в котором я и буду совершать действо.

Пробовал создавать пайп и переадресовывать вывод при запуске консоли (createprocess) на этот пайп, постепенно читая из него. во-первых - передается не все, во-вторых в определенный момент консоль виснит. Подобные решения видел на www.sources.ru, к примеру.

В общем, мне нужна именно постепенная передача всего текста, отображаемого в консоле в мое приложение по мере его появления.
Я так понял выхода два - переадресация вывода (к примеру через пайп) - с этим вариантом по непонятным мне причинам облом вышел, чтение из некоего буфера консоли. Мастера, помогите!

Если необходимо привести те коды, что пробовал использовать, приведу. Пока для экономии места оставляю вопрос в таком виде.


 
P.N.P. ©   (2005-07-18 22:33) [1]

Посмотри http://www.renderman.ru/forum/download.php/1,336,24/FoxUtils.txt
Оно?


 
бывший   (2005-07-18 22:55) [2]

спасибо. интересное решение. оно работает, но все равно получает из консоли далеко не весь текст. консоль все время пополняется информацией (идет лог соединений клиентов, обмена данными, ошибок и т.д.) - этим вариантом получаю только 40% лога инициализации сервера и наступает тишина (не из-за таймаута точно).

этож каким макаром надо в этой консоле писать, чтобы редирект вывода не помогал? )

Мастера, жду вашей помощи и совета.


 
Гаврила ©   (2005-07-18 23:43) [3]


> Если необходимо привести те коды, что пробовал использовать,
> приведу


Видимо, пора


 
бывший   (2005-07-19 08:30) [4]

function GetDosOutput(const CommandLine:string): string;
var
 SA: TSecurityAttributes;
 SI: TStartupInfo;
 PI: TProcessInformation;
 StdOutPipeRead, StdOutPipeWrite: THandle;
 WasOK: Boolean;
 Buffer: array[0..255] of Char;
 BytesRead: Cardinal;
 WorkDir, Line: String;
begin
 Application.ProcessMessages;
 with SA do
 begin
   nLength := SizeOf(SA);
   bInheritHandle := True;
   lpSecurityDescriptor := nil;
 end;
 // создаём пайп для перенаправления стандартного вывода
 CreatePipe(StdOutPipeRead,  // дескриптор чтения
            StdOutPipeWrite, // дескриптор записи
            @SA,              // аттрибуты безопасности
            0                // количество байт принятых для пайпа - 0 по умолчанию
            );
 try
   // Создаём дочерний процесс, используя StdOutPipeWrite в качестве стандартного вывода,
   // а так же проверяем, чтобы он не показывался на экране.
   with SI do
   begin
     FillChar(SI, SizeOf(SI), 0);
     cb := SizeOf(SI);
     dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
     wShowWindow := SW_SHOW; //SW_HIDE
     hStdInput := GetStdHandle(STD_INPUT_HANDLE); // стандартный ввод не перенаправляем
     hStdOutput := StdOutPipeWrite;
     hStdError := StdOutPipeWrite;
   end;

   // Запускаем компилятор из командной строки
   WorkDir := ExtractFilePath(CommandLine);
   WasOK := CreateProcess(nil, PChar(CommandLine), nil, nil, True, 0, nil, PChar(WorkDir), SI, PI);

   // Теперь, когда дескриптор получен, для безопасности закрываем запись.
   // Нам не нужно, чтобы произошло случайное чтение или запись.
   CloseHandle(StdOutPipeWrite);
   // если процесс может быть создан, то дескриптор, это его вывод
   if not WasOK then
     raise Exception.Create("Could not execute command line!")
   else
     try
       // получаем весь вывод до тех пор, пока DOS-приложение не будет завершено
       Line := "";
       repeat
         // читаем блок символов (могут содержать возвраты каретки и переводы строки)
         WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);

         // есть ли что-нибудь ещё для чтения?
         if BytesRead > 0 then
         begin
           // завершаем буфер PChar-ом
           Buffer[BytesRead] := #0;
           // добавляем буфер в общий вывод
           Line := Line + Buffer;
         end;
         uMain.MainForm.richLog.Lines.Add(Line);
       until not WasOK or (BytesRead = 0);
       // ждём, пока завершится консольное приложение
       WaitForSingleObject(PI.hProcess, INFINITE);
     finally
       // Закрываем все оставшиеся дескрипторы
       CloseHandle(PI.hThread);
       CloseHandle(PI.hProcess);
     end;
 finally
     result:=Line;
     CloseHandle(StdOutPipeRead);
 end;
end;


сделано на пайпах. как такового "реал-тайма" нету. мне главное было проверить весь ли текст с консоли будет редиректнут. не весь ( пробовал и много других вариантов, в том числе и предложенный товарищем P.N.P.


 
бывший   (2005-07-19 08:34) [5]

наверняка у многих мастеров есть свои секреты работы с чужой консолью. поделитесь, плиз )


 
Digitman ©   (2005-07-19 08:55) [6]

где-то я этот код уже видел ...

одна и та же ошибка фигурирует


> Buffer[BytesRead] := #0;


а если BytesRead будет равен 256 ?
подумай, что произойдет ..


> Application.ProcessMessages


а это, кстати, зачем ?


 
Digitman ©   (2005-07-19 09:03) [7]


> понимаю что необходим отдельный поток в котором я и буду
> совершать действо


действительно необходим, потому что ReadFile[Ex] в дан.случае не удастся использовать в неблокирующем режиме


 
бывший   (2005-07-19 10:19) [8]

хм...

@Digitman - спасибо за разъяснения. Код был взят в качестве примера с www.sources.ru вроде или с какого-то фака.

application.processmasseges в самом начале затесался случайно )

вы мне скажите одно, данный код с внесением соответствующих исправлений указанных Digitman"ом даст ли 100% редирект?


 
Digitman ©   (2005-07-19 10:39) [9]


> бывший   (19.07.05 10:19) [8]


> вы мне скажите одно


я тебе ничего не скажу)
я увидел грубую ошибку в этом коде и рекомендую тебе обратить на нее столь же серьезное внимание, исправить ее, опробовать результат.. а потом уже будет все остальное ..


 
isasa ©   (2005-07-19 10:52) [10]

да нет, похоже чисто

WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
...
Buffer[BytesRead] := #0;

больше 255 не прочтет


 
бывший   (2005-07-19 10:57) [11]

хм... ну и в итоге? ) вы мне скажите почему читает не полностью? причем вариант предложенный P.N.P в несколько другом исполнении дает тот же результат ( что за буфер ест me консоли и как обрабатывать его содержимое? какими макаром может случиться так, что информация выводимая на консоль не может быть редиректнута? видел в свое время plugin для Frigate (аналог ТС), где подобная фича была реализована, жаль он без исходных кодов...


 
Digitman ©   (2005-07-19 11:36) [12]


> isasa ©   (19.07.05 10:52) [10]


точно.
255 я прозевал.

но в ориг.коде, откуда это было содрано, было 256, потому и не обратил сразу внимание


> бывший   (18.07.05 22:22)  


http://delphi.about.com/cs/adptips2001/a/bltip0201_2.htm


 
бывший   (2005-07-19 16:05) [13]

в примере Digitman"a есть интересная особенность - как я раньше не допер до этого! Вначале ждем пока отработает целиком программа, потом читаем с пайпа, куда на протяжении работы консоли редиректился вывод. Отлично =) Но как теперь быть мне, когда редирект нужен постепенный. Ждать пока отработает сервер мне никак нельзя - я парсю его логи. Какие есть предложения?


 
Ботвин Дмитрий   (2005-07-19 16:47) [14]

Попробуй такой вариант:

procedure RunDosInMemo(CmdLine: string; AMemo: TMemo);
const
 ReadBuffer = 2400;
var
 Security: TSecurityAttributes;
 ReadPipe, WritePipe: THandle;
 start: TStartUpInfo;
 ProcessInfo: TProcessInformation;
 Buffer: Pchar;
 BytesRead: DWord;
 Apprunning: DWord;
begin
 Screen.Cursor := CrHourGlass;
 Form1.Button1.Enabled := False;
 with Security do
 begin
   nlength := SizeOf(TSecurityAttributes);
   binherithandle := true;
   lpsecuritydescriptor := nil;
 end;
 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.hStdInput := ReadPipe;
   start.dwFlags := STARTF_USESTDHANDLES +
     STARTF_USESHOWWINDOW;
   start.wShowWindow := SW_HIDE;

   if CreateProcess(nil,
     PChar(CmdLine),
     @Security,
     @Security,
     true,
     NORMAL_PRIORITY_CLASS,
     nil,
     nil,
     start,
     ProcessInfo) then
   begin
     repeat
       Apprunning := WaitForSingleObject
         (ProcessInfo.hProcess, 100);
       ReadFile(ReadPipe, Buffer[0],
         ReadBuffer, BytesRead, nil);
       Buffer[BytesRead] := #0;
       OemToAnsi(Buffer, Buffer);
       AMemo.Text := AMemo.text + string(Buffer);

       Application.ProcessMessages;
     until (Apprunning <> WAIT_TIMEOUT);
   end;
   FreeMem(Buffer);
   CloseHandle(ProcessInfo.hProcess);
   CloseHandle(ProcessInfo.hThread);
   CloseHandle(ReadPipe);
   CloseHandle(WritePipe);
 end;
 Screen.Cursor := CrDefault;
 Form1.Button1.Enabled := True;
end;

Пример:
procedure TForm1.Button1Click(Sender: TObject);
begin
 Memo1.Clear;
 RunDosInMemo("ping -t 192.168.28.200", Memo1);
end;


 
Ботвин Дмитрий   (2005-07-19 17:00) [15]

Если ничего не произойдёт, то увеличить таймаут в строке:
Apprunning := WaitForSingleObject(ProcessInfo.hProcess, 200);


 
бывший   (2005-07-19 17:17) [16]

спасибо. щас смотреть буду )


 
бывший   (2005-07-19 17:23) [17]

офигенно ) супер! просто супер! вопрос есть такой еще ) как лучше поступить с таймаутом, если есть вероятность "простоя" сервера пустым в течение часа-двух и более, но в тоже время необходимо продолжать наблюдение? ставить большой таймаут - бессмысленно, т.к. в случае падения серва будем в холостую работать. что можете посоветовать?

@Ботвин Дмитрий: спасибо огромное! вы реально меня выручили! щас буду тестировать непосредственно на сервере. но ваш пример работает идеально )


 
бывший   (2005-07-20 09:07) [18]

в общем на боевом примере - провал.

1. виснит моя программа, не откликается даже после истечения таймаута и умервщленном сервере.

2. судя по task manager"у виснит и сам сервер. обычно он сразу после инициализации начинает жрать порядка 500-600 mb, а тут всего пару метров ест.

в отдельном потоке пока не пробовал.
в чем может быть беда? братцы, помогите!


 
Digitman ©   (2005-07-20 09:15) [19]


> в случае падения серва будем в холостую работать


в этом случае поток, вызвавший ReadFile(), "зависнет" в бесконечном ожидании, ибо пайп по прежнему существует, а процесса уже нет

необходимо принять какие-либо меры по слежению за процессом, и как только будет обнаружен факт его терминирования (неважно почему он был терминирован - завершен по инициативе пользователя или снят системой с выполнения по иным причинам) тут же закрыть хэндл пайпа ... как только пайп будет закрыт, ф-ция ReadFile() немедленно вернет управление с кодом ошибки 6 (неверный дескриптор)


 
Digitman ©   (2005-07-20 09:17) [20]


> в отдельном потоке пока не пробовал


в самую пору попробовать.

придется создать два доп.потока - один будет работать с пайпом, другой будет следить за целевым процессом


 
бывший   (2005-07-20 09:54) [21]

оба ) thnx 2Digitman ) утвердил мою уверенность ) будем кроить дальше. не совсем я понял по поводу второго потока: "следить за целевым процессом" - это как раз и имеется в виду то о чем вы говорили ранее?

я с потоками не на ты ) с ходу слепил нечто подобное:

unit uConsole;

interface

uses
 Controls, Windows, SysUtils, Forms, uMain, Classes, StdCtrls;

type
 TDosCapture = class(TThread)
 private
  CmdRun: String;
  AddLog: String;
  procedure DoChangeLog;
 protected
  procedure Execute; override;
  procedure ChangeLog(Text: String);
  procedure RunDosInMemo(CmdLine: string);
 public
  constructor Create(CmdLine: String);
 end;

implementation

procedure TDosCapture.RunDosInMemo(CmdLine: string);
const
ReadBuffer = 2400;
var
Security: TSecurityAttributes;
ReadPipe, WritePipe: THandle;
start: TStartUpInfo;
ProcessInfo: TProcessInformation;
Buffer: Pchar;
BytesRead: DWord;
Apprunning: DWord;
begin
with Security do
begin
 nlength := SizeOf(TSecurityAttributes);
 binherithandle := true;
 lpsecuritydescriptor := nil;
end;
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.hStdInput := ReadPipe;
 start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
 start.wShowWindow := SW_HIDE;

 if CreateProcess(nil,
    PChar(CmdLine),
    @Security,
    @Security,
    true,
    NORMAL_PRIORITY_CLASS,
    nil,
    nil,
    start,
    ProcessInfo) then
  begin
    repeat
      Apprunning := WaitForSingleObject
        (ProcessInfo.hProcess, 100);
      ReadFile(ReadPipe, Buffer[0],
        ReadBuffer, BytesRead, nil);
      Buffer[BytesRead] := #0;
      OemToAnsi(Buffer, Buffer);
      ChangeLog(String(Buffer));
    until (Apprunning <> WAIT_TIMEOUT);
  end;
  FreeMem(Buffer);
  CloseHandle(ProcessInfo.hProcess);
  CloseHandle(ProcessInfo.hThread);
  CloseHandle(ReadPipe);
  CloseHandle(WritePipe);
end;
end;

{ TDosCapture }

procedure TDosCapture.ChangeLog(Text: String);
begin
AddLog:=Text;
Synchronize(DoChangeLog);
end;

constructor TDosCapture.Create(CmdLine: String);
begin
CmdRun:=CmdLine;
FreeOnTerminate:=True;
inherited Create(False);
end;

procedure TDosCapture.DoChangeLog;
begin
with uMain.MainForm.richLog do
 text:=text+AddLog;
end;

procedure TDosCapture.Execute;
begin
RunDosInMemo(CmdRun);
end;

end.


Использую соответственно:
DCapture:=TDosCapture.Create("server.exe");

Что происходит - пока жива моя прога - загрузка сервера не трогается с места ) понимаю что виноват я, т.к. что-то упустил в RunDosInMemo применимо к потоку. Натолкните меня на мысль, господа. Плиз.


 
Digitman ©   (2005-07-20 10:06) [22]


> совсем я понял по поводу второго потока: "следить за целевым
> процессом" - это как раз и имеется в виду то о чем вы говорили
> ранее?


да, в [19]


> пока жива моя прога - загрузка сервера не трогается с места


не понял ..

что есть "загрузка с сервера" ?

речь идет о пайпе, а не о "серверах" и "загрузках" ...


> что-то упустил в RunDosInMemo применимо к потоку


да нет, все вроде бы выглядит вполне благопристойно ..

а что по этому поводу говорит отладчик ?


 
Ботвин Дмитрий   (2005-07-20 10:29) [23]

Для начала проверь свою прогу с потоками на примере того же
пинга, тока без параметра -t. Если всё работает и второй поток
отслеживает завершение создаваемого процесса, тогда переходи
на свой сервер. А без уверенности, что код рабочий к серверу
вообще прикасаться не надо....


 
alpet ©   (2005-07-20 10:30) [24]

А если указать start.wShowWindow := SW_SHOW консоль появляется?


 
бывший   (2005-07-20 10:41) [25]

@aplet. во-первых консоль и так (SW_HIDE) иногжа почему-то появлялась ))) но не суть. с SW_SHOW всегда видна точно ) но она пустая, понятно почему.

@Ботвин Дмитрий: ща попробую

@Digitman [22]: я про загрузку сервера говорю по той причине, что когда слепил thread-пример сервер перестал загружаться вообще. Редиректа тоже нету. Т.е.: 1. моя программа работает нормально, основной поток жив ) 2. сервер запускается, но не начинает инициализацию (по какой-то неведомой причине), пока жива моя программа. Как только я вырубаю мое ПО - работа сервера возобновляется. Складывается ощущение, что созданный поток держит и противится загрузке сервера. Бред... В коде ничего лишнего.

Вот такие вот пироги с капустой и луком...


 
Ботвин Дмитрий   (2005-07-20 10:56) [26]

Ну чё, с пингом работает?


 
бывший   (2005-07-20 10:57) [27]

господа, будите смеяться ) используя thread с ping -t все работает нормально. вопрос дня: каким образом редирект консоли может влиять на работоспособность сервера? сервер запущенный без моего ПО работает нормально. как можно отладить и разобраться в чем траблы?

на данный момент использую только 1 поток (см. пример [21]), как я уже говорил работает с пингом, не работает с сервером - держит сервер, не дает ему грузиться, пока не завершу поток/приложение.

жду ваших советов, Мастера.


 
Ботвин Дмитрий   (2005-07-20 10:59) [28]

Попробуй задать своему потоку приоритет Idle...


 
Digitman ©   (2005-07-20 11:01) [29]


> бывший   (20.07.05 10:41) [25]


отладчик-то что говорит ?!

ставь брейкпойнт на первую же строчку в теле Execute() и пройди пошагово весь код .. убедись что в ходе работы Execute() исключений не возникает


 
alpet ©   (2005-07-20 11:02) [30]

Ботвин Дмитрий   (20.07.05 10:59) [28]
WaitForSingleObject вроде выполняет роль своеобразного Sleep, сомнительно что здесь дело в приоритетах.

бывший   (20.07.05 10:57) [27]
Можно узнать на чем и с какой библиотекой написан злополучный сервер?


 
Ботвин Дмитрий   (2005-07-20 11:07) [31]

> бывший

А ты не пробывал изменить таймаут у WaitForSingleObject? Это я в примере поставил 100. Реально с таким значением может и не работать...


 
alpet ©   (2005-07-20 11:09) [32]

Попробуй также для начала обойтись без перехвата пайпов. Я тут посмотрел одну рабочую программу ориентированную на Dos приложение - там   start.hStdInput используется для передачи в буфер консоли данных (эмуляции клавиатурного ввода), а в твоем случае производится попытка читать из этого дескриптора. Если судить по логике моей программы должно быть так:

ReadFile(WritePipe, Buffer[0],
       ReadBuffer, BytesRead, nil);

...или я чего-то не понял.


 
Ботвин Дмитрий   (2005-07-20 11:10) [33]

> alpet

Дело, конечно, может быть и не в приоритете, просто для подобных
утилит рекомендуется именно простаивающий приоритет, что-бы не
нагружать излишне проц. Хотя это дело каждого...


 
бывший   (2005-07-20 11:10) [34]

alpet ©   (20.07.05 11:02) [30]
сервер... э... а хто его знает ) написан на C++ но с какой библиотекой сходу не скажу - надо копаться. исходников нету ) самое главное, что до потока - редирект был, хотя и 20% текста.

Digitman ©   (20.07.05 11:01) [29]
исключений точно не возникает. щас еще перепроверю всяко разно.

Ботвин Дмитрий   (20.07.05 10:59) [28]
можно попытаться. как задать приоритет потоку если он создан не через CreateThread? я просто в потоках не особо бум-бум. я так понимаю нужен идентификатор потока для этого?


 
бывший   (2005-07-20 11:13) [35]

alpet ©   (20.07.05 11:09) [32]
простите, я вас не до конца понял. как без пайпов то? куда редиректить? или вы имеете в виду что достаточно read-пайпа?

еще раз акцентирую: самое главное, что до потока - редирект был, хотя и 20% текста.

предлагаю вначале разобраться почему поток мешает корректной работоспособности сервера )

з.ы. спасибо вам всем, я рад что вы со мной в трудную минуту =)


 
alpet ©   (2005-07-20 11:14) [36]

бывший   (20.07.05 11:10) [34]
1. SetThreadPriority - метод класса TThread + F1.
2. Посмотрел сейчас в MSDN - походу в моей программе правильно юзается hStdInput для записи (см. пост [32]). Непонятно тогда почему у тебя пинг с таким кодом работал...


 
Ботвин Дмитрий   (2005-07-20 11:15) [37]

Установка приоритета:

constructor TDosCapture.Create(CmdLine: String);
begin
 CmdRun:=CmdLine;
 FreeOnTerminate:=True;
 Priority := tpIdle;
 inherited Create(False);
end;


 
alpet ©   (2005-07-20 11:15) [38]

Блин, точнее метод называется SetPriority. Отвык я от TThread :)


 
alpet ©   (2005-07-20 11:17) [39]

бывший   (20.07.05 11:13) [35]
В смысле не без пайпов, замени код инициации структуры:

start.hStdOutput := WritePipe;
start.hStdInput := ReadPipe;

на

start.hStdOutput := ReadPipe; // output - source
start.hStdInput := WritePipe; // keyboard simulation handle


 
бывший   (2005-07-20 11:22) [40]

ага, ща изменю приоритет и попробую поменять пайпы местами...



Страницы: 1 2 вся ветка

Форум: "WinAPI";
Текущий архив: 2005.09.11;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.58 MB
Время: 0.012 c
8-1115105858
Dennis S
2005-05-03 11:37
2005.09.11
помогите оптимизировать код


4-1121925450
Kair+
2005-07-21 09:57
2005.09.11


3-1122796471
SMP
2005-07-31 11:54
2005.09.11
Связи в SQL


8-1114983781
Apollon
2005-05-02 01:43
2005.09.11
OpenGL. Свет и текстуры


2-1123572231
Андрей235
2005-08-09 11:23
2005.09.11
Как сделать иконку в трее?





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский