在Windows应用程序中如何显示控制台?

93

有没有一种方法可以在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());
        }
    }
}
12个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
78
你想做的事情没有一个合理的方式。有一个类似的问题所以看看答案。 然后还有一种疯狂的方法(网站已关闭 - 备份在这里),由Jeffrey Knight编写:

Question: How do I create an application that can run in either GUI (windows) mode or command line / console mode?

On the surface of it, this would seem easy: you create a Console application, add a windows form to it, and you're off and running. However, there's a problem:

Problem: If you run in GUI mode, you end up with both a window and a pesky console lurking in the background, and you don't have any way to hide it.

What people seem to want is a true amphibian application that can run smoothly in either mode.

If you break it down, there are actually four use cases here:

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.

I'm posting the code to do this, but with a caveat.

I actually think this sort of approach will run you into a lot more trouble down the road than it's worth. For example, you'll have to have two different UIs' -- one for the GUI and one for the command / shell. You're going to have to build some strange central logic engine that abstracts from GUI vs. command line, and it's just going to get weird. If it were me, I'd step back and think about how this will be used in practice, and whether this sort of mode-switching is worth the work. Thus, unless some special case called for it, I wouldn't use this code myself, because as soon as I run into situations where I need API calls to get something done, I tend to stop and ask myself "am I overcomplicating things?".

Output type=Windows Application

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

15
我发现微软公司具有讽刺意味,因为它想为所有API创建C#接口,但是却没有用C#执行这样一个简单任务的方法。 - Ramon Zarazua B.
4
不必依赖于控制台成为前台窗口,可以使用winapi获取当前进程的父进程ID:https://dev59.com/questions/HnRC5IYBdhLWcg3wJNqf#3346055 - ghord
3
截至撰写本文时,该文章的备份副本可在此处获取:http://web.archive.org/web/20111227234507/http://www.rootsilver.com/2007/08/how-to-create-a-consolewindow - Andrew Savinykh
3
你好!我发现如果我像Far或nc那样从Shell运行此解决方案,它会创建新的控制台。如果我像cmd一样附加到Far Console上,它会出错。我建议创建ConsoleApplication,如果需要GUI,则使用FreeConsole()函数。文章写得非常好!谢谢! - Maxim Vasiliev
7
我建议调用AttachConsole并使用API常量ATTACH_PARENT_PROCESS的值 -1,而不是指望前台窗口是正确的命令窗口以便写入。 - Jon Hanna
显示剩余2条评论

76

这篇文章有点老(好吧,非常老),但我现在正在做完全相同的事情。这是一个非常简单的解决方案,对我很有效:

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

6
如果您在cmd窗口中运行此命令,它将打开另一个控制台窗口,这对于需要捕获控制台输出的自动化过程来说是不理想的。 - AaA
1
在我的端上,我使用了提供的代码,但添加了一个InitConsole()共享函数来执行AllocConsole()部分,如果句柄是intptr.zero。当我使用ShowConsoleWindow然后立即在其上打印时,它不起作用。在应用程序启动时分配控制台,然后使用ShowConsoleWindow确实有效。除此之外,这对我来说非常完美。谢谢。 - Sage Pourpre
1
在使用带参数的 cmd 启动程序时,Console.WriteLine 日志未显示。 - Mohammad Ali
1
不要忘记 using System.Runtime.InteropServices; - Darren Griffith

20

最简单的方法是启动一个WinForms应用程序,转到设置并将类型更改为控制台应用程序。


2
应用程序 -> 输出类型:控制台应用程序。已为您完成! - Lodewijk
1
那很好地运作了。希望它不会搞砸其他任何东西。谢谢并点赞。 - deathismyfriend
2
通过双击应用程序启动它会打开一个命令行窗口。 - Mohammad Ali

14

免责声明

实现此功能的方法相当简单,但我不建议在你要让其他人看到的应用程序中采用这种方法。但是,如果你需要展示控制台和窗体,则可以很容易地完成。

