Mostrar console no aplicativo do Windows?

85

Existe uma maneira de mostrar o console em um aplicativo do Windows?

Eu quero fazer algo assim:

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
fonte

Respostas:

77

O que você quer fazer não é possível de uma maneira sã. Houve uma pergunta semelhante, então veja as respostas .

Depois, há também uma abordagem insana (site inativo - backup disponível aqui ), escrita por Jeffrey Knight :

Pergunta: Como faço para criar um aplicativo que pode ser executado no modo GUI (Windows) ou no modo de linha de comando / console?

À primeira vista, parece fácil: você cria um aplicativo de console, adiciona um formulário do Windows a ele e está pronto para começar. No entanto, há um problema:

Problema: se você executar no modo GUI, terá uma janela e um console incômodo à espreita em segundo plano e não terá como ocultá-lo.

O que as pessoas parecem querer é um verdadeiro aplicativo anfíbio que funcione sem problemas em qualquer modo.

Se você analisar, existem na verdade quatro casos de uso aqui:

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.

Estou postando o código para fazer isso, mas com uma ressalva.

Na verdade, acho que esse tipo de abordagem causará muito mais problemas no futuro do que vale a pena. Por exemplo, você terá que ter duas interfaces de usuário diferentes - uma para a GUI e outra para o comando / shell. Você vai ter que construir algum mecanismo lógico central estranho que abstrai da GUI vs. linha de comando, e só vai ficar estranho. Se fosse eu, eu voltaria atrás e pensaria em como isso será usado na prática e se esse tipo de troca de modo vale o trabalho. Portanto, a menos que algum caso especial exigisse isso, eu não usaria esse código, porque assim que me deparo com situações em que preciso de chamadas de API para fazer algo, tenho a tendência de parar e me perguntar "estou complicando as coisas? "

Tipo de saída = aplicativo do 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
fonte
13
Acho irônico com a Microsoft e como ela deseja criar interfaces C # para todas as suas APIs, mas não há uma maneira C # de realizar uma tarefa tão simples.
Ramon Zarazua B.
3
Em vez de depender do console ser a janela de primeiro plano, você poderia obter a id do processo pai do processo atual usando winapi: stackoverflow.com/a/3346055/855432
ghord
2
No momento da redação deste artigo, uma cópia de backup do artigo está disponível aqui web.archive.org/web/20111227234507/http://www.rootsilver.com/…
Andrew Savinykh
2
Oi! Descobri que se executei esta solução do shell como Far, nc cria um novo console. Se eu anexar ao Far Console como cmd, ele funcionará errado. Eu recomendo criar ConsoleApplication e se a GUI for necessária, faça FreeConsole (); Excelente artigo! Obrigado!
Maxim Vasiliev
6
Eu recomendo chamar AttachConsolecom -1(o valor da constante API ATTACH_PARENT_PROCESS) em vez de esperar que a janela de primeiro plano seja a janela de comando certa para gravar.
Jon Hanna de
70

Isso é um pouco velho (OK, é MUITO velho), mas estou fazendo exatamente a mesma coisa agora. Aqui está uma solução muito simples que está funcionando para mim:

