Wyświetlić konsolę w aplikacji Windows?

85

Czy istnieje sposób pokazania konsoli w aplikacji systemu Windows?

Chcę zrobić coś takiego:

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
źródło

Odpowiedzi:

77

To, co chcesz zrobić, nie jest możliwe w rozsądny sposób. Było podobne pytanie, więc spójrz na odpowiedzi .

Jest też szalone podejście (witryna nie działa - kopia zapasowa dostępna tutaj. ) Napisane przez Jeffrey Knight :

Pytanie: Jak utworzyć aplikację, która może działać w trybie GUI (Windows) lub w trybie wiersza poleceń / konsoli?

Pozornie wydaje się to łatwe: tworzysz aplikację Console, dodajesz do niej formularz Windows i zaczynasz działać. Jest jednak problem:

Problem: Jeśli uruchamiasz w trybie GUI, w tle czai się zarówno okno, jak i nieznośna konsola i nie masz żadnego sposobu, aby to ukryć.

Wydaje się, że ludzie chcą prawdziwej aplikacji dla płazów, która może działać płynnie w obu trybach.

Jeśli to zepsujesz, istnieją tutaj cztery przypadki użycia:

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.

Wysyłam kod, aby to zrobić, ale z zastrzeżeniem.

Właściwie myślę, że takie podejście sprawi, że w przyszłości będziesz miał o wiele więcej kłopotów, niż jest to warte. Na przykład będziesz musiał mieć dwa różne interfejsy użytkownika - jeden dla GUI i jeden dla polecenia / powłoki. Będziesz musiał zbudować jakiś dziwny centralny silnik logiczny, który abstrahuje od GUI i wiersza poleceń, a to będzie po prostu dziwne. Gdybym to był ja, cofnąłbym się i zastanowiłbym się, jak zostanie to wykorzystane w praktyce i czy tego rodzaju przełączanie trybu jest warte pracy. Tak więc, o ile nie wymaga tego jakiś specjalny przypadek, sam nie użyłbym tego kodu, ponieważ gdy tylko napotkam sytuacje, w których potrzebuję wywołań API, aby coś zrobić, zatrzymuję się i zadaję sobie pytanie „czy nadmiernie komplikuję sprawy? ”.

Typ danych wyjściowych = aplikacja systemu 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();
            }
        }
    }
}
Igal Serban
źródło
13
Uważam to za ironię w przypadku Microsoftu i tego, jak chce on tworzyć interfejsy C # dla całego swojego API, ale nie ma sposobu C # na wykonanie tak prostego zadania.
Ramon Zarazua B.,
3
Zamiast zależeć od tego, czy konsola jest oknem pierwszego planu, możesz uzyskać identyfikator procesu nadrzędnego bieżącego procesu za pomocą winapi: stackoverflow.com/a/3346055/855432
ghord
2
W chwili pisania tego tekstu kopia zapasowa artykułu jest dostępna tutaj: web.archive.org/web/20111227234507/http://www.rootsilver.com/…
Andrew Savinykh
2
Cześć! Odkryłem, że jeśli uruchomiłem to rozwiązanie z powłoki takiej jak Far, nc tworzy nową konsolę. Jeśli podłączę się do Far Console jak cmd, działa źle. Polecam stworzyć ConsoleApplication i jeśli potrzebny jest GUI, to zrób FreeConsole (); Świetny artykuł! Dzięki!
Maxim Vasiliev
6
Zalecałbym wywoływanie AttachConsolez -1(wartością stałej API ATTACH_PARENT_PROCESS) zamiast mieć nadzieję, że okno pierwszego planu jest właściwym oknem poleceń do zapisu.
Jon Hanna
70

To jest odrobinę stare (OK, jest BARDZO stare), ale teraz robię dokładnie to samo. Oto bardzo proste rozwiązanie, które działa dla mnie:

[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);
}
Anthony
źródło
5
jeśli uruchamiasz to w oknie cmd, otworzy się kolejne okno konsoli, co nie jest pożądane w zautomatyzowanym procesie, który będzie musiał przechwytywać dane wyjściowe konsoli.
AaA
Na koniec użyłem dostarczonego kodu, ale dodałem współdzieloną funkcję InitConsole (), aby wykonać część AllocConsole (), jeśli uchwyt jest intptr.zero. Kiedy użyłem ShowConsoleWindow, a następnie wydrukowałem na nim natychmiast po, to nie zadziałało. Przydzielenie konsoli podczas uruchamiania aplikacji, a następnie użycie ShowConsoleWindow zadziałało. Poza tym jest to dla mnie idealne. Dziękuję…
Sage Pourpre
Console.WriteLinelogi nie są wyświetlane w przypadku rozpoczętym z cmd z argumentami
Mohammad Ali
Nie zapomnijusing System.Runtime.InteropServices;
Darren Griffith
19

