Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Подземелье Магов
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  19:37[Войти] | [Зарегистрироваться]

StdIn, StdOut и StdErr. Перенаправление, чтение и запись.

Сергей Горбань
дата публикации 23-12-2002 18:26

StdIn, StdOut и StdErr. Перенаправление, чтение и запись.

Вообще - я программист молодой, стаж - всего 2 года. И я никак не ожидал, что в век GDI мне придется возится с консолью... Ан нет, пришлось.

Начал писать "движок" для собственного сайта. А именно - "Apache 1.x shared module" (dll - линкуется к Апачу и обрабатывает определенные адреса).

Написал. Всего три сотни строк. НО умеет кучу всяких полезностей, типа вставлять на страницы данные из файлов (файл в файл), строки и, главное, данные из БД. Все это прекрасно. НО не умеет вставлять результаты работы других файлов (типа как CGI). Ну, думаю, надо сделать.
Ага, а как? Вот тут то все и началось...

Итак,
ЗАДАЧА:
запустить процесс (некий файл), передать ему команды и получить от него результаты работы. Вставить полученные результаты на страницу сайта.
Причем в целях совместимости механизмы передачи данных ДОЛЖНЫ быть стандартными - StdIn, StdOut, StdErr.
Поискал на КД. Нашел вот такую штуку: Как переназначить StdOut в файл для консольной программы запускаемой по CreateProcess

Хорошая статья, но мне-то НЕ в ФАЙЛ, а в ПРОГРАММУ надо!
Автор (Спасибо ему!!!) предусмотрительно указал ссылки на полезные раздел справки - "Creating a Child Process with Redirected Input and Output".
Лезем туда. Ууууух... Круто. В общем, каких-то два дня ковыряния и вуаля!

Работает!

Получился небольшой такой класс... За кривизну некоторых мест, типа отсутствия проверок - НЕ бить! (по крайней мере ногами :-)). Кому надо - тот сам вставит. (Вот так и рождается "кривой" код. Типа сейчас лень, потом добавлю... Ага... Через час уже забудешь!!!)

В общем - перехожу таки к технике дела.

Для передачи данных используются "безымянные" (Anonymus) "каналы" (Pipes). Чтобы заставить программу писать в (читать из) канал (а) - просто подменяем соответствующие Std(In, Out, Err). Программа и знать не будет, что ее данные уходят в "трубу" а не на реальную консоль.

При создании каналов есть одна ВАЖНАЯ особенность. Создаем-то мы их в своем процессе (Parent) а использовать будем и в дочернем. (Учтите! дочерний процесс НЕ будет знать, что использует КАНАЛ! НО будет его использовать...). Так, вот, чтобы дочерний процесс мог нормально работать - хэндлы канала должны быть НАСЛЕДУЕМЫМИ.
Чтобы это обеспечить - надо правильно заполнить структуру SECURITY_ATTRIBUTES используемую при вызове CreatePipe:

    New(FsaAttr);
    FsaAttr.nLength:=SizeOf(SECURITY_ATTRIBUTES);
    FsaAttr.bInheritHandle:=True;
    FsaAttr.lpSecurityDescriptor:=Nil;

Заполнили? Молодцы! Теперь создаем каналы (я делаю только два, StdErr мне не нужен):

If not CreatePipe(FChildStdoutRd, FChildStdoutWr, FsaAttr, 0) Then 
//Создаем "читальный" Pipe
      raise ECreatePipeErr.CreateRes(@sCreatePipeMsg)
    Else
      If not CreatePipe(FChildStdinRd, FChildStdinWr, FsaAttr, 0) Then 
//Создаем "писальный" Pipe
        raise ECreatePipeErr.CreateRes(@sCreatePipeMsg) 

Создали? Если нет - то дальше ловить нечего, поэтому генерим Exception'ы...
Есть еще одна тонкость. У нас Все созданные хэндлы наследуемые! А дочернему процессу понадобятся только два... Поэтому:

//Делаем НЕ наследуемые дубликаты
//Это нужно, чтобы не тащить лишние хэндлы в дочерний процесс...
          If not DuplicateHandle(GetCurrentProcess(), FChildStdoutRd,
GetCurrentProcess(), @Tmp1, 0, False, DUPLICATE_SAME_ACCESS) Then
            raise EDuplicateHandleErr.CreateRes(@sDuplicateHandleMsg)
          Else
            If not DuplicateHandle(GetCurrentProcess(), FChildStdinWr,
GetCurrentProcess(), @Tmp2, 0, False, DUPLICATE_SAME_ACCESS) Then
              raise EDuplicateHandleErr.CreateRes(@sDuplicateHandleMsg)

Дубликаты у нас в Tmp1 и Tmp2, теперь:

CloseHandle(FChildStdoutRd);//Закроем наследуемый вариант "Читального" хэндла
CloseHandle(FChildStdinWr); //Закроем наследуемый вариант "Писального" хэндла
FChildStdoutRd:=Tmp1;       //И воткнем их места НЕ наследуемые дубликаты
FChildStdinWr:=Tmp2;        //И воткнем их места НЕ наследуемые дубликаты

Ура! Теперь можем создавать дочерний процесс!

    If not CreateChildProcess(ExeName, CommadLine, FChildStdinRd,
FChildStdoutWr) Then  //Наконец-то! Создаем дочерний процесс!
      raise ECreateChildProcessErr.CreateRes(@sCreateChildProcessMsg)

