【问题标题】:No console output when using AllocConsole and target architecture x86使用 AllocConsole 和目标架构 x86 时没有控制台输出
【发布时间】:2013-03-14 07:07:59
【问题描述】:

我有一个 WinForms 项目,如果用户想要一个调试控制台,我分配一个带有 AllocConsole() 的控制台。

在目标架构设置为“任何 CPU”的情况下,所有控制台输出都可以正常工作,但是当我将其更改为“x86”时,它不会输出任何内容(Console.Read() 仍然可以按预期工作)。如果我直接打开 EXE,则输出有效。看起来 Visual Studio 将其重定向到它自己的“输出”窗口。

我也试过this的回答,但是没用,我也试过Console.SetOut(GetStdHandle(-11)),也没用。

将目标架构设置为“任何 CPU”对我来说是没有选择的。

所以这是我的两个问题:

  • 为什么只有目标架构设置为 x86 时才会出现这种情况?
  • 在 Visual Studio 中运行时如何输出到控制台?

【问题讨论】:

  • 在“输出”窗口中查找“第一次机会”异常。
  • @HansPassant:不,没有例外,但是您的评论帮助我找到了(部分)解决方案:当我直接打开 .exe 时,它​​可以工作,但是当我调试我的解决方案时,所有输出都会消失到VS中的“输出”视图。但是仍然存在两个问题:为什么只使用 x86 以及在调试解决方案时如何使控制台输出? (仍然为您 +1:D)

标签: c# .net pinvoke


【解决方案1】:

启用“启用本机代码调试”后,使用AllocConsole 创建的控制台的输出将被重定向到调试输出窗口。

这只发生在 x86 而不是 AnyCPU 的原因是您只能在 x86 应用程序中调试本机代码。

请注意,此行为仅发生在使用 AllocConsole 创建的控制台上。控制台应用程序的输出未重定向。

编辑:控制台不输出文本的另一个原因是您在调用 AllocConsole 之前已写入控制台。

不管是什么原因,如果它被重定向,这段代码将恢复输出,并在它无效的情况下重新打开控制台。它使用幻数7,这就是stdout 的句柄通常等于。

using System;
using System.IO;
using System.Runtime.InteropServices;

public static class ConsoleHelper
{
    public static void CreateConsole()
    {
        AllocConsole();

        // stdout's handle seems to always be equal to 7
        IntPtr defaultStdout = new IntPtr(7);
        IntPtr currentStdout = GetStdHandle(StdOutputHandle);

        if (currentStdout != defaultStdout)
            // reset stdout
            SetStdHandle(StdOutputHandle, defaultStdout);

        // reopen stdout
        TextWriter writer = new StreamWriter(Console.OpenStandardOutput()) 
        { AutoFlush = true };
        Console.SetOut(writer);
    }

    // P/Invoke required:
    private const UInt32 StdOutputHandle = 0xFFFFFFF5;
    [DllImport("kernel32.dll")]
    private static extern IntPtr GetStdHandle(UInt32 nStdHandle);
    [DllImport("kernel32.dll")]
    private static extern void SetStdHandle(UInt32 nStdHandle, IntPtr handle);
    [DllImport("kernel32")]
    static extern bool AllocConsole();
}

请参阅How to detect if Console.In (stdin) has been redirected? 了解另一种检测控制台句柄是否已重定向的方法。

【讨论】:

  • 听起来不错,但奇怪的是我的项目属性中禁用了本机代码调试...
  • 感谢非常好的回答。我想补充一点,使用0x7 幻数作为默认粗壮句柄不是很安全,因为它不是真正的句柄,并且可能是一个未记录的功能。您最好使用CreateFile(L"CONOUT$") 来查找实际的控制台屏幕缓冲区并进行测试。
  • 在 C++ 中,完整的指令是CreateFile(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);。将它移植到 C# 应该不会非常困难。
  • 注意:Windows Server 2012 R2 上的默认 STD 输出句柄不是 7。似乎值是 1228 (注意:我正在使用的这台服务器上也安装了 Citrix)。如果我强制 7,什么都不会显示。
  • @tunafish24,在调用AllocConsole 之前,通过SetStdHandle 将感兴趣的句柄重置为NULL。之后不要拨打SetStdHandle。无需对句柄值做任何假设。在 Windows 8+ 中,它将不是传统的控制台伪句柄(即 3、7、11、15 等),而是 ConDrv 设备上文件的内核句柄(例如“\Device\ConDrv\Output” ) 的任意句柄值是 4 的倍数(例如 28、112、448)。