Najłatwiej jest uruchomić aplikację WinForms, przejść do ustawień i zmienić typ na aplikację konsolową.

ICR
źródło
1
Aplikacja -> typ wyjścia: Aplikacja konsolowa. Zrobiłem to dla mnie!
Lodewijk
To działało świetnie. Mam nadzieję, że nic innego nie zepsuje. Dzięki i +1.
deathismyfriend
1
Uruchomienie aplikacji poprzez dwukrotne kliknięcie otwiera okno cmd
Mohammad Ali
13

Zrzeczenie się

Jest na to sposób, który jest dość prosty, ale nie sugerowałbym, że jest to dobre podejście do aplikacji, którą chcesz pokazać innym. Ale jeśli jakiś programista musiałby wyświetlać jednocześnie konsolę i formularze systemu Windows, można to zrobić całkiem łatwo.

Ta metoda obsługuje również wyświetlanie tylko okna konsoli, ale nie obsługuje wyświetlania tylko formularza systemu Windows - tzn. Konsola będzie zawsze wyświetlana. Możesz wchodzić w interakcje (tj. Odbierać dane - Console.ReadLine(), Console.Read()) z oknem konsoli tylko wtedy, gdy nie pokazujesz formularzy Windows; wyjście do konsoli - Console.WriteLine()- działa w obu trybach.

Jest to dostarczane w stanie, w jakim jest; nie ma gwarancji, że to nie zrobi później czegoś strasznego, ale działa.

Etapy projektu

Zacznij od standardowej aplikacji konsolowej .

Oznacz Mainmetodę jako[STAThread]

Dodaj odwołanie w projekcie do System.Windows.Forms

Dodaj formularz systemu Windows do projektu.

Dodaj standardowy kod startowy systemu Windows do swojej Mainmetody:

Wynik końcowy

Będziesz mieć aplikację, która pokazuje konsolę i opcjonalnie formularze Windows.

Przykładowy kod

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");
        }
    }
}
Sam Meldrum
źródło
niezły kod. ale jak wyłączyć wyświetlanie konsoli, jeśli chcesz sprzedać, wdrożyć lub coś innego. ???
r4ccoon
@ r4ccoon - nie możesz. Ale możesz łatwo przenieść cały kod do normalnej aplikacji Windows.
Sam Meldrum
Cóż, IMHO prostszym sposobem osiągnięcia tego samego efektu jest utworzenie projektu Windows Forms w zwykły sposób, a następnie kliknięcie go prawym przyciskiem myszy w Eksploratorze rozwiązań -> Właściwości i zmiana typu wyjścia na aplikację konsolową. (edycja: teraz zdałem sobie sprawę, że to w zasadzie odpowiedź ICR)
kamilk
Niezła odpowiedź krok po kroku
AminM,
9

Ponowne wskrzeszenie bardzo starego wątku, ponieważ żadna z odpowiedzi tutaj nie działała dla mnie bardzo dobrze.

Znalazłem prosty sposób, który wydaje się dość solidny i prosty. U mnie to zadziałało. Pomysł:

  • Skompiluj swój projekt jako aplikację systemu Windows. Podczas uruchamiania pliku wykonywalnego może istnieć konsola nadrzędna, ale może nie. Celem jest ponowne wykorzystanie istniejącej konsoli, jeśli taka istnieje, lub utworzenie nowej, jeśli nie.
  • AttachConsole (-1) będzie szukać konsoli procesu nadrzędnego. Jeśli istnieje, przyczepia się do niego i gotowe. (Próbowałem tego i zadziałało poprawnie podczas wywoływania mojej aplikacji z cmd)
  • Jeśli AttachConsole zwrócił false, nie ma konsoli nadrzędnej. Utwórz je za pomocą AllocConsole.

Przykład:

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());
        }
    }
}

Uwaga: wydaje się, że jeśli spróbujesz napisać do konsoli przed dołączeniem lub przydzieleniem konsoli, to podejście nie działa. Domyślam się, że po raz pierwszy wywołujesz Console.Write / WriteLine, jeśli nie ma jeszcze konsoli, to Windows automatycznie tworzy dla ciebie ukrytą konsolę. (Więc może odpowiedź Anthony'ego na ShowConsoleWindow jest lepsza, gdy już napisałeś do konsoli, a moja odpowiedź jest lepsza, jeśli jeszcze nie napisałeś do konsoli). Ważne jest, aby pamiętać, że to nie działa:

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());
        }
    }