Причем CreateChildProcess - это не API - это моя функция! Вот она:

//*************************************************************************
function TChildProc.CreateChildProcess(ExeName, CommadLine: String; StdIn,
  StdOut: THandle): Boolean;
Var
  piProcInfo: TProcessInformation;
  siStartInfo: TStartupInfo;
begin
// Set up members of STARTUPINFO structure.
  ZeroMemory(@siStartInfo, SizeOf(TStartupInfo));
  siStartInfo.cb:=SizeOf(TStartupInfo);
  siStartInfo.hStdInput:=StdIn;
  siStartInfo.hStdOutput:=StdOut;
  siStartInfo.dwFlags:=STARTF_USESTDHANDLES;
// Create the child process.
   Result:=CreateProcess(Nil,
      PChar(ExeName+' '+CommadLine),       // command line
      Nil,          // process security attributes
      Nil,          // primary thread security attributes
      TRUE,          // handles are inherited
      0,             // creation flags
      Nil,          // use parent's environment
      Nil,          // use parent's current directory
      siStartInfo,  // STARTUPINFO pointer
      piProcInfo);  // receives PROCESS_INFORMATION
end;    
//*************************************************************************

Здесь важное значение имеют вот эти строчки:
      siStartInfo.hStdInput:=StdIn;
      siStartInfo.hStdOutput:=StdOut;
      siStartInfo.dwFlags:=STARTF_USESTDHANDLES;
Первые две - понятно. А третья - читайте Хелп! Там все написано...

Самые умные (то есть те, кто ухитрился дочитать до этого места :-))) спросят:

- Ну, создали мы процесс и что дальше?

А дальше - мы можем с ентим процессом общаться! Например вот так:

//*************************************************************************
function TChildProc.WriteToChild(Data: String; Timeout: Integer=1000):
Boolean;
Var
        dwWritten, BufSize: DWORD;
  chBuf: PChar;
begin
//Обратите внимание на Chr($0D)+Chr($0A)!!! Без них - будет работать с ошибками
//На досуге - подумайте почему...
//Для тех, кому думать лень - подскажу - это пара символов конца строки.
//(вообще-то можно обойтись одним, но так надежнее, программы-то бывают разные)
  chBuf:=PChar(Data+Chr($0D)+Chr($0A));
  BufSize:=Length(chBuf);
  Result:=WriteFile(FChildStdinWr, chBuf^, BufSize, dwWritten, Nil);
  Result:=Result and (BufSize = dwWritten);
end;    
//*************************************************************************

Это мы посылаем данные на StdIn процесса.

Читать - несколько сложнее. Нам же не надо вешать всю нашу программу только потому, что процесс не желает нам ничего сообщать??? А ReadFile - функция синхронная и висит - пока не прочитает! Если заранее известно, чего и сколько ДОЛЖЕН выдать процесс, то еще ничего... А если нет?

А если нет - делаем хитрый финт ушами :-) Есть у Мелко-Мягких такая ф-ия PeekNamedPipe. Не покупайтесь, на то, что она "Named" - фигня! Она прекрасно работает а анонимными пайпами! (кто не верит - можете почитать хелп)

Поэтому делаем так:

//*************************************************************************
function TChildProc.ReadStrFromChild(Timeout: Integer): String;
Var
  i: Integer;
  dwRead, BufSize, DesBufSize: DWORD;
  chBuf: PChar;
  Res: Boolean;
begin
  Try
    BufSize:=0;
    New(chBuf);
    Repeat
      For i:=0 to 9 do
        begin
          Res:=PeekNamedPipe(FChildStdoutRd, nil, 0, nil, @DesBufSize, nil);
          Res:=Res and (DesBufSize > 0);
          If Res Then
            Break;
          Sleep(Round(Timeout/10));
        end;
      If Res Then
        begin
          If DesBufSize > BufSize Then
            begin
              FreeMem(chBuf);
              GetMem(chBuf, DesBufSize);
              BufSize:=DesBufSize;
            end;
          Res:=ReadFile(FChildStdoutRd, chBuf^, BufSize, dwRead, Nil);
          Result:=Result+LeftStr(chBuf, dwRead);
        end;
    Until not Res;
  Except
    Result:='Read Err';
  End;
end;
//*************************************************************************

Ну, вот, как я и говорил - работает. Даже слишком хорошо. Как я и говорил - эта вся бодяга для Web сервера. Ну, беру я в качестве файла - format.exe.... Ндаааа....

Если честно - с format'ом я не прверял - а вот help c парметрами и "net use" прошли на ура! Так что пришлось резко думать, как ограничить список разрешенных для запуска программ....

В общем, кому лень разбираться - вот вам исходники модуля с готовым классом. А вот пример его использования:

/*************************************************************************
  With TChildProc.Create(ReadIni(TagParams.Values['file'], FPage),
TagParams.Values['cmd']) do
    Try
      WriteToChild(TagParams.Text);
      ReplaceText:=ReadStrFromChild;
    Finally
      Free;
    End;
//*************************************************************************

Не правда ли просто?

Горбань С.В.



К материалу прилагаются файлы:


Смотрите также материалы по темам:
[Mailslot, pipes] [Ввод/вывод (StdIn/StdOut)]

 Обсуждение материала [ 17-03-2009 08:21 ] 17 сообщений
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования