¿Mostrar consola en la aplicación de Windows?

85

¿Existe alguna forma de mostrar la consola en una aplicación de Windows?

Quiero hacer algo como esto:

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());
        }
    }
}
Plaza bursátil norteamericana
fuente

Respuestas:

77

Lo que quieres hacer no es posible de una manera cuerda. Hubo una pregunta similar, así que mire las respuestas .

Luego también hay un enfoque loco (sitio caído - copia de seguridad disponible aquí ) escrito por Jeffrey Knight :

Pregunta: ¿Cómo creo una aplicación que pueda ejecutarse en modo GUI (Windows) o en modo de línea de comandos / consola?

A primera vista, esto parecería fácil: creas una aplicación de consola, le agregas un formulario de Windows y ya estás listo. Sin embargo, hay un problema:

Problema: si ejecuta en modo GUI, terminará con una ventana y una consola molesta acechando en segundo plano, y no tiene ninguna forma de ocultarla.

Lo que la gente parece querer es una verdadera aplicación anfibia que pueda funcionar sin problemas en cualquier modo.

Si lo desglosa, en realidad hay cuatro casos de uso aquí:

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.

Estoy publicando el código para hacer esto, pero con una advertencia.

De hecho, creo que este tipo de enfoque te traerá muchos más problemas en el futuro de lo que vale la pena. Por ejemplo, deberá tener dos interfaces de usuario diferentes: una para la GUI y otra para el comando / shell. Tendrás que construir un extraño motor lógico central que se abstraiga de la GUI frente a la línea de comandos, y se pondrá extraño. Si fuera yo, daría un paso atrás y pensaría en cómo se usará en la práctica, y si este tipo de cambio de modo vale la pena el trabajo. Por lo tanto, a menos que un caso especial lo requiera, no usaría este código yo mismo, porque tan pronto como me encuentro con situaciones en las que necesito llamadas a la API para hacer algo, tiendo a detenerme y preguntarme "¿estoy complicando demasiado las cosas? ".

Tipo de salida = Aplicación de 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
fuente
13
Lo encuentro irónico con Microsoft y cómo quiere crear interfaces C # para todas sus API, pero no hay forma de C # para realizar una tarea tan simple.
Ramon Zarazua B.
3
En lugar de depender de que la consola sea la ventana de primer plano, puede obtener la identificación del proceso principal del proceso actual usando winapi: stackoverflow.com/a/3346055/855432
ghord
2
En el momento de escribir este artículo, la copia de seguridad del artículo está disponible aquí web.archive.org/web/20111227234507/http://www.rootsilver.com/…
Andrew Savinykh
2
¡Hola! Descubrí que si ejecuté esta solución desde el shell como Far, nc crea una nueva consola. Si me adjunto a la consola lejana como cmd, funciona mal. Recomiendo crear ConsoleApplication y, si se necesita GUI, haga FreeConsole (); Excelente articulo! ¡Gracias!
Maxim Vasiliev
6
Recomiendo llamar AttachConsolecon -1(el valor de la constante de la API ATTACH_PARENT_PROCESS) en lugar de esperar que la ventana de primer plano sea la ventana de comando correcta para escribir.
Jon Hanna
70

Esto es un poco viejo (OK, es MUY antiguo), pero estoy haciendo exactamente lo mismo en este momento. Aquí hay una solución muy simple que me funciona:

[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);
}
Antonio
fuente
5
si está ejecutando esto en una ventana de cmd, esto abrirá otra ventana de consola que no es deseable en un proceso automatizado que necesitará capturar la salida de la consola.
AaA
Por mi parte, utilicé el código proporcionado pero agregué una función compartida InitConsole () para hacer la parte de AllocConsole () si el identificador es intptr.zero. Cuando usé ShowConsoleWindow y luego lo imprimí inmediatamente después, no funcionó. Asignar la consola cuando se inicia la aplicación y luego usar ShowConsoleWindow funcionó. Aparte de eso, esto es perfecto para mí. Gracias ..
Sage Pourpre
Console.WriteLinelos registros no se muestran en el caso que comenzó desde cmd con args
Mohammad Ali
No lo olvidesusing System.Runtime.InteropServices;
Darren Griffith
19

La forma más sencilla es iniciar una aplicación WinForms, ir a la configuración y cambiar el tipo a una aplicación de consola.

ICR
fuente
1
Aplicación -> tipo de salida: Aplicación de consola. ¡Lo hice por mí!
Lodewijk
Eso funcionó muy bien. Ojalá no estropee nada más. Gracias y +1.
deathismyfriend
1
Al iniciar la aplicación haciendo doble clic en ella, se abre una ventana de cmd
Mohammad Ali
13

Descargo de responsabilidad

Hay una manera de lograr esto que es bastante simple, pero no sugeriría que sea un buen enfoque para una aplicación que vas a dejar que otras personas vean. Pero si algún desarrollador necesita mostrar la consola y los formularios de Windows al mismo tiempo, se puede hacer con bastante facilidad.

Este método también admite mostrar solo la ventana de la consola, pero no admite mostrar solo el formulario de Windows, es decir, la consola siempre se mostrará. Puede sólo interactúan (es decir, recibir datos - Console.ReadLine(), Console.Read()) con la ventana de la consola si no se presenta las formas de las ventanas; salida a la consola -Console.WriteLine() - funciona en ambos modos.

Esto se proporciona tal cual; no hay garantías de que esto no hará algo horrible más adelante, pero funciona.

Pasos del proyecto

Comience desde una aplicación de consola estándar .

Marque el Mainmétodo como[STAThread]

Agregue una referencia en su proyecto a System.Windows.Forms

Agregue un formulario de Windows a su proyecto.

Agregue el código de inicio estándar de Windows a su Mainmétodo:

Resultado final

Tendrá una aplicación que muestra la Consola y opcionalmente formularios de Windows.

Código de muestra

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
fuente
buen código aquí. pero ¿cómo deshabilita mostrar la consola si quiere vender o implementar o algo más?
r4ccoon
@ r4ccoon - no puedes. Pero puede mover fácilmente todo su código a una aplicación normal de Windows.
Sam Meldrum
Bueno, en mi humilde opinión, una forma más sencilla de lograr el mismo efecto es crear un proyecto de Windows Forms como de costumbre, luego hacer clic derecho en el Explorador de soluciones -> Propiedades, y cambiar el Tipo de salida a Aplicación de consola. (editar: ahora me he dado cuenta de que es básicamente la respuesta de ICR)
kamilk
Buena respuesta paso a paso
AminM
9

Resucitando un hilo muy antiguo una vez más, ya que ninguna de las respuestas aquí funcionó muy bien para mí.

Encontré una forma simple que parece bastante robusta y simple. Funcionó para mí. La idea:

  • Compile su proyecto como una aplicación de Windows. Es posible que haya una consola principal cuando se inicie el ejecutable, pero tal vez no. El objetivo es reutilizar la consola existente, si existe, o crear una nueva en caso contrario.
  • AttachConsole (-1) buscará la consola del proceso padre. Si hay uno, se adhiere a él y ya está. (Probé esto y funcionó correctamente al llamar a mi aplicación desde cmd)
  • Si AttachConsole devolvió falso, no hay una consola principal. Crea uno con AllocConsole.

Ejemplo:

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

Una advertencia: parece que si intenta escribir en la consola antes de adjuntar o asignar una consola, este enfoque no funciona. Supongo que es la primera vez que llama a Console.Write / WriteLine, si aún no hay una consola, Windows crea automáticamente una consola oculta en algún lugar para usted. (Entonces, tal vez la respuesta ShowConsoleWindow de Anthony sea mejor después de que ya haya escrito en la consola, y mi respuesta es mejor si aún no ha escrito en la consola). Lo importante a tener en cuenta es que esto no funciona:

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
fuente
gracias por compartir el código de muestra. Lo probé y descubrí que funciona, pero con limitaciones. No hay redirección de salida de la consola (se puede arreglar con código fuente adicional). Pero la principal desventaja es que devuelve el control inmediatamente a la consola. Por ejemplo, cuando \bin\Debug>shareCheck.exe /once\bin\Debug>hello. It looks like you started me from an existing console.
escribo
Gracias Kevin por la advertencia. Tengo problemas con los enfoques sugeridos en esta publicación SO y parece que estoy obteniendo la "consola oculta" aunque no tengo ninguna salida de consola antes ... Resultó que la ventana "Salida" en Visual Studio es la consola oculta, si su aplicación se ejecuta con el depurador adjunto. Vale la pena mencionar ... (entonces, mi programa funcionó al cambiar para ejecutarse sin depurador, es decir, Ctrl-F5)
Según Lundberg
3

Lo que funcionó para mí fue escribir una aplicación de consola por separado que hiciera lo que quería que hiciera, compilarla en un exe y luego hacer Process.Start("MyConsoleapp.exe","Arguments")

Ian
fuente
1
Esa es la versión navaja de Occam. No es lo suficientemente desafiante: P
Michael Hoffmann
3

Consulte este código fuente. Todo el código comentado: se utiliza para crear una consola en una aplicación de Windows. Sin comentarios: para ocultar la consola en una aplicación de consola. De aqui . (Anteriormente aquí .) Proyecto 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
fuente
1

En realidad, AllocConsole con SetStdHandle en una aplicación GUI podría ser un enfoque más seguro. El problema con el "secuestro de la consola" ya mencionado, es que la consola podría no ser una ventana en primer plano en absoluto, (especialmente considerando la afluencia de nuevos administradores de ventanas en Vista / Windows 7) entre otras cosas.

EFraim
fuente
0

En wind32, las aplicaciones en modo consola son una bestia completamente diferente de las aplicaciones habituales de recepción de mensajes en cola. Se declaran y compilan de forma diferente. Puede crear una aplicación que tenga tanto una parte de consola como una ventana normal y ocultar una u otra. Pero sospeche que encontrará todo un poco más trabajo de lo que pensaba.

Joe portador de almas
fuente
Wind32 suena como algo que pertenece a un foro de meteorología, pero si se refiere a Win32, tenga en cuenta que podemos crear bucles de mensajes en programas de consola, como en PostThreadMessage a la aplicación de consola .
user34660
0

Según la cita anterior de Jeffrey Knight, tan pronto como me encuentro con situaciones en las que necesito llamadas a la API para hacer algo, tiendo a detenerme y preguntarme "¿estoy complicando demasiado las cosas?".

Si lo que se desea es tener algo de código y ejecutarlo en el modo GUI de Windows o en el modo Consola, considere mover el código utilizado en ambos modos a una biblioteca de códigos DLL y luego tener una aplicación de Windows Forms que use esa DLL y una consola aplicación que usa esa DLL (es decir, si en Visual Studio ahora tiene una solución de tres proyectos: biblioteca con la mayor parte del código, GUI con solo el código de Win Forms y Consola con solo el código de su consola).

Jinlye
fuente
0

Y otra respuesta tardía. No pude obtener ningún resultado en la consola creada con AllocConsolelas sugerencias anteriores, así que en su lugar estoy comenzando con la aplicación Consola . Entonces, si la consola no es necesaria:

        [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
fuente