此方法还支持仅显示控制台窗口,但不支持仅显示窗体 - 即始终会显示控制台。如果您不显示窗体,则只能与控制台窗口进行交互(即接收数据 - Console.ReadLine()Console.Read());控制台输出 - Console.WriteLine() - 在两种模式下都有效。

本文所提供的代码是"按原样提供";不能保证以后不会出现问题,但它确实有效。

项目步骤

从标准的控制台应用程序开始。

Main方法标记为[STAThread]

在项目中添加对System.Windows.Forms的引用

向您的项目添加一个Windows 窗体

将标准的Windows启动代码添加到您的Main方法中:

最终结果

你将拥有一个显示控制台和可选窗体的应用程序。

示例代码

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

1
这里的代码很好。但是如果你想出售、部署或其他什么操作,如何禁用控制台的显示呢? - r4ccoon
1
@r4ccoon - 你不能这样做。但是你可以轻松地将所有代码移植到普通的Windows应用程序中。 - Sam Meldrum
2
在我看来,实现相同效果的一个更简单方法是通常创建一个Windows Forms项目,然后在解决方案资源管理器中右键单击->属性,并将输出类型更改为控制台应用程序。(编辑:现在我意识到这基本上就是ICR的答案) - kamilk

13

再次挖掘一个非常老的主题,因为这里的答案都不太适合我。

我找到了一种看起来非常稳健和简单的方法。它对我有效。思路如下:

  • 将项目编译为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会自动为您创建一个隐藏的控制台。(因此,也许Anthony的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());
        }
    }

2
感谢您分享示例代码。我已经尝试了它并且发现它可以工作,但是有一些限制。它没有控制台输出重定向(可以通过其他源代码修复)。但主要的缺点是它立即返回到控制台。例如,当我键入\bin\Debug>shareCheck.exe /once并按Enter时,命令提示符会显示,然后控制台开始输出:\bin\Debug>hello。看起来你从一个现有的控制台启动了我。当程序结束时,没有命令提示符,只有最后一个输出行和空屏幕,这有点疯狂。 - oleksa
3
感谢Kevin提醒。我遇到了麻烦,按照Stack Overflow网站上的建议进行操作后,即使在没有任何控制台输出的情况下,仍似乎出现了“隐藏的控制台”问题...原来,在Visual Studio的“输出”窗口中,如果你的应用程序正在使用调试器运行,那么它就是隐藏的控制台!值得一提的是...(所以,当我切换到不带调试器运行时,也就是Ctrl-F5时,我的程序就可以正常工作) - Per Lundberg

4

对我而言有效的方法是单独编写一个控制台应用程序来执行我所需的操作,将其编译为exe文件,然后使用Process.Start("MyConsoleapp.exe","Arguments")命令运行。


2
那是奥卡姆剃刀版本。不够有挑战性 :P - Michael Hoffmann

4
请查看这段源代码。注释的代码用于在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
    }
}

2

实际上,在GUI应用程序中使用AllocConsole和SetStdHandle可能是更安全的方法。已经提到过的“控制台劫持”的问题是,控制台可能根本不是前景窗口(特别是考虑到Vista / Windows 7中新窗口管理器的涌现),还有其他问题。


2
根据上面Jeffrey Knight的引述,每当我遇到需要API调用才能完成某些任务的情况时,我会停下来问自己“我是否过于复杂化了事情?”。 如果想要编写一些代码并在Windows GUI模式或控制台模式下运行它,请考虑将两种模式中使用的代码移动到一个代码库DLL中,然后有一个Windows Forms应用程序使用该DLL,以及一个控制台应用程序使用该DLL(即如果您在Visual Studio中现在有一个包含三个项目的解决方案:具有大部分代码的库,仅包含Win Forms代码的GUI和仅包含控制台代码的控制台)。

2

又是一篇迟到的回答。 按照之前的建议,我无法使用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;
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,