Получаем дескриптор и записываем в консоль, которая запустила наш процесс

#c# #c #windows #winapi

#c# #c #Windows #winapi

Вопрос:

Как я мог бы записать в стандартный вывод какой-нибудь уже открытой консоли? Я нахожу нужную мне консоль с помощью этого фрагмента кода:

     IntPtr ptr = GetForegroundWindow();           
    int u;
    GetWindowThreadProcessId(ptr, out u);
    Process process = Process.GetProcessById(u);
  

Проблема в том, как получить стандартный указатель выходного дескриптора (stdHandle) этого процесса.

Тогда я бы хотел что-то вроде:

                 SafeFileHandle safeFileHandle = new SafeFileHandle(stdHandle, true);
                FileStream fileStream = new FileStream(safeFileHandle, FileAccess.Write);
                Encoding encoding = Encoding.ASCII;
                StreamWriter standardOutput = new StreamWriter(fileStream, encoding);
                standardOutput.AutoFlush = true;
                Console.SetOut(standardOutput);
  

Код на C с использованием Windows API в порядке — я могу использовать PInvoke.

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

Можно ли перенаправить стандартный вывод после создания процесса?

PS: Я читал о каком-то COM-файле, который можно использовать для этого, так что это означает, что существует программный способ …

Спасибо!

Ответ №1:

Я, наконец, понял, как прозрачно подключиться к консоли, если это окно переднего плана при запуске приложения Windows.

Не спрашивайте меня, почему STD_ERROR_HANDLE должен быть передан вместо STD_OUTPUT_HANDLE , но это просто работает, вероятно, потому, что стандартная ошибка может быть передана совместно.

Примечание: консоль может принимать вводимые пользователем данные при отображении сообщений вашего приложения внутри, но использовать ее, когда stderr выводится из вашего приложения, немного запутанно.

С помощью этого фрагмента кода, если вы запустите приложение из окна консоли с хотя бы одним параметром, оно подключит консоль.Запишите в нее, и если вы запустите приложение с параметром /debug, то оно прикрепит даже отладку.Записываем в консоль.

Вызовите Cleanup() перед выходом из приложения, чтобы освободить консоль, и отправьте нажатие клавиши Enter для освобождения последней строки, чтобы консоль можно было использовать так же, как и до запуска приложения.

PS. Вы не можете использовать перенаправление вывода с помощью этого метода, т.Е.: yourapp.exe > file.txt потому что вы получите пустой файл. И даже не пытайтесь myapp.exe > file.txt 2>amp;1 потому что вы приведете к аварийному завершению работы приложения (ошибка перенаправления на вывод означает, что мы пытаемся подключиться к неразделяемому буферу).

Вот код:

 [DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);

[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

[DllImport("kernel32.dll",
    EntryPoint = "GetStdHandle",
    SetLastError = true,
    CharSet = CharSet.Auto,
    CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr GetStdHandle(int nStdHandle);

[DllImport("kernel32", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);

[DllImport("kernel32.dll",
    EntryPoint = "AllocConsole",
    SetLastError = true,
    CharSet = CharSet.Auto,
    CallingConvention = CallingConvention.StdCall)]
private static extern int AllocConsole();

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();

private const int STD_OUTPUT_HANDLE = -11;
private const int STD_ERROR_HANDLE = -12;
private static bool _consoleAttached = false;
private static IntPtr consoleWindow;

[STAThread]
static void Main()
{
    args = new List<string>(Environment.GetCommandLineArgs());

    int prId;
    consoleWindow = GetForegroundWindow();            
    GetWindowThreadProcessId(consoleWindow, out prId);
    Process process = Process.GetProcessById(prId);

    if (args.Count > 1 amp;amp; process.ProcessName == "cmd")
    {
        if (AttachConsole((uint)prId)) {
            _consoleAttached = true;
            IntPtr stdHandle = GetStdHandle(STD_ERROR_HANDLE); // must be error dunno why
            SafeFileHandle safeFileHandle = new SafeFileHandle(stdHandle, true);
            FileStream fileStream = new FileStream(safeFileHandle, FileAccess.Write);
            Encoding encoding = Encoding.ASCII;
            StreamWriter standardOutput = new StreamWriter(fileStream, encoding);
            standardOutput.AutoFlush = true;
            Console.SetOut(standardOutput);
            if (args.Contains("/debug")) Debug.Listeners.Add(new TextWriterTraceListener(Console.Out));
            Console.WriteLine(Application.ProductName   " was launched from a console window and will redirect output to it.");
        }
    }
    // ... do whatever, use console.writeline or debug.writeline
    // if you started the app with /debug from a console
    Cleanup();
}

private static void Cleanup() {
    try
    {
        if (_consoleAttached)
        {
            SetForegroundWindow(consoleWindow);
            SendKeys.SendWait("{ENTER}");
            FreeConsole();
        }    
    }        
}
  

Ответ №2:

Если предполагается выполнить запись в родительскую консоль, если таковая имеется, вы можете использовать функцию AttachConsole с аргументом ATTACH_PARENT_PROCESS. (см. msdn attachconsole)

ATTACH_PARENT_PROCESS (DWORD)-1 : Используем консоль родительского элемента текущего процесса

И если вам действительно нужно проверить родительский процесс, вы можете использовать CreateToolhelp32Snapshot и получить родительский процесс через элемент th32ParentProcessID структуры PROCESSENTRY32.

Ответ №3:

Если вы просто хотите записать в консоль, которая используется каким-либо другим приложением, то вы можете воспользоваться следующим — вам нужно использовать P / Invoke для выполнения первого шага:

  • AttachConsole(pid) для подключения к этой консоли — если ваш процесс уже связан с консолью, вам придется сначала освободить консоль, поскольку процесс может быть связан только с одной консолью одновременно.
  • Теперь, когда вы подключены, получаем дескриптор вывода консоли с помощью CreateFile(«CONOUT $», GENERIC_WRITE, FILE_SHARE_WRITE, … ) — возможно, удастся выполнить эту часть в управляемом коде.
  • Теперь, когда у вас есть ДЕСКРИПТОР, оберните его в управляемый код — эту часть вы уже знаете.

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

Ответ №4:

Системный процесс однозначно идентифицируется в системе по его идентификатору процесса. Как и многие ресурсы Windows, процесс также идентифицируется по его дескриптору, который может быть не уникальным на компьютере. Дескриптор — это общий термин для обозначения идентификатора ресурса. Операционная система сохраняет дескриптор процесса, доступ к которому осуществляется через Процесс.Обрабатываем свойство компонента процесса, даже когда процесс завершен. Таким образом, вы можете получить административную информацию о процессе, такую как сам процесс.Код выхода (обычно либо нулевой для успеха, либо ненулевой код ошибки) и процесс.Время выхода. Дескрипторы — чрезвычайно ценный ресурс, поэтому утечка дескрипторов более опасна, чем утечка памяти.

Это не точный ответ на ваши вопросы, но он помогает вам понять основную вещь на самом деле.

Комментарии:

1. Я понимаю, что дескрипторы деликатны, но я бы не стал увеличивать количество ссылок, делающих плохие вещи с дескриптором. Когда процесс-владелец удалит дескриптор, мой код просто завершится ошибкой … чего я и ожидал. Нет способа записать в стандартный вывод процесса (то есть в Windows, потому что в Linux вы можете)?

2. да, я знаю, что в Linux мы можем, но я думаю, что сейчас необходимы некоторые исследования COM, потому что я уверен, что есть способы сделать это через COM. Уведомлю вас, если найду что-то релевантное. Между тем, вопрос и тема выглядят интересными для меня.