Подтвердить что ты не робот

Отсутствие вывода консоли при использовании AllocConsole и целевой архитектуры x86

У меня есть проект WinForms, и если пользователю нужна консоль отладки, я выделяю консоль с AllocConsole().

Весь вывод консоли работает нормально с целевой архитектурой, установленной в "Любой процессор", но когда я меняю ее на "x86", она ничего не выводит (Console.Read() по-прежнему работает так, как ожидалось). Если я сразу открою EXE, выход будет работать. Похоже, Visual Studio перенаправляет его в собственное окно "Выход".

Я также попробовал этот ответ, но это не сработало, я также пробовал Console.SetOut(GetStdHandle(-11)), который тоже не работал.

Настройка целевой архитектуры на "Любой процессор" для меня не является вариантом.

Итак, вот мои два вопроса:

  • Почему это так, когда целевая архитектура установлена ​​на x86?
  • Как я могу выводить на консоль при работе внутри Visual Studio?
4b9b3361

Ответ 1

Когда "Включить отладку собственного кода" включено, выходные данные с консолей, созданных с помощью AllocConsole, перенаправляются в окно вывода отладочной информации.

Причина, по которой это происходит только в x86, а не в AnyCPU, заключается в том, что вы можете отлаживать только собственный код в приложении x86.

Обратите внимание, что это происходит только с консолями, созданными с помощью AllocConsole. Вывод консольного приложения не перенаправляется.

РЕДАКТИРОВАТЬ: Другая причина того, что консоль не выводит текст, заключается в том, что вы записали в консоль перед вызовом AllocConsole.

Независимо от причины, этот код восстановит вывод, если он был перенаправлен, и повторно откроет консоль, если она будет недействительной. Он использует магическое число 7, которому обычно соответствует дескриптор stdout.

using System;
using System.IO;
using System.Runtime.InteropServices;

public static class ConsoleHelper
{
    public static void CreateConsole()
    {
        AllocConsole();

        // stdout handle seems to always be equal to 7
        IntPtr defaultStdout = new IntPtr(7);
        IntPtr currentStdout = GetStdHandle(StdOutputHandle);

        if (currentStdout != defaultStdout)
            // reset stdout
            SetStdHandle(StdOutputHandle, defaultStdout);

        // reopen stdout
        TextWriter writer = new StreamWriter(Console.OpenStandardOutput()) 
        { AutoFlush = true };
        Console.SetOut(writer);
    }

    // P/Invoke required:
    private const UInt32 StdOutputHandle = 0xFFFFFFF5;
    [DllImport("kernel32.dll")]
    private static extern IntPtr GetStdHandle(UInt32 nStdHandle);
    [DllImport("kernel32.dll")]
    private static extern void SetStdHandle(UInt32 nStdHandle, IntPtr handle);
    [DllImport("kernel32")]
    static extern bool AllocConsole();
}

См. Как определить, был ли перенаправлен файл Console.In(stdin)? для другого способа определить, были ли перенаправлены дескрипторы консоли.

Ответ 2

Ни один из предыдущих ответов не помог мне с VS2017 и Windows 10 (например, они не дали результатов, если запустить приложение в режиме отладки).

Ниже вы можете найти немного улучшенный код. Идея та же, но магические числа удалены (Ceztko уже упоминал об этом) и все необходимые входящие/исходящие потоки инициализируются.

Этот код работает для меня, если создать новую консоль (alwaysCreateNewConsole = true).

Присоединение к консоли родительского процесса (alwaysCreateNewConsole = false) имеет несколько недостатков. Например, я не смог полностью имитировать поведение консольного приложения, запущенного из cmd. И я не уверен, что это вообще возможно.

И самое главное: после пересмотра класса Console я пересмотрел общую идею использования класса Console с созданной вручную консолью. Это работает хорошо (я надеюсь) для большинства случаев, но может принести много боли в будущем.

    static class WinConsole
    {
        static public void Initialize(bool alwaysCreateNewConsole = true)
        {
            bool consoleAttached = true;
            if (alwaysCreateNewConsole
                || (AttachConsole(ATTACH_PARRENT) == 0
                && Marshal.GetLastWin32Error() != ERROR_ACCESS_DENIED))
            {
                consoleAttached = AllocConsole() != 0;
            }

            if (consoleAttached)
            {
                InitializeOutStream();
                InitializeInStream();
            }
        }

        private static void InitializeOutStream()
        {
            var fs = CreateFileStream("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, FileAccess.Write);
            if (fs != null)
            {
                var writer = new StreamWriter(fs) { AutoFlush = true };
                Console.SetOut(writer);
                Console.SetError(writer);
            }
        }

        private static void InitializeInStream()
        {
            var fs = CreateFileStream("CONIN$", GENERIC_READ, FILE_SHARE_READ, FileAccess.Read);
            if (fs != null)
            {
                Console.SetIn(new StreamReader(fs));
            }
        }

        private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode,
                                FileAccess dotNetFileAccess)
        {
            var file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero), true);
            if (!file.IsInvalid)
            {
                var fs = new FileStream(file, dotNetFileAccess);
                return fs;
            }
            return null;
        }

        #region Win API Functions and Constants
        [DllImport("kernel32.dll",
            EntryPoint = "AllocConsole",
            SetLastError = true,
            CharSet = CharSet.Auto,
            CallingConvention = CallingConvention.StdCall)]
        private static extern int AllocConsole();

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

        [DllImport("kernel32.dll",
            EntryPoint = "CreateFileW",
            SetLastError = true,
            CharSet = CharSet.Auto,
            CallingConvention = CallingConvention.StdCall)]
        private static extern IntPtr CreateFileW(
              string lpFileName,
              UInt32 dwDesiredAccess,
              UInt32 dwShareMode,
              IntPtr lpSecurityAttributes,
              UInt32 dwCreationDisposition,
              UInt32 dwFlagsAndAttributes,
              IntPtr hTemplateFile
            );

        private const UInt32 GENERIC_WRITE = 0x40000000;
        private const UInt32 GENERIC_READ = 0x80000000;
        private const UInt32 FILE_SHARE_READ = 0x00000001;
        private const UInt32 FILE_SHARE_WRITE = 0x00000002;
        private const UInt32 OPEN_EXISTING = 0x00000003;
        private const UInt32 FILE_ATTRIBUTE_NORMAL = 0x80;
        private const UInt32 ERROR_ACCESS_DENIED = 5;

        private const UInt32 ATTACH_PARRENT = 0xFFFFFFFF;

        #endregion
    }

