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

Answers:


77

您想要做的事不可能是理智的。还有一个类似的问题,请看一下答案

然后,还有一种由杰弗里·奈特Jeffrey Knight)编写的疯狂方法可在此处下载站点-进行备份):

问题:如何创建可以在GUI(Windows)模式或命令行/控制台模式下运行的应用程序?

从表面上看,这似乎很容易:创建一个Console应用程序,向其中添加Windows窗体,然后就可以运行了。但是,有一个问题:

问题:如果以GUI模式运行,最终将在后台隐藏一个窗口和一个讨厌的控制台,而您将无法隐藏它。

人们似乎想要的是一个真正的两栖应用程序,它可以在任何一种模式下都能平稳运行。

如果将其分解,实际上这里有四个用例:

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.

我正在发布代码来执行此操作,但需要注意。

实际上,我认为这种方法会使您遇到很多麻烦,而不是值得的。例如,您将必须具有两个不同的UI-一个用于GUI,一个用于命令/ shell。您将必须构建一些奇怪的中央逻辑引擎,该引擎从GUI与命令行进行抽象,这只会变得很奇怪。如果是我,我会退后一步,思考一下如何在实践中使用它,以及是否值得进行这种模式切换。因此,除非有特殊情况需要,否则我不会自己使用该代码,因为一旦遇到需要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具有讽刺意味,并且它想如何为其所有API创建C#接口,但是没有C#方式可以执行如此简单的任务。
Ramon Zarazua B.

3
不用依赖控制台作为前台窗口,您可以使用winapi获取当前进程的父进程ID:stackoverflow.com/a/3346055/855432
ghord

2
截至截稿时,文章的备份副本可以在这里找到web.archive.org/web/20111227234507/http://www.rootsilver.com/...
安德鲁Savinykh

2
嗨!我发现,如果我从Far等nc外壳程序运行此解决方案,它将创建新的控制台。如果我像cmd那样附加到Far Console,它会出错。我建议创建ConsoleApplication,如果需要GUI,则创建FreeConsole()。优秀的文章!谢谢!
Maxim Vasiliev

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

最后,我使用了提供的代码,但是添加了一个InitConsole()共享函数,如果handle是intptr.zero,则执行AllocConsole()部分。当我使用ShowConsoleWindow然后在其后立即在其上打印时,它不起作用。在应用程序启动时分配控制台,然后使用ShowConsoleWindow起作用。除此之外,这对我来说是完美的。谢谢..
Sage Pourpre

Console.WriteLine在从带有args的cmd开始的情况下未显示日志
Mohammad Ali

不要忘记using System.Runtime.InteropServices;
达伦·格里菲斯

19

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


1
应用程序->输出类型:控制台应用程序。为我做到了!
罗德韦克2014年

效果很好。希望它不会弄乱其他任何东西。谢谢,+ 1。
deathismyfriend 2015年

1
开始通过双击它的应用程序打开一个cmd窗口
穆罕默德·阿里

13

免责声明

有一种方法可以很简单地实现这一目标,但是我不建议您将这种方法用作让其他人看到的应用程序的好方法。但是,如果您需要一些开发人员同时显示控制台和Windows窗体,则可以轻松完成。

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

按原样提供;无法保证以后不会做任何可怕的事情,但是它确实起作用。

项目步骤

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

Main方法标记为[STAThread]

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

将Windows窗体添加到您的项目。

将标准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应用程序。
山姆·梅尔德鲁姆

好吧,恕我直言,达到相同效果的一种更简单的方法是照常创建Windows Forms Project,然后在Solution Explorer-> Properties中右键单击它,然后将Output Type更改为Console Application。(编辑:现在我已经意识到这基本上是ICR的答案)
kamilk

不错的分步解答
AminM 2013年

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会在您的某个位置自动创建一个隐藏的控制台。(因此,在您已经写入控制台后,也许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());
        }
    }

感谢您分享示例代码。我尝试过,发现它可以工作,但有局限性。没有控制台输出重定向(可以通过其他源代码进行修复)。但是主要的缺点是它将控制权立即返回到控制台。例如,当我键入\bin\Debug>shareCheck.exe /once 并按Enter时,将显示命令提示符,然后控制台开始输出:\bin\Debug>hello. It looks like you started me from an existing console.并且当程序结束时,没有命令提示符,因此最后的输出行和空白屏幕有点
让人

谢谢Kevin的警告-我在这篇SO帖子中建议的方法遇到了麻烦,即使我之前没有任何控制台输出,我似乎也得到了“隐藏的控制台” 。如果您的应用程序运行时附带了Debugger,则Visual Studio中的“输出”窗口是隐藏的控制台!值得一提的是...(因此,我的程序在切换为不运行调试器即Ctrl-F5时可以正常工作)
Per Lundberg

3

对我有用的是分别编写一个控制台应用程序,该程序执行了我想要的操作,将其编译为一个exe,然后执行 Process.Start("MyConsoleapp.exe","Arguments")


1
那是Occam的剃须刀版本。挑战性不够:P
Michael Hoffmann

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

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


0

在wind32中,控制台模式应用程序与通常的消息队列接收应用程序完全不同。它们的声明和编译方式不同。您可能会创建一个同时具有控制台部分和普通窗口的应用程序,并隐藏其中一个。但是怀疑您会发现整个事情比您想象的要多。


Wind32听起来像是属于某个气象论坛的东西,但是如果您是说Win32,那么请注意,我们可以在控制台程序中创建消息循环,如PostThreadMessage to Console Application
user34660 '19

0

根据上面的Jeffrey Knight引用,一旦我遇到需要API调用来完成某件事的情况,我就会停下来问自己“我是否使事情变得过于复杂了?”。

如果需要一些代码并在Windows GUI模式或控制台模式下运行,请考虑将两种模式下使用的代码移到代码库DLL中,然后拥有一个使用该DLL的Windows Forms应用程序和一个控制台使用该DLL的应用程序(即,如果在Visual Studio中,您现在有一个三个项目的解决方案:包含大量代码的库,仅包含Win Forms代码的GUI和仅包含您的控制台代码的Console。)


0

另一个迟来的答案。AllocConsole根据先前的建议,我无法获得使用创建的控制台的任何输出,因此我开始使用Console application。然后,如果不需要控制台:

        [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;
        }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.