Как получить стандартный вывод родительского процесса? - PullRequest
2 голосов
/ 18 декабря 2008

Я пишу утилиту (http://reg2run.sf.net), которая в случае выполнения без аргументов работает как приложение Windows (показывает OpenFileDialog и т. Д.), В противном случае - как консольное приложение.

Итак, в первом случае я не хочу показывать окно консоли, поэтому проект является приложением Windows. Но во-вторых - мне нужно показать это, и он создан с

if (ptrNew == IntPtr.Zero)
{
    ptrNew = GetStdHandle(-11);
}
if (!AllocConsole())
{
    throw new ExternalCallException("AllocConsole");
}
ptrNew = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, 3, 0, IntPtr.Zero);
if (!SetStdHandle(-11, ptrNew))
{
    throw new ExternalCallException("SetStdHandle");
}
StreamWriter newOut = new StreamWriter(Console.OpenStandardOutput());
newOut.AutoFlush = true;
Console.SetOut(newOut);
Console.SetError(newOut);

И что я хочу - это захватить стандартный вывод родительского процесса и использовать его, если он существует (в случае выполнения через cmd.exe или Far Manager). Как я могу это сделать?

Я пытался

static Process GetParentProc()
{
int pidParent = 0;
int pidCurrent = Process.GetCurrentProcess().Id;

IntPtr hSnapshot = CreateToolhelp32Snapshot(2, 0);
if (hSnapshot == IntPtr.Zero)
{
    return null;
}

PROCESSENTRY32 oProcInfo = new PROCESSENTRY32();
oProcInfo.dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32));

if (!Process32First(hSnapshot, ref oProcInfo))
{
    return null;
}
do
{
    if (pidCurrent == oProcInfo.th32ProcessID)
    {
        pidParent = (int)oProcInfo.th32ParentProcessID;
    }
}
while (pidParent == 0 && Process32Next(hSnapshot, ref oProcInfo));

if (pidParent > 0)
{
    return Process.GetProcessById(pidParent);
}
else
{
    return null;
}

и

StreamWriter newOut = GetParentProc().StandardInput;

но получено исключение InvalidOperationException: StandardIn не было перенаправлено. Из-за

GetParentProc().StartInfo.RedirectStandardOutput = false

Ответы [ 2 ]

6 голосов
/ 18 декабря 2008

Существует несколько подходов для приложений, которым необходимо выбрать, будут ли они работать в качестве консольных или графических приложений в зависимости от контекста в Windows:

  1. Имеет два отдельных приложения и одно условно запускает другое.
  2. Вариант вышеупомянутой стратегии имеет два приложения, одно из которых называется «app.com» (т.е. просто переименовывает консольный EXE-файл с расширением COM), а другое - «app.exe», так что вызовы командной строки найдут app.com первым. Из-за древней совместимости с DOS исполняемые файлы .COM были найдены раньше .EXE. (Это настраивается в Windows; см. Переменную среды PATHEXT.)
  3. Техника rxvt / Cygwin, которую я не видел нигде в документации.

Позвольте мне немного подробнее рассказать о том, как работает rxvt на Cygwin. Rxvt - это эмулятор терминала, который обычно работает в системе X Window. Из-за ограничений консоли Win32 Cygwin упаковывает ее как более полнофункциональную консоль с поддержкой таких вещей, как множество строк истории, динамическое изменение размера, настраиваемые шрифты для каждого экземпляра и цветовые темы, выбор мыши без блокировки приложения и копирование, и т. д. Для того, чтобы работать в Windows изначально, rxvt, поставляемый с Cygwin, включает в себя крошечную библиотеку оболочки X11 для Win32. Rxvt в Windows на самом деле является консольным приложением по причинам совместимости с существующими собственными исполняемыми файлами Win32, но в большинстве случаев вы никогда не увидите консоль; вы просто видите само окно эмулятора терминала rxvt.

Способ его работы специально реализован в rxvt/W11/wrap/wrap.c в дереве исходных текстов rxvt, в функции под названием hideConsole(). По сути, он открывает консоль (с CreateFile("CONOUT$" ...)) и проверяет, находится ли позиция курсора в (0,0) (используя GetConsoleScreenBufferInfo() на дескрипторе консоли).

Если это так, то это означает, что оно было запущено как отдельное приложение, а не из родительского приложения консоли, и, таким образом, оно знает, что ОС создала выделенную консоль Win32 для процесса. Он продолжает скрывать это консольное окно, но сначала его нужно найти. Он использует SetConsoleTitle, чтобы установить для заголовка окна консоли уникальное значение на основе имени приложения и текущего идентификатора потока. Затем он использует FindWindow для поиска дескриптора этого окна (периодически Sleep в течение нескольких мс, если необходимо изменить заголовок, поскольку консольные окна фактически полностью управляются другим процессом в Windows). Когда он в конце концов находит дескриптор окна, он скрывает его с ShowWindowAsync, передавая SW_HIDE.

Используя этот подход, вы можете написать приложение, которое:

  • если запущен из родительской консоли, он может продолжать использовать эту консоль
  • если запущено как приложение, оно может по выбору выбрать, скрывать ли консоль

Единственным недостатком является очень короткая вспышка окна консоли при запуске приложения.

3 голосов
/ 18 декабря 2008

Вы всегда можете использовать следующий метод P / Invoke:

[DllImport("kernel32.dll")]
static extern bool AttachConsole(int dwProcessId);

const int ATTACH_PARENT_PROCESS = -1;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...