Ответ 3

После работы для меня в vs 2015, никто не работал из других ответов:

Источник: https://social.msdn.microsoft.com/profile/dmitri567/?ws=usercard-mini

using System;   
using System.Windows.Forms;   
using System.Text;   
using System.IO;   
using System.Runtime.InteropServices;   
using Microsoft.Win32.SafeHandles;   

namespace WindowsApplication   
{   
    static class Program   
    {   
        [DllImport("kernel32.dll",   
            EntryPoint = "GetStdHandle",   
            SetLastError = true,   
            CharSet = CharSet.Auto,   
            CallingConvention = CallingConvention.StdCall)]   
        private static extern IntPtr GetStdHandle(int nStdHandle);   
        [DllImport("kernel32.dll",   
            EntryPoint = "AllocConsole",   
            SetLastError = true,   
            CharSet = CharSet.Auto,   
            CallingConvention = CallingConvention.StdCall)]   
        private static extern int AllocConsole();   
        private const int STD_OUTPUT_HANDLE = -11;   
        private const int MY_CODE_PAGE = 437;   

        static void Main(string[] args)   
        {   
            Console.WriteLine("This text you can see in debug output window.");   

            AllocConsole();   
            IntPtr stdHandle=GetStdHandle(STD_OUTPUT_HANDLE);   
            SafeFileHandle safeFileHandle = new SafeFileHandle(stdHandle, true);   
            FileStream fileStream = new FileStream(safeFileHandle, FileAccess.Write);   
            Encoding encoding = System.Text.Encoding.GetEncoding(MY_CODE_PAGE);   
            StreamWriter standardOutput = new StreamWriter(fileStream, encoding);   
            standardOutput.AutoFlush = true;   
            Console.SetOut(standardOutput);   

            Console.WriteLine("This text you can see in console window.");   

            MessageBox.Show("Now I'm happy!");   
        }   
    }   
}  

Ответ 4

У меня также была эта проблема. Каждый раз, когда я пытался отлаживать свое приложение, консоль была пуста. Как ни странно, запуск exe без отладчика работал нормально.

Я обнаружил, что мне пришлось Enable the Visual Studio hosting process из проекта Debug.

Стивен прав, что Enable native code debugging перенаправляет консоль в окно вывода. Однако, независимо от настройки отладки собственного кода, я не видел абсолютно никакого вывода ни в одном месте, пока не включил процесс хостинга Visual Studio.

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

Ответ 5

Просто хотел опубликовать ответ от сообщества разработчиков Visual studio. https://developercommunity.visualstudio.com/content/problem/12166/console-output-is-gone-in-vs2017-works-fine-when-d.html Перейдите по этой ссылке и посмотрите ответ рамкумара Рамеша. Я проверил этот код в VS 2017 года. Я потратил один день, чтобы найти этот ответ. Надеюсь, это поможет и вам.

Edit-- Как предложено Майком, чтобы включить некоторое описание. Я хотел бы предложить некоторые исправления в ответе Зуниара. Он тестировал с VS 2015. Но это не будет работать в VS 2017. Вместо GetStdHandle, пожалуйста, используйте ссылку CreateFile из kernel32.dll

IntPtr stdHandle = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, 0, 
OPEN_EXISTING, 0, 0);

Перед добавлением вышеуказанного кода, пожалуйста, объявите

[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFile(string lpFileName, uint 
dwDesiredAccess, uint dwShareMode, uint lpSecurityAttributes, uint 
dwCreationDisposition, uint dwFlagsAndAttributes, uint hTemplateFile);

private const int MY_CODE_PAGE = 437;
private const uint GENERIC_WRITE = 0x40000000;
private const uint FILE_SHARE_WRITE = 0x2;        
private const uint OPEN_EXISTING = 0x3;

Я взял этот код по указанной ссылке.