【解决方案2】:

之前的答案都不适用于 VS2017 和 Windows 10(例如,如果在调试模式下启动应用程序,它们会失败)。

您可以在下面找到一些增强的代码。想法是一样的,但是去掉了幻数(Ceztko 已经提到过)并且所有必要的输入/输出流都被初始化了。

如果创建一个新控制台(alwaysCreateNewConsole = true),此代码对我有用。

附加到父进程的控制台 (alwaysCreateNewConsole = false) 有几个缺点。例如,我无法完全模仿从 cmd 启动的控制台应用程序的行为。而且我不确定这是否可能。

最重要的是:在修改Console class 之后,我重新考虑了将 Console 类与手动创建的控制台一起使用的总体思路。它在大多数情况下都能正常工作(我希望如此),但将来会带来很多痛苦。

    static class WinConsole
    {
        static public void Initialize(bool alwaysCreateNewConsole = true)
        {
            bool consoleAttached = true;
            if (alwaysCreateNewConsole
                || (AttachConsole(ATTACH_PARRENT) == 0
                && Marshal.GetLastWin32Error() != ERROR_ACCESS_DENIED))
            {
                consoleAttached = AllocConsole() != 0;
            }

            if (consoleAttached)
            {
                InitializeOutStream();
                InitializeInStream();
            }
        }

        private static void InitializeOutStream()
        {
            var fs = CreateFileStream("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, FileAccess.Write);
            if (fs != null)
            {
                var writer = new StreamWriter(fs) { AutoFlush = true };
                Console.SetOut(writer);
                Console.SetError(writer);
            }
        }

        private static void InitializeInStream()
        {
            var fs = CreateFileStream("CONIN$", GENERIC_READ, FILE_SHARE_READ, FileAccess.Read);
            if (fs != null)
            {
                Console.SetIn(new StreamReader(fs));
            }
        }

        private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode,
                                FileAccess dotNetFileAccess)
        {
            var file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero), true);
            if (!file.IsInvalid)
            {
                var fs = new FileStream(file, dotNetFileAccess);
                return fs;
            }
            return null;
        }

        #region Win API Functions and Constants
        [DllImport("kernel32.dll",
            EntryPoint = "AllocConsole",
            SetLastError = true,
            CharSet = CharSet.Auto,
            CallingConvention = CallingConvention.StdCall)]
        private static extern int AllocConsole();

        [DllImport("kernel32.dll",
            EntryPoint = "AttachConsole",
            SetLastError = true,
            CharSet = CharSet.Auto,
            CallingConvention = CallingConvention.StdCall)]
        private static extern UInt32 AttachConsole(UInt32 dwProcessId);

        [DllImport("kernel32.dll",
            EntryPoint = "CreateFileW",
            SetLastError = true,
            CharSet = CharSet.Auto,
            CallingConvention = CallingConvention.StdCall)]
        private static extern IntPtr CreateFileW(
              string lpFileName,
              UInt32 dwDesiredAccess,
              UInt32 dwShareMode,
              IntPtr lpSecurityAttributes,
              UInt32 dwCreationDisposition,
              UInt32 dwFlagsAndAttributes,
              IntPtr hTemplateFile
            );

        private const UInt32 GENERIC_WRITE = 0x40000000;
        private const UInt32 GENERIC_READ = 0x80000000;
        private const UInt32 FILE_SHARE_READ = 0x00000001;
        private const UInt32 FILE_SHARE_WRITE = 0x00000002;
        private const UInt32 OPEN_EXISTING = 0x00000003;
        private const UInt32 FILE_ATTRIBUTE_NORMAL = 0x80;
        private const UInt32 ERROR_ACCESS_DENIED = 5;

        private const UInt32 ATTACH_PARRENT = 0xFFFFFFFF;

        #endregion
    }

【讨论】:

  • 在 VS2017(或禁用 vhost 的 VS2015)中不适合我。
  • 在 W10 VS2017/VS2019 上运行良好,但在 Windows 7 上运行时不起作用。
  • 适用于 W10 / VS2019 !! (即使 Console 方法是 WinApi 而不是 StdCall 并且如果大部分 EntryPoint 和 CharSet 在这里都没用......)
  • 谢谢,唯一对我有用的东西/VS 2019
  • 在 VS 2019 上运行良好。
