Показать консоль в приложении Windows?

85

Есть ли способ показать консоль в приложении Windows?

Я хочу сделать что-то вроде этого:

static class Program
{
    [STAThread]
    static void Main(string[] args) {
        bool consoleMode = Boolean.Parse(args[0]);

        if (consoleMode) {
            Console.WriteLine("consolemode started");
            // ...
        } else {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}
Ase
источник

Ответы:

77

То, что вы хотите делать, невозможно в здравом уме. Был похожий вопрос, посмотрите ответы .

Также есть безумный подход (сайт отключен - резервная копия доступна здесь ), написанная Джеффри Найтом :

Вопрос: Как мне создать приложение, которое может работать как в режиме графического интерфейса (Windows), так и в режиме командной строки / консоли?

На первый взгляд это может показаться простым: вы создаете консольное приложение, добавляете к нему форму окна, и все готово. Однако есть проблема:

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

Кажется, что людям нужно настоящее приложение-амфибия, которое может без сбоев работать в любом режиме.

Если разобрать его, на самом деле здесь есть четыре варианта использования:

User starts application from existing cmd window, and runs in GUI mode
User double clicks to start application, and runs in GUI mode
User starts application from existing cmd window, and runs in command mode
User double clicks to start application, and runs in command mode.

Я публикую код для этого, но с оговоркой.

Я на самом деле думаю, что такой подход вызовет у вас гораздо больше проблем, чем он того стоит. Например, вам понадобится два разных пользовательских интерфейса - один для графического интерфейса, а другой - для команды / оболочки. Вам придется создать какой-то странный центральный логический движок, который абстрагируется от графического интерфейса пользователя и командной строки, и это будет просто странно. Если бы это был я, я бы отступил и подумал о том, как это будет использоваться на практике, и стоит ли такого рода переключение режимов работы. Таким образом, если для этого не требуется какой-то особый случай, я бы сам не стал использовать этот код, потому что, как только я сталкиваюсь с ситуациями, когда мне нужны вызовы API для выполнения чего-либо, я, как правило, останавливаюсь и спрашиваю себя: «Я слишком усложняю? ".

Тип вывода = Приложение Windows

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Microsoft.Win32;

namespace WindowsApplication
{
    static class Program
    {
        /*
    DEMO CODE ONLY: In general, this approach calls for re-thinking 
    your architecture!
    There are 4 possible ways this can run:
    1) User starts application from existing cmd window, and runs in GUI mode
    2) User double clicks to start application, and runs in GUI mode
    3) User starts applicaiton from existing cmd window, and runs in command mode
    4) User double clicks to start application, and runs in command mode.

    To run in console mode, start a cmd shell and enter:
        c:\path\to\Debug\dir\WindowsApplication.exe console
        To run in gui mode,  EITHER just double click the exe, OR start it from the cmd prompt with:
        c:\path\to\Debug\dir\WindowsApplication.exe (or pass the "gui" argument).
        To start in command mode from a double click, change the default below to "console".
    In practice, I'm not even sure how the console vs gui mode distinction would be made from a
    double click...
        string mode = args.Length > 0 ? args[0] : "console"; //default to console
    */

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

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

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

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

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

        [STAThread]
        static void Main(string[] args)
        {
            //TODO: better handling of command args, (handle help (--help /?) etc.)
            string mode = args.Length > 0 ? args[0] : "gui"; //default to gui

            if (mode == "gui")
            {
                MessageBox.Show("Welcome to GUI mode");

                Application.EnableVisualStyles();

                Application.SetCompatibleTextRenderingDefault(false);

                Application.Run(new Form1());
            }
            else if (mode == "console")
            {

                //Get a pointer to the forground window.  The idea here is that
                //IF the user is starting our application from an existing console
                //shell, that shell will be the uppermost window.  We'll get it
                //and attach to it
                IntPtr ptr = GetForegroundWindow();

                int  u;

                GetWindowThreadProcessId(ptr, out u);

                Process process = Process.GetProcessById(u);

                if (process.ProcessName == "cmd" )    //Is the uppermost window a cmd process?
                {
                    AttachConsole(process.Id);

                    //we have a console to attach to ..
                    Console.WriteLine("hello. It looks like you started me from an existing console.");
                }
                else
                {
                    //no console AND we're in console mode ... create a new console.

                    AllocConsole();

                    Console.WriteLine(@"hello. It looks like you double clicked me to start
                   AND you want console mode.  Here's a new console.");
                    Console.WriteLine("press any key to continue ...");
                    Console.ReadLine();       
                }

                FreeConsole();
            }
        }
    }
}
Игал Сербан
источник
13
Я нахожу иронию с Microsoft и тем, как она хочет создавать интерфейсы C # для всего своего API, но при этом не существует способа C # для выполнения такой простой задачи.
Рамон Заразуа Б.
3
Вместо того, чтобы зависеть от консоли, являющейся окном переднего плана, вы можете получить идентификатор родительского процесса текущего процесса, используя winapi: stackoverflow.com/a/3346055/855432
ghord
2
На момент написания резервная копия статьи доступна здесь web.archive.org/web/20111227234507/http://www.rootsilver.com/…
Андрей Савиных
2
Привет! Я обнаружил, что если я запустил это решение из оболочки, такой как Far, nc, она создаст новую консоль. Если я подключаюсь к Far Console как cmd, он работает неправильно. Я рекомендую создать ConsoleApplication и, если нужен графический интерфейс, выполните FreeConsole (); Отличная статья! Благодаря!
Максим Васильев
6
Я бы рекомендовал вызывать AttachConsoleс -1(значением константы API ATTACH_PARENT_PROCESS), а не надеяться, что окно переднего плана - это правильное командное окно для записи.
Джон Ханна
70

Это немного старовато (хорошо, оно ОЧЕНЬ старое), но я делаю то же самое прямо сейчас. Вот очень простое решение, которое мне подходит:

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

[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();

[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

const int SW_HIDE = 0;
const int SW_SHOW = 5;

public static void ShowConsoleWindow()
{
    var handle = GetConsoleWindow();

    if (handle == IntPtr.Zero)
    {
        AllocConsole();
    }
    else
    {
        ShowWindow(handle, SW_SHOW);
    }
}

public static void HideConsoleWindow()
{
    var handle = GetConsoleWindow();
    ShowWindow(handle, SW_HIDE);
}
Энтони
источник
5
если вы запускаете это в окне cmd, это откроет другое окно консоли, что нежелательно в автоматическом процессе, который должен будет захватывать вывод консоли.
AaA 08
Со своей стороны, я использовал предоставленный код, но добавил общую функцию InitConsole () для выполнения части AllocConsole (), если дескриптором является intptr.zero. Когда я использовал ShowConsoleWindow, а затем сразу же распечатал его, это не сработало. Выделение консоли при запуске приложения с использованием ShowConsoleWindow действительно работает. В остальном это идеально подходит для меня. Спасибо ..
Sage Pourpre
Console.WriteLineлоги не отображаются в случае, запущенном из cmd с аргументами
Мохаммед Али
Не забывайтеusing System.Runtime.InteropServices;
Даррен Гриффит
19

Самый простой способ - запустить приложение WinForms, перейти в настройки и изменить тип на консольное приложение.

ICR
источник
1
Приложение -> тип вывода: Консольное приложение. Сделал это для меня!
Lodewijk
Это отлично сработало. Надеюсь, это больше ничего не испортит. Спасибо и +1.
deathismyfriend
1
При запуске приложения двойным щелчком по нему открывается окно cmd
Мохаммед Али
13

Отказ от ответственности

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

Этот метод также поддерживает отображение только окна консоли, но не поддерживает отображение только формы Windows, т. Е. Консоль будет отображаться всегда. Вы можете взаимодействовать (т.е. получать данные - Console.ReadLine(), Console.Read()) с окном консоли, только если вы не показываете формы окна; вывод в консоль -Console.WriteLine() - работает в обоих режимах.

Это предоставляется как есть; нет никаких гарантий, что это не приведет к чему-то ужасному позже, но это работает.

Шаги проекта

Начать со стандартного консольного приложения .

Отметьте Mainметод как[STAThread]

Добавьте ссылку в свой проект на System.Windows.Forms

Добавить для Windows Form .

Добавьте стандартный стартовый код Windows в свой Main метод :

Конечный результат

У вас будет приложение, которое отображает консоль и, возможно, формы Windows.

Образец кода

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ConsoleApplication9 {
    class Program {

        [STAThread]
        static void Main(string[] args) {

            if (args.Length > 0 && args[0] == "console") {
                Console.WriteLine("Hello world!");
                Console.ReadLine();
            }
            else {
                Application.EnableVisualStyles(); 
                Application.SetCompatibleTextRenderingDefault(false); 
                Application.Run(new Form1());
            }
        }
    }
}

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ConsoleApplication9 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Click(object sender, EventArgs e) {
            Console.WriteLine("Clicked");
        }
    }
}
Сэм Мелдрам
источник
хороший код здесь. а как отключить отображение консоли, если вы хотите продать или развернуть или еще что-то ???
r4ccoon
@ r4ccoon - нельзя. Но вы можете легко перенести весь свой код в обычное приложение для Windows.
Сэм Мелдрам,
Ну, IMHO, более простой способ добиться того же эффекта - создать проект Windows Forms как обычно, затем щелкнуть его правой кнопкой мыши в обозревателе решений -> Свойства и изменить Тип вывода на Консольное приложение. (править: теперь я понял, что это в основном ответ ICR)
kamilk
9

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

Я нашел простой способ, который кажется довольно надежным и простым. У меня это сработало. Идея:

  • Скомпилируйте свой проект как приложение Windows. При запуске исполняемого файла может быть родительская консоль, а может и нет. Цель состоит в том, чтобы повторно использовать существующую консоль, если она существует, или создать новую, если нет.
  • AttachConsole (-1) будет искать консоль родительского процесса. Если он есть, он прикрепляется к нему, и все готово. (Я пробовал это, и он работал правильно при вызове моего приложения из cmd)
  • Если AttachConsole вернула false, родительская консоль отсутствует. Создайте его с помощью AllocConsole.

Пример:

static class Program
{
    [DllImport( "kernel32.dll", SetLastError = true )]
    static extern bool AllocConsole();

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

    static void Main(string[] args)
    {
        bool consoleMode = Boolean.Parse(args[0]);
        if (consoleMode)
        {
           if (!AttachConsole(-1))
              AllocConsole();
           Console.WriteLine("consolemode started");
           // ...
        } 
        else
        {
           Application.EnableVisualStyles();
           Application.SetCompatibleTextRenderingDefault(false);
           Application.Run(new Form1());
        }
    }
}

Предупреждение: кажется, что если вы попытаетесь написать на консоль до присоединения или выделения консоли, этот подход не сработает. Я предполагаю, что это первый раз, когда вы вызываете Console.Write / WriteLine, если консоли еще нет, тогда Windows автоматически создает для вас скрытую консоль. (Так что, возможно, ответ Энтони ShowConsoleWindow лучше после того, как вы уже написали на консоли, и мой ответ лучше, если вы еще не написали на консоль). Важно отметить, что это не работает:

static void Main(string[] args)
    {
        Console.WriteLine("Welcome to the program");   //< this ruins everything
        bool consoleMode = Boolean.Parse(args[0]);
        if (consoleMode)
        {
           if (!AttachConsole(-1))
              AllocConsole();
           Console.WriteLine("consolemode started");   //< this doesn't get displayed on the parent console
           // ...
        } 
        else
        {
           Application.EnableVisualStyles();
           Application.SetCompatibleTextRenderingDefault(false);
           Application.Run(new Form1());
        }
    }
Кевин Холт
источник
спасибо, что поделились образцом кода. Я попробовал и обнаружил, что он работает, но с ограничениями. Перенаправление вывода на консоль отсутствует (может быть исправлено дополнительным исходным кодом). Но главный недостаток в том, что он сразу возвращает управление консоли. Например, когда я \bin\Debug>shareCheck.exe /once\bin\Debug>hello. It looks like you started me from an existing console.
печатаю и
Спасибо, Кевин за предупреждение - у меня проблемы с подходами, предложенными в этом сообщении SO, и я, кажется, получаю «скрытую консоль», хотя раньше у меня не было вывода консоли ... Оказалось, что окно «Вывод» в Visual Studio - это скрытая консоль, если ваше приложение работает с присоединенным отладчиком! Стоит упомянуть ... (Итак, моя программа работала при переключении на запуск без отладчика, то есть Ctrl-F5)
Per Lundberg
3

Что сработало для меня, так это написать консольное приложение отдельно, которое делало бы то, что я хотел, скомпилировать его в exe, а затем выполнить Process.Start("MyConsoleapp.exe","Arguments")

Ян
источник
1
Это версия бритвы Оккама. Недостаточно сложно: P
Майкл Хоффманн
3

Проверьте этот исходный код. Весь закомментированный код - используется для создания консоли в приложении Windows. Без комментариев - чтобы скрыть консоль в консольном приложении. От сюда . (Ранее здесь .) Проект reg2run.

// Copyright (C) 2005-2015 Alexander Batishchev (abatishchev at gmail.com)

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace Reg2Run
{
    static class ManualConsole
    {
        #region DllImport
        /*
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool AllocConsole();
        */

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr handle);

        /*
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        private static extern IntPtr CreateFile([MarshalAs(UnmanagedType.LPStr)]string fileName, [MarshalAs(UnmanagedType.I4)]int desiredAccess, [MarshalAs(UnmanagedType.I4)]int shareMode, IntPtr securityAttributes, [MarshalAs(UnmanagedType.I4)]int creationDisposition, [MarshalAs(UnmanagedType.I4)]int flagsAndAttributes, IntPtr templateFile);
        */

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool FreeConsole();

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

        /*
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetStdHandle(int nStdHandle, IntPtr handle);
        */
        #endregion

        #region Methods
        /*
        public static void Create()
        {
            var ptr = GetStdHandle(-11);
            if (!AllocConsole())
            {
                throw new Win32Exception("AllocConsole");
            }
            ptr = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, 3, 0, IntPtr.Zero);
            if (!SetStdHandle(-11, ptr))
            {
                throw new Win32Exception("SetStdHandle");
            }
            var newOut = new StreamWriter(Console.OpenStandardOutput());
            newOut.AutoFlush = true;
            Console.SetOut(newOut);
            Console.SetError(newOut);
        }
        */

        public static void Hide()
        {
            var ptr = GetStdHandle(-11);
            if (!CloseHandle(ptr))
            {
                throw new Win32Exception();
            }
            ptr = IntPtr.Zero;
            if (!FreeConsole())
            {
                throw new Win32Exception();
            }
        }
        #endregion
    }
}
Абатищев
источник
1

На самом деле AllocConsole с SetStdHandle в приложении с графическим интерфейсом может быть более безопасным подходом. Проблема с уже упомянутым "захватом консоли" заключается в том, что консоль может вообще не быть окном переднего плана (особенно учитывая приток новых оконных менеджеров в Vista / Windows 7) среди прочего.

EFraim
источник
0

В wind32 приложения консольного режима полностью отличаются от обычных приложений, принимающих очереди сообщений. Они объявляются и компилируются по-разному. Вы можете создать приложение, которое имеет как консольную часть, так и обычное окно, и скрыть одно или другое. Но подозреваю, что вы найдете все это немного больше работы, чем вы думали.

Джо Несущий души
источник
Wind32 звучит как что-то, что принадлежит метеорологическому форуму, но если вы имеете в виду Win32, обратите внимание, что мы можем создавать циклы сообщений в консольных программах, как в PostThreadMessage для консольного приложения .
user34660
0

Согласно приведенной выше цитате Джеффри Найта, как только я сталкиваюсь с ситуациями, когда мне нужны вызовы API, чтобы что-то сделать, я, как правило, останавливаюсь и спрашиваю себя: «Я слишком усложняю?».

Если нужно иметь некоторый код и запускать его в режиме графического интерфейса Windows или в режиме консоли, рассмотрите возможность перемещения кода, используемого в обоих режимах, в DLL библиотеки кода, а затем наличие приложения Windows Forms, которое использует эту DLL, и консоли приложение, которое использует эту DLL (например, если в Visual Studio у вас теперь есть решение из трех проектов: библиотека с основной частью кода, графический интерфейс только с кодом Win Forms и консоль только с кодом вашей консоли).

Джинли
источник
0

И еще один запоздалый ответ. Я не смог получить никакого вывода на консоль, созданную AllocConsoleв соответствии с предыдущими предложениями, поэтому вместо этого я начинаю с консольного приложения . Затем, если консоль не нужна:

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetConsoleWindow();

        [DllImport("user32.dll")]
        private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        private const int SW_HIDE = 0;
        private const int SW_SHOW = 5;

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

        public static bool HideConsole()
        {
            var hwnd = GetConsoleWindow();
            GetWindowThreadProcessId(hwnd, out var pid);

            if (pid != Process.GetCurrentProcess().Id) // It's not our console - don't mess with it.
                return false;

            ShowWindow(hwnd, SW_HIDE);

            return true;
        }
v_b
источник