Kevin Holt
źródło
dziękuję za udostępnienie przykładowego kodu. Wypróbowałem to i stwierdziłem, że działa, ale z ograniczeniami. Nie ma przekierowania wyjścia konsoli (można to naprawić za pomocą dodatkowego kodu źródłowego). Ale główną wadą jest to, że natychmiast zwraca sterowanie do konsoli. Na przykład, kiedy wpisuję \bin\Debug>shareCheck.exe /once i naciskam Enter, pojawia się \bin\Debug>hello. It looks like you started me from an existing console.wiersz polecenia, a następnie konsola zaczyna wyświetlać: a po zakończeniu programu nie ma wiersza polecenia, więc ostatnia linia wyjściowa i pusty ekran jest trochę szalony
oleksa
Dziękuję Kevinowi za słowo ostrzeżenia - mam problem z podejściami sugerowanymi w tym poście SO i wydaje mi się, że otrzymuję "ukrytą konsolę", mimo że nie mam żadnych danych wyjściowych z konsoli wcześniej ... Okazało się, że Okno „Wyjście” w programie Visual Studio to ukryta konsola, jeśli Twoja aplikacja działa z dołączonym debugerem! Warto wspomnieć ... (więc mój program działał po przełączeniu na działanie bez debuggera, tj. Ctrl-F5)
Per Lundberg
3

Udało mi się napisać osobno aplikację konsolową, która zrobiła to, co chciałem, skompilować do exe, a następnie zrobić Process.Start("MyConsoleapp.exe","Arguments")

Ian
źródło
1
To jest wersja brzytwy Ockhama. Niewystarczające: P
Michael Hoffmann
3

Sprawdź ten kod źródłowy. Cały kod z komentarzem - służy do tworzenia konsoli w aplikacji Windows. Uncommented - aby ukryć konsolę w aplikacji konsoli. Od tutaj . (Wcześniej tutaj .) Projekt 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
    }
}
abatishchev
źródło
1

Właściwie AllocConsole z SetStdHandle w aplikacji GUI może być bezpieczniejszym podejściem. Problem z „przejmowaniem konsoli” już wspomnianym polega na tym, że konsola może w ogóle nie być oknem na pierwszym planie (szczególnie biorąc pod uwagę napływ nowych menedżerów okien w Vista / Windows 7).

EFraim
źródło
0

W wind32 aplikacje w trybie konsoli są zupełnie inną bestią niż zwykłe aplikacje odbierające kolejki wiadomości. Są deklarowane i kompilowane inaczej. Możesz utworzyć aplikację, która ma zarówno część konsoli, jak i normalne okno, i ukryć jedną lub drugą. Ale podejrzewaj, że okaże się, że cała sprawa wymaga nieco więcej pracy, niż myślałeś.

Joe Soul-bringer
źródło
Wind32 brzmi jak coś, co należy do forum meteorologicznego, ale jeśli masz na myśli Win32, to zwróć uwagę, że możemy tworzyć pętle wiadomości w programach konsolowych, jak w PostThreadMessage to Console Application .
user34660
0

Zgodnie z powyższym cytatem Jeffreya Knighta, gdy tylko napotkam sytuacje, w których potrzebuję wywołań API, aby coś zrobić, zatrzymuję się i zadaję sobie pytanie „czy zbytnio komplikuję?”.

Jeśli chcemy mieć jakiś kod i uruchomić go w trybie graficznego interfejsu użytkownika systemu Windows lub w trybie konsoli, rozważ przeniesienie kodu używanego w obu trybach do biblioteki DLL biblioteki kodu, a następnie posiadanie aplikacji Windows Forms korzystającej z tej biblioteki DLL i konsoli aplikacja korzystająca z tej biblioteki DLL (tj. jeśli w programie Visual Studio masz teraz rozwiązanie obejmujące trzy projekty: bibliotekę z większością kodu, interfejs GUI z samym kodem Win Forms i konsolę z samym kodem konsoli).

Jinlye
źródło
0

I jeszcze jedna spóźniona odpowiedź. Nie mogłem uzyskać żadnych danych wyjściowych do konsoli utworzonej AllocConsolezgodnie z wcześniejszymi sugestiami, więc zamiast tego zaczynam od aplikacji Console . Następnie, jeśli konsola nie jest potrzebna:

        [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
źródło