【解决方案3】:

以下在 2015 年对我有用,其他答案都没有:

来源:https://social.msdn.microsoft.com/profile/dmitri567/?ws=usercard-mini

using System;   
using System.Windows.Forms;   
using System.Text;   
using System.IO;   
using System.Runtime.InteropServices;   
using Microsoft.Win32.SafeHandles;   

namespace WindowsApplication   
{   
    static class Program   
    {   
        [DllImport("kernel32.dll",   
            EntryPoint = "GetStdHandle",   
            SetLastError = true,   
            CharSet = CharSet.Auto,   
            CallingConvention = CallingConvention.StdCall)]   
        private static extern IntPtr GetStdHandle(int nStdHandle);   
        [DllImport("kernel32.dll",   
            EntryPoint = "AllocConsole",   
            SetLastError = true,   
            CharSet = CharSet.Auto,   
            CallingConvention = CallingConvention.StdCall)]   
        private static extern int AllocConsole();   
        private const int STD_OUTPUT_HANDLE = -11;   
        private const int MY_CODE_PAGE = 437;   

        static void Main(string[] args)   
        {   
            Console.WriteLine("This text you can see in debug output window.");   

            AllocConsole();   
            IntPtr stdHandle=GetStdHandle(STD_OUTPUT_HANDLE);   
            SafeFileHandle safeFileHandle = new SafeFileHandle(stdHandle, true);   
            FileStream fileStream = new FileStream(safeFileHandle, FileAccess.Write);   
            Encoding encoding = System.Text.Encoding.GetEncoding(MY_CODE_PAGE);   
            StreamWriter standardOutput = new StreamWriter(fileStream, encoding);   
            standardOutput.AutoFlush = true;   
            Console.SetOut(standardOutput);   

            Console.WriteLine("This text you can see in console window.");   

            MessageBox.Show("Now I'm happy!");   
        }   
    }   
}  

【讨论】:

  • 在 VS2017 中对我不起作用-调试,anycpu(调试窗口的两个消息)
【解决方案4】:

我也有这个问题。每次我尝试调试我的应用程序时,控制台都是空白的。奇怪的是,在没有调试器的情况下启动 exe 工作正常。

我发现我必须从项目的Debug 菜单中选择Enable the Visual Studio hosting process

Stephen 是正确的,Enable native code debugging 确实将控制台重定向到输出窗口。但是,无论本机代码调试设置如何,在启用 Visual Studio 托管进程之前,我在任何地方都绝对看不到任何输出。

这可能是仅仅禁用本机代码调试并不能解决您的问题的原因。

【讨论】:

  • 此选项已在 Visual Studio 2017 中删除。
【解决方案5】:

只是想从 Visual Studio 开发人员社区发布答案。 https://developercommunity.visualstudio.com/content/problem/12166/console-output-is-gone-in-vs2017-works-fine-when-d.html 转到此链接并查看 Ramkumar Ramesh 的答案。 我已经在 VS 2017 中测试了这段代码。 我花了一天时间找到这个答案。希望对你也有帮助。

编辑—— 正如迈克建议的那样,包括一些描述。我想建议对 Zuniar 答案进行一些更正。他使用 VS 2015 进行了测试。但这在 VS 2017 中不起作用。 请使用 kernel32.dll 中的 CreateFile 引用而不是 GetStdHandle

IntPtr stdHandle = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, 0, 
OPEN_EXISTING, 0, 0);

在添加上述代码之前,请声明

[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFile(string lpFileName, uint 
dwDesiredAccess, uint dwShareMode, uint lpSecurityAttributes, uint 
dwCreationDisposition, uint dwFlagsAndAttributes, uint hTemplateFile);

private const int MY_CODE_PAGE = 437;
private const uint GENERIC_WRITE = 0x40000000;
private const uint FILE_SHARE_WRITE = 0x2;        
private const uint OPEN_EXISTING = 0x3;

我已从给定链接中获取此代码。

【讨论】:

  • 您应该在此处包含对答案的描述,以防链接内容发生变化。
猜你喜欢
  • 2016-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-20
  • 2011-12-01
  • 1970-01-01
  • 2011-06-03
相关资源
最近更新 更多