Сергей Горбань дата публикации 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
raise ECreatePipeErr.CreateRes(@sCreatePipeMsg)
Else
If not CreatePipe(FChildStdinRd, FChildStdinWr, FsaAttr, 0) Then
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
ZeroMemory(@siStartInfo, SizeOf(TStartupInfo));
siStartInfo.cb:=SizeOf(TStartupInfo);
siStartInfo.hStdInput:=StdIn;
siStartInfo.hStdOutput:=StdOut;
siStartInfo.dwFlags:=STARTF_USESTDHANDLES;
Result:=CreateProcess(Nil,
PChar(ExeName+' '+CommadLine),
Nil,
Nil,
TRUE,
0,
Nil,
Nil,
siStartInfo,
piProcInfo);
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
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 сообщений |