C# 如何在窗体应用程序中显示控制台输出/窗口?

C# 如何在窗体应用程序中显示控制台输出/窗口?,c#,winforms,compilation,console-application,C#,Winforms,Compilation,Console Application,要立即陷入困境,有一个非常基本的例子: using System; using System.Windows.Forms; class test { static void Main() { Console.WriteLine("test"); MessageBox.Show("test"); } } 如果我使用默认选项(在命令行中使用csc)编译它,正如预期的那样,它将编译为控制台应用程序。另外,由于我导入了System.Wind

要立即陷入困境,有一个非常基本的例子:

using System;
using System.Windows.Forms;

class test
{ 
    static void Main()
    { 
        Console.WriteLine("test");
        MessageBox.Show("test");
    }
}
如果我使用默认选项(在命令行中使用csc)编译它,正如预期的那样,它将编译为控制台应用程序。另外,由于我导入了
System.Windows.Forms
,它还将显示一个消息框

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

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


因此,我的问题是-我知道您可以从控制台应用程序中输出“windows”/“forms”,但是否仍然可以从windows应用程序中显示控制台?

您可以使用pinvoke调用
AttachConsole
以获取连接到WinForms项目的控制台窗口:

您可能还需要考虑Log4NET:()用于配置不同配置中的日志输出。

< P>应该工作。

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

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

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


请记住在部署程序之前将其关闭。

这里基本上有两种情况

控制台输出 winforms程序可以将自身附加到创建它的控制台窗口(或者附加到其他控制台窗口,或者如果需要,附加到新的控制台窗口)。一旦连接到console窗口console.WriteLine()等,它就会按预期工作。这种方法的一个缺陷是,程序立即将控制返回控制台窗口,然后继续写入,因此用户也可以在控制台窗口中输入。我认为可以使用start with/wait参数来处理这个问题

重定向控制台输出 这是指有人将您程序的输出传输到其他地方,例如

yourapp>file.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);
    }
}

这对我来说很有效,将输出通过管道传输到一个文件。 使用以下命令调用控制台:

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;
            }
        }
    }
[DllImport(“kernel32.dll”)]
静态外部布尔附件控制台(UInt32 dwProcessId);
[DllImport(“kernel32.dll”)]
私有静态外部bool GetFileInformationByHandle(
安全文件句柄,
out BY\处理\文件\信息lpFileInformation
);
[DllImport(“kernel32.dll”)]
私有静态外部安全文件句柄GetStdHandle(UInt32-nStdHandle);
[DllImport(“kernel32.dll”)]
专用静态外部bool SetStdHandle(UInt32 nStdHandle、SafeFileHandle hHandle);
[DllImport(“kernel32.dll”)]
私有静态外部布尔重复句柄(
IntPtr hSourceProcessHandle,
安全文件句柄hSourceHandle,
IntPtr HTargetProcess句柄,
out SafeFileHandle lpTargetHandle,
UInt32 DWD期望访问,
布尔二进制句柄,
UInt32 DWO选项
);
私有const UInt32连接父进程=0xFFFFFFFF;
私有常量UInt32标准输出句柄=0xFFFFF5;
私有const UInt32标准错误句柄=0xFFFFF4;
私人建筑32重复\u相同\u访问=2;
struct BY\u HANDLE\u FILE\u信息
{
公共UInt32文件属性;
public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
公共UInt32卷序号;
公共UInt32文件大小高;
公开UInt32文件;
公共UInt32个链接;
公共UInt32文件索引高;
公共UInt32 FileIndexLow;
}
静态void InitConsoleHandles()
{
安全文件句柄hStdOut、hStdErr、hStdOutDup、hStdErrDup;
由bhfi处理文件信息;
hStdOut=GetStdHandle(标准输出句柄);
hStdErr=GetStdHandle(标准错误句柄);
//获取当前进程句柄
IntPtr hProcess=Process.GetCurrentProcess().Handle;
//复制标准输出句柄以保存初始值
重复句柄(hProcess、hStdOut、hProcess、out-hStdOutDup、,
0,为真,重复(相同访问);
//复制Stderr句柄以保存初始值
DuplicateHandle(hProcess、hStdErr、hProcess、out-hStdErrDup、,
0,为真,重复(相同访问);
//附加到控制台窗口–这可能会修改标准手柄
附加控制台(附加父进程);
//调整标准手柄
if(GetFileInformationByHandle(GetStdHandle(标准输出句柄),输出bhfi))
{
设置TDHandle(标准输出句柄,hStdOutDup);
}
其他的
{
设置TDHandle(标准输出句柄,hStdOut);
}
if(GetFileInformationByHandle(GetStdHandle(标准错误句柄),输出bhfi))
{
设置TDHandle(标准错误句柄,hStdErrDup);
}
其他的
{
设置TDHandle(标准错误句柄,hStdErr);
}
}
/// 
///主要的入口点
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));
    }
}
//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;
        }
    }
}
<PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0-windows10.0.17763.0</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <DisableWinExeOutputInference>true</DisableWinExeOutputInference>
    <Platforms>AnyCPU;x64;x86</Platforms>
</PropertyGroup>