[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
fonte
5
se você estiver executando isso em uma janela cmd, isso abrirá outra janela do console, o que não é desejável no processo automatizado que precisará capturar a saída do console.
AaA
No meu fim, usei o código fornecido, mas adicionei uma função compartilhada InitConsole () para fazer a parte AllocConsole () se o identificador for intptr.zero. Quando usei ShowConsoleWindow e imprimi imediatamente depois, não funcionou. Alocar o console quando o aplicativo é iniciado, em seguida, usando o ShowConsoleWindow funcionou. Fora isso, é perfeito para mim. Obrigado ..
Sage Pourpre
Console.WriteLinelogs não estão aparecendo no caso iniciado em cmd com args
Mohammad Ali
Não se esqueçausing System.Runtime.InteropServices;
Darren Griffith
19

A maneira mais fácil é iniciar um aplicativo WinForms, ir para as configurações e alterar o tipo para um aplicativo de console.

ICR
fonte
1
Aplicativo -> tipo de saída: Aplicativo de console. Fez por mim!
Lodewijk,
Isso funcionou muito bem. Esperançosamente, não bagunce mais nada. Obrigado e +1.
deathismyfriend
1
Iniciar o aplicativo clicando duas vezes nele abre uma janela cmd
Mohammad Ali
13

aviso Legal

Existe uma maneira de fazer isso que é bastante simples, mas eu não sugeriria que seja uma boa abordagem para um aplicativo que você vai permitir que outras pessoas vejam. Mas se você tiver algum desenvolvedor que precise mostrar o console e os formulários do Windows ao mesmo tempo, isso pode ser feito facilmente.

Este método também suporta a exibição apenas da janela do Console, mas não oferece suporte à exibição apenas do Windows Form - ou seja, o Console sempre será mostrado. Você só pode interagir (ou seja, receber dados - Console.ReadLine(), Console.Read()) com a janela do console se não mostrar os formulários da janela; saída para o console - Console.WriteLine()- funciona em ambos os modos.

Isso é fornecido como está; não há garantias de que isso não fará algo horrível mais tarde, mas funciona.

Etapas do projeto

Comece com um aplicativo de console padrão .

Marque o Mainmétodo como[STAThread]

Adicione uma referência em seu projeto para System.Windows.Forms

Adicione um Windows Form ao seu projeto.

Adicione o código de inicialização padrão do Windows ao seu Mainmétodo:

Resultado final

Você terá um aplicativo que mostra o Console e, opcionalmente, os formulários do Windows.

Código de amostra

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
fonte
bom código aqui. mas como você desativa a exibição do console se quiser vender ou implantar ou algo mais. ???
r4ccoon de
@ r4ccoon - você não pode. Mas você pode mover facilmente todo o seu código para um aplicativo normal do Windows.
Sam Meldrum
Bem, IMHO, uma maneira mais simples de obter o mesmo efeito é criar um Projeto do Windows Forms como de costume, então clicar com o botão direito no Gerenciador de Soluções -> Propriedades e alterar o Tipo de Saída para Aplicativo de Console. (editar: agora percebi que é basicamente a resposta do ICR)
Kamilk
9

Ressuscitando um tópico muito antigo mais uma vez, já que nenhuma das respostas aqui funcionou muito bem para mim.

Eu encontrei uma maneira simples que parece bastante robusta e simples. Funcionou para mim A ideia:

  • Compile seu projeto como um aplicativo Windows. Pode haver um console pai quando o executável é iniciado, mas talvez não. O objetivo é reutilizar o console existente, se houver, ou criar um novo, caso não exista.
  • AttachConsole (-1) irá procurar o console do processo pai. Se houver um, ele se conecta a ele e você está acabado. (Eu tentei isso e funcionou corretamente ao chamar meu aplicativo do cmd)
  • Se AttachConsole retornou falso, não há console pai. Crie um com AllocConsole.

Exemplo:

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

Uma palavra de cautela: parece que se você tentar gravar no console antes de anexar ou alocar um console, essa abordagem não funciona. Meu palpite é que a primeira vez que você chamar Console.Write / WriteLine, se ainda não houver um console, o Windows criará automaticamente um console oculto em algum lugar para você. (Portanto, talvez a resposta ShowConsoleWindow de Anthony seja melhor depois de você já ter escrito no console, e minha resposta é melhor se você ainda não escreveu no console). O importante a notar é que isso não 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
fonte
obrigado por compartilhar o código de amostra. Eu tentei e descobri que funciona, mas com limitações. Não há redirecionamento de saída do console (pode ser corrigido por código-fonte adicional). Mas a principal desvantagem é que ele retorna o controle imediatamente para o console. Por exemplo, quando eu digito \bin\Debug>shareCheck.exe /once e pressiono Enter, o prompt de comando é mostrado e o console começa a produzir: \bin\Debug>hello. It looks like you started me from an existing console.e quando o programa termina não há prompt de comando, então a última linha de saída e a tela em branco que é um pouco maluca
oleksa
Obrigado Kevin pela palavra de cautela - estou tendo problemas com as abordagens sugeridas neste post SO e parece que estou obtendo o "console oculto", embora não tenha nenhuma saída de console acontecendo antes ... Acontece que a janela "Saída" no Visual Studio é o console oculto, se seu aplicativo estiver sendo executado com o Debugger conectado! Vale a pena mencionar ... (então, meu programa funcionou ao alternar para executar sem depurador, ou seja, Ctrl-F5)
Por Lundberg
3

O que funcionou para mim foi escrever um aplicativo de console separadamente que fizesse o que eu queria, compilá-lo em um exe e depois fazer Process.Start("MyConsoleapp.exe","Arguments")

Ian
fonte
1
Essa é a versão da navalha de Occam. Não é desafiador o suficiente: P
Michael Hoffmann
3

Verifique este código-fonte. Todo o código comentado - usado para criar um console em um aplicativo do Windows. Não comentado - para ocultar o console em um aplicativo de console. A partir daqui . (Anteriormente aqui .) Projeto 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
fonte
1

Na verdade, AllocConsole com SetStdHandle em um aplicativo GUI pode ser uma abordagem mais segura. O problema com o "sequestro de console" já mencionado, é que o console pode não ser uma janela em primeiro plano, (especialmente considerando o influxo de novos gerenciadores de janela no Vista / Windows 7) entre outras coisas.

EFraim
fonte
0

No wind32, os aplicativos no modo console são completamente diferentes dos aplicativos usuais de recebimento de mensagens. Eles são declarados e compilados de maneira diferente. Você pode criar um aplicativo que tenha uma parte do console e uma janela normal e ocultar uma ou outra. Mas suspeite que você achará a coisa toda um pouco mais de trabalho do que você pensava.

Joe Soul-bringer
fonte
Wind32 soa como algo que pertence a um fórum de meteorologia, mas se você quer dizer Win32, então observe que podemos criar loops de mensagem em programas de console, como em PostThreadMessage to Console Application .
user34660
0

De acordo com a citação de Jeffrey Knight acima, assim que me deparo com situações em que preciso de chamadas de API para fazer algo, tenho a tendência de parar e me perguntar "estou complicando demais as coisas?".

Se o que se deseja é ter algum código e executá-lo no modo Windows GUI ou no modo Console, considere mover o código usado em ambos os modos para uma biblioteca de código DLL e, em seguida, ter um aplicativo Windows Forms que usa essa DLL e um Console aplicativo que usa essa DLL (ou seja, se no Visual Studio você agora tem uma solução de três projetos: biblioteca com a maior parte do código, GUI apenas com o código do Win Forms e Console apenas com o código do console).

Jinlye
fonte
0

E ainda outra resposta tardia. Não consegui obter nenhuma saída para o console criado AllocConsoleconforme as sugestões anteriores, então, em vez disso, estou começando com o aplicativo Console . Então, se o console não for necessário:

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