AttachConsole(-1), but Console.WriteLine won't

2019-03-08 20:29发布

问题:

If I have set my program to be a Windows Application, and used the AttachConsole(-1) API, how do I get Console.WriteLine to write to the console I launched the application from? It isn't working for me.

In case it is relevant, I'm using Windows 7 x64, and I have UAC enabled. Elevating doesn't seem to solve the problem though, nor does using start /wait.

Update

Some additional background that might help:

I've just discovered that if I go to the command prompt and type cmd /c MyProgram.exe, Then console output works. The same is true if I launch a command prompt, open a cmd.exe sub-process, and run the program from that sub-shell.

I've also tried logging out and back in, running from a cmd.exe launched from the start menu (as opposed to right-click -> command prompt), and running from a console2 instance. None of those work.

Background

I've read on other sites and in several SO answers that I can call the win32 API AttachConsole to bind my Windows Application to the console that ran my program, so I can have something that is "both a console application, and a Windows application".

For example, this question: Is it possible to log message to cmd.exe in C#/.Net?.

I've written a bunch of logic to make this work (using several other APIs), and I have gotten every other scenario to work (including redirection, which others have claimed won't work). The only scenario left is to get Console.WriteLine to write to the console I launched my program with. From everything I've read this is supposed to work if I use AttachConsole.

Repro

Here's a minimal sample - Note that the project is set to be a Windows Application:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;

class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        if (!AttachConsole(-1))
        {
            MessageBox.Show(
                new Win32Exception(Marshal.GetLastWin32Error())
                    .ToString()
                );
        }

        Console.WriteLine("Test");
    }

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    private static extern bool AttachConsole(int processId);
}
  • When I run this from a command prompt, I don't get an error, but I don't get any console output either. This is the problem
  • If I add extra message boxes anywhere in the execution flow of the app, the message box gets displayed. I expect this, so all good here.
  • When I run this from Visual Studio or by double clicking on it, a message box with an error is displayed. I expect this, so no worries here (will use AllocConsole in my real app).

If I call Marshal.GetLastWin32Error after the call to Console.WriteLine, I get the error "System.ComponentModel.Win32Exception (0x80004005): The handle is invalid". I suspect that attaching to the console is causing Console.Out to get messed up, but I'm not sure how to fix it.

回答1:

This is how I do it in Winforms. Using WPF would be similar.

static class SybilProgram
{
    [STAThread]
    static void Main(string[] args)
    {
        if (args.Length > 0)
        {
            // Command line given, display console
            if ( !AttachConsole(-1) )  // Attach to a parent process console
                AllocConsole(); // Alloc a new console if none available


            ConsoleMain(args);
        }
        else
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());  // instantiate the Form
        }
    }

    private static void ConsoleMain(string[] args)
    {
        Console.WriteLine("Command line = {0}", Environment.CommandLine);
        for (int ix = 0; ix < args.Length; ++ix)
            Console.WriteLine("Argument{0} = {1}", ix + 1, args[ix]);
        Console.ReadLine();
    }

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

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


回答2:

Had the same problem and it appears that when running cmd.exe in Administrator mode AttachConsole() call succeeds but Console.Write() and Console.WriteLine() don't work. If you run cmd.exe normally (non-admin) everything seems to work fine.



回答3:

Had the same problem. Everything worked great when I launched the built .exe file, but failed to run inside the VS.

Solution:

  1. Check Enable the VS Hosting process.
  2. Run VS as administrator.

Maybe this will help other people to fix this issue.



回答4:

I cannot see any significant difference between our implementations. For what it is worth, below is what I have in my application and it works fine. I also create a sample WPF application and it also worked fine.

I suspect that your issue is elsewhere. Sorry I couldn't be more help.

[STAThread]
public static void Main()
{            
    AttachProcessToConsole();    
}

private static void AttachProcessToConsole()
{
    AttachConsole(-1);
}

// Attaches the calling process to the console of the specified process.
// http://msdn.microsoft.com/en-us/library/ms681952%28v=vs.85%29.aspx
[DllImport("Kernel32.dll")]
private static extern bool AttachConsole(int processId);


回答5:

I had a similar situation: could not get a Windows Application to output anything in the programmatically attached console. Eventually, it turned out that I was using Console.WriteLine once before AttachConsole, and that was tampering with everything that followed after.



回答6:

+1

I had the same problem. Console output would not show up using various flavours of AllocConsole or AttachConsole.

Check if you have disabled Enable the visual studio hosting process in your project configuration. Enabling this option magically made all console messages appear as expected for me. I'm running VS2010 and .NET4, but this post suggests the 'feature' is still there in VS2012.



回答7:

I was suffering from the same problem with my application's current version (targeting .NET 4.0) but am sure AttachConsole(-1) did work as expected in earlier versions (which targeted .NET 2.0).

I found that I could get console output as soon as I removed my (custom) TraceListener from my application's .exe.config file, even though I don't know why yet.

Perhaps this is what's swallowing your console output as well...

Update

In fact I had a Console.WriteLine() in my custom trace listener's c'tor which was messing things up. After removing this line, console output after AttachConsole(-1) went back to normal.



回答8:

DETACHED_PROCESS and CREATE_NEW_CONSOLE is the problem here.
see https://stackoverflow.com/a/494000/883015



回答9:

Same problem here. I was using gflags.exe, (part of Debugging Tools for Windows) to attach a commandline WPF application to vsjitdebugger.exe (See this post). As long as my application was coupled with vsjitdebugger.exe, no output was written to the console.

From the moment I detached my application, output to the console, from where I launched my application, was restored.



回答10:

I had a similar problem. To compound things, I'm using WPF w/PRISM, so I need to suppress the creation of the 'Shell' as well when in "CLI Mode," but I digress...

This was the only thing that I found that finally worked

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool AttachConsole(int processId);

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    private static extern IntPtr GetStdHandle(int nStdHandle);

    public static void InitConsole()
    {
        const int STD_OUTPUT_HANDLE = -11;

        AttachConsole(-1);

        var stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
        var safeFileHandle = new SafeFileHandle(stdHandle, true);
        var fileStream = new FileStream(safeFileHandle, FileAccess.Write);
        var standardOutput = new StreamWriter(fileStream) { AutoFlush = true };
        Console.SetOut(standardOutput);
        Console.WriteLine();
        Console.WriteLine("As seen on StackOverflow!");
    }

All calls to Console.WriteLine() output to the CLI window after the call to InitConsole().

Hope this helps.