Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Как переназначить вывод в файл для консольной программы, запускаемой по CreateProcess

Автор: Кузнецов Алексей
Специально для Королевства Delphi

Я не профи в Win API, просто у меня возникла именно такая проблема. Я нашел решение устраивающее меня. И к тому же решил, поделился с вами. Если кому-то требуется что-то другое - дерзайте, я с удовольствием прочту на "Королевстве" что и как у вас получилось. Handle = Хэндл = Рукоятка :)

Хочу предложить 2 способа:

  • 1) Простой, с использованием command.com /c имя_консольной_проги > имя_файла_куда_переназначить_StdOut
  • 2) С использованием Win API (2 штуки)

Вы уж сами выберите, что вам подходит больше. Я использую способ № 2.2.

Рассмотрим их более подробно на примерах.

Способ №1


var
  StartupInfo: TStartupInfo;
  ProcessInformation: TProcessInformation;
begin
  GetStartupInfo(StartupInfo);
  with StartupInfo do
  begin
    wShowWindow := SW_HIDE; //не показывать окно
    dwFlags := STARTF_USESHOWWINDOW;
  end;

// для примера будем запускать [c:\program files\Borland\Delphi5\Bin]grep.exe с ключом '?'
  Win32Check(CreateProcess(nil, 'command.com /c  grep.exe ? > MyStdOut.txt',
    nil, nil, FALSE, CREATE_NEW_CONSOLE, nil, nil, StartupInfo, ProcessInformation));

// ждем пока наш процесс отработает
  WaitForSingleObject(ProcInfo.hProcess, INFINITE);

  Win32Check(CloseHandle(ProcInfo.hProcess);
end;

Способ №2.1


var
  ProcInfo: TProcessInformation;
  StartupInfo: TStartupInfo;
  hOut, hOutDup: THandle;
begin
  // Создаем файл в который и будем переназначать StdOut
  // Например, с такими настройками, вы можете их изменить под свои нужды
  hOut := CreateFile('MyStdOut.txt', GENERIC_WRITE, 0, nil,
    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  if (hOut = INVALID_HANDLE_VALUE) then
    RaiseLastWin32Error;
end;

А вот в этом месте и происходит все самое важное!!! Необходимо сделать рукоятку нашего файла НАСЛЕДУЕМОЙ, что и делаем…

 Win32Check(DuplicateHandle(GetCurrentProcess, hOut, 
   GetCurrentProcess, @hOutDup, 0, TRUE, DUPLICATE_SAME_ACCESS));

Небольшое замечание: следует отметить, что если вы пишите прогу ТОЛЬКО под NT/2000, то сделать рукоятку наследуемой можно проще:

 Win32Check(SetHandleInformation (hOut, HANDLE_FLAG_INHERIT, 
   HANDLE_FLAG_INHERIT);

и не надо будет заводить дубликат рукоятки hOutDup


// эта рукоятка нам уже не нужна, хотя вы можете ее
// использовать для своих целей
Win32Check(CloseHandle(hOut));

GetStartupInfo(StartupInfo);
with StartupInfo do
begin
  wShowWindow := SW_HIDE; // не показывать окно
  dwFlags := dwFlags or STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
  hStdOutput := hOutDup; // присваиваем рукоятку на свой файл
end;

Для примера будем запускать [c:\program files\Borland\Delphi5\Bin]grep.exe с ключом '?' Вызов CreateProcess с флагом bInheritHandles = TRUE !!!


Win32Check(CreateProcess(nil, 'grep.exe ?', nil, nil, TRUE,
  CREATE_NEW_CONSOLE, nil, nil, StartupInfo, ProcInfo));

// ждем пока наш процесс отработает
WaitForSingleObject(ProcInfo.hProcess, INFINITE);

Win32Check(CloseHandle(ProcInfo.hProcess));

// если вы больше ничего не хотите делать с файлом, в который
// перенаправили StdOut, то закроем его
Win32Check(CloseHandle(hOutDup));
end;


Способ №2.2

Этот способ мне показал Юрий Зотов (поместив его в разделе "Обсуждение статьи"), спасибо. Оказывается, рукоятку гораздо проще сделать наследуемой, если использовать SECURITY_ATTRIBUTES.


var
  ProcInfo: TProcessInformation;
  StartupInfo: TStartupInfo;
  SecAtrtrs: TSecurityAttributes;
  hOut: THandle;
begin
  with SecAtrtrs do
  begin
    nLength := SizeOf(TSecurityAttributes);
    lpSecurityDescriptor := nil;
    bInheritHandle := true; // ВОТ ОНО !!! Наша рукоятка будет НАСЛЕДУЕМОЙ
  end;

  // Создаем файл в который и будем переназначать StdOut
  // Например, с такими настройками, вы можете их изменить под свои нужды
  hOut := CreateFile('MyStdOut.txt', GENERIC_WRITE, 0, @SecAtrtrs,
    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  if (hOut = INVALID_HANDLE_VALUE) then
    RaiseLastWin32Error;

  GetStartupInfo(StartupInfo);
  with StartupInfo do
  begin
    wShowWindow := SW_HIDE; // не показывать окно
    dwFlags := dwFlags or STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
    hStdOutput := hOutDup; // присваиваем рукоятку на свой файл
  end;

  // для примера будем запускать
  // [c:\program files\Borland\Delphi5\Bin]grep.exe с ключом '?'
  // Вызов CreateProcess с флагом bInheritHandles = TRUE !!!
  Win32Check(CreateProcess(nil, 'grep.exe ?', nil, nil, TRUE,
    CREATE_NEW_CONSOLE, nil, nil, StartupInfo, ProcInfo));

  // ждем пока наш процесс отработает
  WaitForSingleObject(ProcInfo.hProcess, INFINITE);

  Win32Check(CloseHandle(ProcInfo.hProcess));

  // если вы больше ничего не хотите делать с файлом, в который
  // перенаправили StdOut, то закроем его
  Win32Check(CloseHandle(hOut));
end;


Заключение

Первый способ проверялся мной под Win98 и Win2k Pro. Второй (обе разновидности) только под Win2k Pro.

Оба способа служат одной и той же цели, но во втором случае программист получает больше контроля над ситуацией. Вызовы Win32Check и RaiseLastWin32Error добавляйте (убирайте) по своему вкусу.

Кстати, кто хочет узнать на эту тему больше - откройте Win32.hlp (поставляется вместе с Делфой) и на закладке "Предметный указатель" наберите "Creating a Child Process with Redirected Input and Output", "Inheritance" и "SECURITY_ATTRIBUTES" и ВНИМАТЕЛЬНО изучите. Изучив эти (и смежные) разделы вы сможете переназначить StdOut, StdIn и StdErr куда вам захочется.

Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay