如何在表单应用程序中显示控制台输出/窗口?

Posted

技术标签:

【中文标题】如何在表单应用程序中显示控制台输出/窗口?【英文标题】:How do I show a console output/window in a forms application? 【发布时间】:2011-05-20 17:06:14 【问题描述】:

为了直接陷入困境,一个非常基本的例子:

using System;
using System.Windows.Forms;

class test
 
    static void Main()
     
        Console.WriteLine("test");
        MessageBox.Show("test");
    

如果我使用默认选项(在命令行使用 csc)编译它,如预期的那样,它将编译为控制台应用程序。另外,因为我导入了System.Windows.Forms,所以也会显示一个消息框。

现在,如果我使用选项 /target:winexe,我认为这与从项目选项中选择 Windows Application 相同,正如预期的那样,我只会看到消息框而没有控制台输出。

(事实上,从命令行启动它的那一刻,我可以在应用程序完成之前发出下一个命令。

所以,我的问题是 - 我知道您可以从控制台应用程序中获得“windows”/forms 输出,但是无论如何可以从 Windows 应用程序中显示控制台?

【问题讨论】:

您认为两者之间的区别是什么?为什么不直接编译为控制台并显示一个表单。 @Doggett,简单 - 我正在学习并想了解为什么/如何去做,即使我最终没有在实际应用程序中使用它......目前,我在想提供额外命令/输出的选项,例如在 VLC 中,但是 TBH,我不需要它 - 再次,只是学习并想理解它! 我使用本教程完成了这一点:saezndaree.wordpress.com/2009/03/29/… 【参考方案1】:

这个应该可以的。

using System.Runtime.InteropServices;

private void Form1_Load(object sender, EventArgs e)

    AllocConsole();


[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AllocConsole();

【讨论】:

太棒了,这个问题似乎被问了很多,这是我能找到的问题的唯一实际答案,+1 主要问题:当你关闭它时,所有应用程序都会关闭。 我在 Windows 8 和 Windows 10 上进行了测试: - AttachConsole 在 cmd 框中工作 - AllocConsole 在 Visual Studio 中工作。当需要 alloc 时,AttachConsole 返回 false。您还应该在以控制台模式终止应用程序之前调用 FreeConsole()。在我的程序中,我使用了 Matthew Strawbridge 的代码(见下文),将 AttachConsole() 行修改为: if (!AttachConsole(-1)) AllocConsole(); 这不是很好,当从命令行运行时它会弹出一个 separate 控制台窗口,当从命令行运行并尝试使用> 重定向时输出我得到一个单独的控制台窗口和我的文件中的零输出。 如何在这个控制台打印消息? Console.Writeline() 不在控制台中打印任何内容。【参考方案2】:

也许这太简单了……

创建一个 Windows 窗体项目...

然后:项目属性 -> 应用程序 -> 输出类型 -> 控制台应用程序

然后可以让控制台和表单一起运行,对我有用

【讨论】:

看起来最简单,也解决了我的问题。 这绝对是最好的解决方案!其他人很聪明,但方式很复杂 简单且工作正常。这应该是公认的答案。 虽然,是的,从技术上讲,这可用于允许发布者要求的内容 - 这不是一个很好的解决方案。通过这样做,如果您随后使用 GUI 启动您的 winforms 应用程序 - 您还将打开一个控制台窗口。在这种情况下,您需要更像 Mike de Klerk 的答案。 这是我能够让我的 Winforms 应用程序在从命令行运行时将输出写入控制台,或者在使用> 在命令行上重定向时写入文件的唯一解决方案.但是,我希望有一个解决方案可以解释如何仅在某些时候作为“控制台应用程序”运行(即以编程方式启用更改这个神秘的 Visual Studio 设置所做的任何事情)。有谁知道这在幕后是如何工作的?【参考方案3】:

如果您不担心打开控制台命令,您可以进入项目的属性并将其更改为控制台应用程序

这仍然会显示您的表单以及弹出一个控制台窗口。您无法关闭控制台窗口,但它可以作为一个出色的调试临时记录器。

只需记住在部署程序之前将其关闭即可。

【讨论】:

不错。这解决了我的表单应用程序的问题,即我需要能够输出到控制台窗口,同时支持将输出重定向到文件。而且我不需要手动附加任何控制台... @JasonHarrison 如果关闭控制台窗口,程序将关闭。此外,程序运行时窗口始终处于打开状态。 @gun2171:谢谢。答案中指出了这种方法的缺点:如果通过双击、开始菜单等启动应用程序,则会出现控制台窗口。 有什么方法可以跟踪控制台关闭事件? 这不适用于 .Net Core 5。我只测试了 .Net Core 的单一版本。除了不显示控制台窗口之外,它甚至导致 Visual Studio 2019 在我保存它并尝试打开表单设计选项卡后完全冻结。【参考方案4】:

您可以使用 pinvoke 调用 AttachConsole 以获取附加到 WinForms 项目的控制台窗口:http://www.csharp411.com/console-output-from-winforms-application/

您可能还需要考虑使用 Log4net (http://logging.apache.org/log4net/index.html) 在不同配置中配置日志输出。

【讨论】:

+1 - 哇,我希望有一个 console.show 或类似的!比我想象的要复杂得多!我会暂时保持开放,以防万一有更好/更简单的答案。 这对我有用,AllocConsole() 没有,因为它产生了一个新的控制台窗口(没有深入研究 AllocConsole,也许我错过了那里的东西)。【参考方案5】:

创建一个 Windows 窗体应用程序,并将输出类型更改为控制台。

这将导致控制台和表单都打开。

【讨论】:

这正是我想要的。简单且不使用 WINAPI。 我尝试了很多例子,但没有一个产生符合我期望的结果。然而,这个解决方案正是我想要的,也是迄今为止最简单的解决方案。【参考方案6】:

这对我有用,可以将输出通过管道传输到文件。 使用

调用控制台

cmd /c "C:\path\to\your\application.exe" > myfile.txt

将此代码添加到您的应用程序中。

    [DllImport("kernel32.dll")]
    static extern bool AttachConsole(UInt32 dwProcessId);
    [DllImport("kernel32.dll")]
    private static extern bool GetFileInformationByHandle(
        SafeFileHandle hFile,
        out BY_HANDLE_FILE_INFORMATION lpFileInformation
        );
    [DllImport("kernel32.dll")]
    private static extern SafeFileHandle GetStdHandle(UInt32 nStdHandle);
    [DllImport("kernel32.dll")]
    private static extern bool SetStdHandle(UInt32 nStdHandle, SafeFileHandle hHandle);
    [DllImport("kernel32.dll")]
    private static extern bool DuplicateHandle(
        IntPtr hSourceProcessHandle,
        SafeFileHandle hSourceHandle,
        IntPtr hTargetProcessHandle,
        out SafeFileHandle lpTargetHandle,
        UInt32 dwDesiredAccess,
        Boolean bInheritHandle,
        UInt32 dwOptions
        );
    private const UInt32 ATTACH_PARENT_PROCESS = 0xFFFFFFFF;
    private const UInt32 STD_OUTPUT_HANDLE = 0xFFFFFFF5;
    private const UInt32 STD_ERROR_HANDLE = 0xFFFFFFF4;
    private const UInt32 DUPLICATE_SAME_ACCESS = 2;
    struct BY_HANDLE_FILE_INFORMATION
    
        public UInt32 FileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
        public UInt32 VolumeSerialNumber;
        public UInt32 FileSizeHigh;
        public UInt32 FileSizeLow;
        public UInt32 NumberOfLinks;
        public UInt32 FileIndexHigh;
        public UInt32 FileIndexLow;
    
    static void InitConsoleHandles()
    
        SafeFileHandle hStdOut, hStdErr, hStdOutDup, hStdErrDup;
        BY_HANDLE_FILE_INFORMATION bhfi;
        hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
        hStdErr = GetStdHandle(STD_ERROR_HANDLE);
        // Get current process handle
        IntPtr hProcess = Process.GetCurrentProcess().Handle;
        // Duplicate Stdout handle to save initial value
        DuplicateHandle(hProcess, hStdOut, hProcess, out hStdOutDup,
        0, true, DUPLICATE_SAME_ACCESS);
        // Duplicate Stderr handle to save initial value
        DuplicateHandle(hProcess, hStdErr, hProcess, out hStdErrDup,
        0, true, DUPLICATE_SAME_ACCESS);
        // Attach to console window – this may modify the standard handles
        AttachConsole(ATTACH_PARENT_PROCESS);
        // Adjust the standard handles
        if (GetFileInformationByHandle(GetStdHandle(STD_OUTPUT_HANDLE), out bhfi))
        
            SetStdHandle(STD_OUTPUT_HANDLE, hStdOutDup);
        
        else
        
            SetStdHandle(STD_OUTPUT_HANDLE, hStdOut);
        
        if (GetFileInformationByHandle(GetStdHandle(STD_ERROR_HANDLE), out bhfi))
        
            SetStdHandle(STD_ERROR_HANDLE, hStdErrDup);
        
        else
        
            SetStdHandle(STD_ERROR_HANDLE, hStdErr);
        
    

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    
        // initialize console handles
        InitConsoleHandles();

        if (args.Length != 0)
        

            if (args[0].Equals("waitfordebugger"))
            
                MessageBox.Show("Attach the debugger now");
            
            if (args[0].Equals("version"))
            
#if DEBUG
                String typeOfBuild = "d";
#else
                String typeOfBuild = "r";
#endif
                String output = typeOfBuild + Assembly.GetExecutingAssembly()
                    .GetName().Version.ToString();
                //Just for the fun of it
                Console.Write(output);
                Console.Beep(4000, 100);
                Console.Beep(2000, 100);
                Console.Beep(1000, 100);
                Console.Beep(8000, 100);
                return;
            
        
    

我在这里找到了这段代码:http://www.csharp411.com/console-output-from-winforms-application/ 我认为也值得在这里发布。

【讨论】:

这很好用,除了它现在在 Windows 8 和 Windows 10 中失败。失败的意思是除了和额外的提示之外没有输出(如果这是一个线索)。有人建议 AllocConsole 但只是闪现了一个 cmd 窗口. 还尝试了上面的 Chaz 的答案,但这在 Windows 7 中提供了一个新的控制台(尽管不在 8 或 10 中)。如果没有参数,我只需要在命令行上使用重定向运行或作为 gui 运行的选项。 我试过这个但没有用。仅使用AttachConsole(ATTACH_PARENT_PROCESS) 我得到了控制台输出,但在命令行上使用&gt; 将其重定向不起作用。当我尝试这个答案时,我无法在控制台或文件中获得任何输出。【参考方案7】:

这里基本上可以发生两件事。

控制台输出 winforms 程序可以将自身附加到创建它的控制台窗口(或附加到不同的控制台窗口,或者如果需要,甚至附加到新的控制台窗口)。一旦附加到控制台窗口 Console.WriteLine() 等按预期工作。这种方法的一个问题是程序立即将控制权返回给控制台窗口,然后继续对其进行写入,因此用户也可以在控制台窗口中键入。我认为您可以使用带有 /wait 参数的 start 来处理这个问题。

Link to start Command syntax

重定向控制台输出 这是当有人通过管道将您的程序的输出输出到其他地方时,例如。

你的应用程序 > 文件.txt

在这种情况下附加到控制台窗口会有效地忽略管道。要完成这项工作,您可以调用 Console.OpenStandardOutput() 来获取输出应通过管道传输到的流的句柄。这仅在输出是管道的情况下才有效,因此如果您想处理这两种情况,您需要打开标准输出并将其写入并附加到控制台窗口。这确实意味着输出被发送到控制台窗口和管道,但它是我能找到的最佳解决方案。在我用来执行此操作的代码下方。

// This always writes to the parent console window and also to a redirected stdout if there is one.
// It would be better to do the relevant thing (eg write to the redirected file if there is one, otherwise
// write to the console) but it doesn't seem possible.
public class GUIConsoleWriter : IConsoleWriter

    [System.Runtime.InteropServices.DllImport("kernel32.dll")]
    private static extern bool AttachConsole(int dwProcessId);

    private const int ATTACH_PARENT_PROCESS = -1;

    StreamWriter _stdOutWriter;

    // this must be called early in the program
    public GUIConsoleWriter()
    
        // this needs to happen before attachconsole.
        // If the output is not redirected we still get a valid stream but it doesn't appear to write anywhere
        // I guess it probably does write somewhere, but nowhere I can find out about
        var stdout = Console.OpenStandardOutput();
        _stdOutWriter = new StreamWriter(stdout);
        _stdOutWriter.AutoFlush = true;

        AttachConsole(ATTACH_PARENT_PROCESS);
    

    public void WriteLine(string line)
    
        _stdOutWriter.WriteLine(line);
        Console.WriteLine(line);
    

【讨论】:

我无法写入控制台;首先附加父进程就可以了。谢谢。 看来这个答案需要你重写所有对Console.WriteLine的调用,而不是调用上面定义的新WriteLine。即使我尝试在命令行上运行应用程序并使用 &gt; 重定向到文件时,我无法使用此代码将任何内容重定向到文件。 @uglycoyote,请确保您在应用程序中尽早构建 GUIConsoleWriter,否则由于神秘的 windows 类型原因,它将无法工作。我认为封装对Console.WriteLine 的调用只是一种好习惯,因为它允许您进行测试并轻松更改您登录的位置(例如,您可能希望开始登录到基于云的日志服务,如 PaperTrail ,或其他) 这对我来说在 Win10 中运行良好,甚至没有 StreamWriter _stdOutWriter; 管道是答案,但不是文件,而是使用更多,例如:yourapp |更多的 ;请参考***.com/a/13010823/1845672【参考方案8】:
//From your application set the Console to write to your RichTextkBox 
//object:
Console.SetOut(new RichTextBoxWriter(yourRichTextBox));

//To ensure that your RichTextBox object is scrolled down when its text is 
//changed add this event:
private void yourRichTextBox_TextChanged(object sender, EventArgs e)

    yourRichTextBox.SelectionStart = yourRichTextBox.Text.Length;
    yourRichTextBox.ScrollToCaret();


public delegate void StringArgReturningVoidDelegate(string text);
public class RichTextBoxWriter : TextWriter

    private readonly RichTextBox _richTextBox;
    public RichTextBoxWriter(RichTextBox richTexttbox)
    
        _richTextBox = richTexttbox;
    

    public override void Write(char value)
    
        SetText(value.ToString());
    

    public override void Write(string value)
    
        SetText(value);
    

    public override void WriteLine(char value)
    
        SetText(value + Environment.NewLine);
    

    public override void WriteLine(string value)
    
        SetText(value + Environment.NewLine);
    

    public override Encoding Encoding => Encoding.ASCII;

    //Write to your UI object in thread safe way:
    private void SetText(string text)
    
        // InvokeRequired required compares the thread ID of the  
        // calling thread to the thread ID of the creating thread.  
        // If these threads are different, it returns true.  
        if (_richTextBox.InvokeRequired)
        
            var d = new StringArgReturningVoidDelegate(SetText);
            _richTextBox.Invoke(d, text);
        
        else
        
            _richTextBox.Text += text;
        
    

【讨论】:

非常酷的解决方案。正是我想要的!【参考方案9】:

基于 Chaz 的回答,在 .NET 5 中有 a breaking change,因此需要在项目文件中进行两项修改,即更改 OutputType 并添加 DisableWinExeOutputInference。示例:

<PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0-windows10.0.17763.0</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <DisableWinExeOutputInference>true</DisableWinExeOutputInference>
    <Platforms>AnyCPU;x64;x86</Platforms>
</PropertyGroup>

【讨论】:

【参考方案10】:
using System;
using System.Runtime.InteropServices;

namespace SomeProject

    class GuiRedirect
    
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool AttachConsole(int dwProcessId);
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr GetStdHandle(StandardHandle nStdHandle);
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool SetStdHandle(StandardHandle nStdHandle, IntPtr handle);
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern FileType GetFileType(IntPtr handle);

    private enum StandardHandle : uint
    
        Input = unchecked((uint)-10),
        Output = unchecked((uint)-11),
        Error = unchecked((uint)-12)
    

    private enum FileType : uint
    
        Unknown = 0x0000,
        Disk = 0x0001,
        Char = 0x0002,
        Pipe = 0x0003
    

    private static bool IsRedirected(IntPtr handle)
    
        FileType fileType = GetFileType(handle);

        return (fileType == FileType.Disk) || (fileType == FileType.Pipe);
    

    public static void Redirect()
    
        if (IsRedirected(GetStdHandle(StandardHandle.Output)))
        
            var initialiseOut = Console.Out;
        

        bool errorRedirected = IsRedirected(GetStdHandle(StandardHandle.Error));
        if (errorRedirected)
        
            var initialiseError = Console.Error;
        

        AttachConsole(-1);

        if (!errorRedirected)
            SetStdHandle(StandardHandle.Error, GetStdHandle(StandardHandle.Output));
    

【讨论】:

在命令提示符下工作正常,但在“开始”>“运行”或在 Visual Studio 中不行。要使其在所有情况下都能正常工作,请将 AttachConsole 行替换为: if (!AttachConsole(-1)) AllocConsole();如果 AllocConsole() 被调用,FreeConsole() 也应该被调用,否则控制台主机在终止程序后继续运行。 initialiseOut 和 initialiseError 的预期用途是什么,因为它们没有被使用? StandardHandle : uint 在这里是错误的......应该是 IntPtr 在 x86 和 x64 上都可以工作【参考方案11】:

在项目属性中将输出类型设置为 Console 将为您提供一个控制台应用程序以及您创建的表单。

【讨论】:

【参考方案12】:

为什么不将其保留为 Window Forms 应用程序,并创建一个简单的表单来模仿控制台。可以使表单看起来像黑屏控制台,并让它直接响应按键。 然后,在 program.cs 文件中,您决定是否需要运行主窗体或 ConsoleForm。例如,我使用这种方法来捕获 program.cs 文件中的命令行参数。我创建了 ConsoleForm,最初将其隐藏,然后将命令行字符串传递给其中的 AddCommand 函数,该函数显示允许的命令。最后,如果用户给了 -h 或 -?命令,我在 ConsoleForm 上调用 .Show ,当用户点击它上的任何键时,我关闭了程序。如果用户不给 -?命令,我关闭隐藏的 ConsoleForm 并运行主窗体。

【讨论】:

我不确定这算不算一个问题。他给出了如何按照他的建议做的完整的一步一步,即使实际的代码会很好。【参考方案13】:

您可以随时在应用程序类型、控制台或窗口之间切换。因此,您不会编写特殊的逻辑来查看标准输出。此外,在调试器中运行应用程序时,您将在输出窗口中看到所有标准输出。您也可以只添加一个断点,并在断点属性更改“当命中...”时,您可以输出任何消息和变量。您也可以选中/取消选中“继续执行”,您的断点将变为方形。因此,断点消息不会在调试输出窗口中更改应用程序中的任何内容。

【讨论】:

以上是关于如何在表单应用程序中显示控制台输出/窗口?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 GUI 输出窗口而不是 Visual Studio 2015 中的 cmd 控制台上显示我的 C++ 程序输出?

delphi控制台程序输出中文乱码,如何解决?

项目输出类型未更改为控制台应用程序,控制台窗口未在 WinForms 中显示

React app无法登录到控制台或终端窗口

如何创建一个 C# 应用程序来决定自己是显示为控制台还是窗口应用程序?

在控制台窗口中输出颜色方阵