使用AllocConsole和目标体系结构x86时没有控制台输出

tea*_*441 22 .net c# pinvoke

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

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

我也试过这个答案,但它没有用,我也尝试过Console.SetOut(GetStdHandle(-11)),但也没用.

将目标体系结构设置为"任何CPU"对我来说是没有选择的.

所以这是我的两个问题:

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

小智 34

启用"启用本机代码调试"时,来自crated with的控制台的输出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();
}
Run Code Online (Sandbox Code Playgroud)

请参阅如何检测Console.In(stdin)是否已重定向?用于检测控制台句柄是否已重定向的另一种方法.

  • 谢谢你的回答非常好.我想补充一点,使用默认的粗壮句柄的"0x7"幻数不是很安全,因为它不是一个真正的句柄,可能它是一个没有文档的功能.你最好使用`CreateFile(L"CONOUT $")`来查找实际的控制台屏幕缓冲区并测试它. (3认同)
  • 在 C++ 中,完整的指令是 `CreateFile(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);`。将它移植到 C# 应该不是非常困难。 (2认同)
  • 注意:Windows Server 2012 R2上的默认STD输出句柄不是7.似乎值为1228(注意:我正在使用的服务器上也安装了Citrix).如果我强迫7,没有任何表现. (2认同)
  • @tunafish24,在调用“AllocConsole”之前通过“SetStdHandle”将感兴趣的句柄重置为“NULL”。之后不要调用`SetStdHandle`。无需对句柄值做出任何假设。在 Windows 8+ 中,它不会是旧版控制台伪句柄(即 3、7、11、15 等),而是 ConDrv 设备上文件的内核句柄(例如“\Device\ConDrv\Output” ),具有 4 倍数的任意句柄值(例如 28、112、448)。 (2认同)

Pav*_*o K 13

对于VS2017和Windows 10,我之前的答案都没有得到很好的解决(例如,如果在调试模式下启动应用程序,它们就失败了).

您可以在下面找到一些增强的代码.想法是一样的,但魔术数字被删除(Ceztko已经提到过),并且\ out流中的所有必要条件都被初始化.

如果创建一个新控制台(alwaysCreateNewConsole = true),此代码适用于我.

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

最重要的是:在修改Console类之后,我重新考虑了使用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
    }
Run Code Online (Sandbox Code Playgroud)

  • 在 W10 VS2017/VS2019 上运行良好,但在 Windows 7 上运行时不起作用。 (2认同)

dss*_*539 5

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

我发现我必须Enable the Visual Studio hosting process从项目的Debug菜单中进行。

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

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

  • 此选项已在 Visual Studio 2017 中删除。 (2认同)

Zun*_*air 5

以下是我在vs 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!");   
        }   
    }   
}  
Run Code Online (Sandbox Code Playgroud)