AttachConsole(-1),但Console.WriteLine无法输出到父级命令提示符?

26
如果我将程序设置为Windows应用程序,并使用AttachConsole(-1) API,那么如何使Console.WriteLine写入我启动应用程序的控制台?这对我不起作用。
如果有关系的话,我正在使用Windows 7 x64,并且已启用UAC。 升级似乎无法解决问题,使用start /wait也是如此。
更新
可能会有所帮助的其他背景:
我刚刚发现,如果我转到命令提示符并键入cmd /c MyProgram.exe然后控制台输出就可以工作了。 如果我启动命令提示符,打开一个cmd.exe子进程,并从该子shell运行程序,则情况也是如此。
我还尝试过注销并重新登录,从开始菜单启动的cmd.exe运行(而不是右键单击->命令提示符),以及从a console2 instance运行。 这些都不起作用。
背景
我在其他网站和几个SO答案中阅读到,我可以调用win32 API AttachConsole将我的Windows应用程序绑定到运行我的程序的控制台,以便我可以拥有既是“控制台应用程序”又是“Windows应用程序”的东西。
例如,这个问题: 在C#/.Net中是否可能将消息记录到cmd.exe中?
我编写了一堆逻辑使其工作(使用其他几个API),并且我已经使每种情况都能工作(包括重定向,其他人声称不起作用)。 唯一剩下的情况是让Console.WriteLine写入我启动程序的控制台。 根据我所读的所有内容,如果我使用AttachConsole,这应该起作用。 复制 这是一个最小示例 - 请注意,该项目设置为Windows应用程序:
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);
}
  • 当我从命令提示符中运行时,没有出现错误,但是也没有任何控制台输出。这就是问题所在。
  • 如果我在应用程序的执行流程中添加额外的消息框,则消息框会显示。我期望这样做,所以一切都好。
  • 当我从Visual Studio或通过双击运行它时,会显示一个带有错误的消息框。我期望这样做,所以这里没问题(在我的真实应用程序中将使用AllocConsole)。

如果在调用Console.WriteLine后调用Marshal.GetLastWin32Error,则会出现错误“System.ComponentModel.Win32Exception (0x80004005):句柄无效”。我怀疑附加到控制台会导致Console.Out混乱,但我不知道如何解决它。


1
非常奇怪...我在一个应用程序中也使用了AttachConsole(-1),而且没有任何问题。我将在上班时比较代码,如果有任何差异,我会回复的。@CodeInChoas:如果您将其创建为控制台应用程序,则始终会在后台拥有控制台窗口。通过使用AttachConsole,您可以获得最佳效果-当通过命令行(控制台)打开应用程序时,它将写入控制台流。 - Dennis
2
@CodeInChaos:这似乎是一个“显而易见的”解决方案 :) 但我不想要“控制台和Windows应用程序”,我想要控制台或Windows应用程序,基于命令行开关。而且我不希望控制台在启动时闪烁消失。这是一个非常常见的情况,我已经找到了大约十几个线程来使所有东西都能正常工作,包括这一部分。除了这一部分没有起作用。如果你认为我应该在我的问题中更清楚地表达我的要求,请让我知道,我会很乐意修复它。 - Merlyn Morgan-Graham
2
@Cheeso:除了AttachConsole不允许窗口写入控制台之外,其他都可以。这正是我想要解决的问题。 - Merlyn Morgan-Graham
1
我不知道,感觉是环境相关的。但你试图做的基本上是有缺陷的,因为cmd.exe正在写入控制台窗口(显示提示符)和你的程序写入“Test”之间存在竞争。至少编写超过4个字符。否则,这种竞争基本上破坏了这个想法。 - Hans Passant
1
@MerlynMorgan-Graham 你能解决这个问题吗?我也遇到了同样的问题,AttackConsole、AllocConsole都不起作用。 - fhnaseer
显示剩余8条评论
12个回答

16

在Winforms中,我是这样做的。使用WPF也类似。

static class SybilProgram
{
    [STAThread]
    static void Main(string[] args)
    {
        if (args.Length > 0)
        {
            // Command line given, display console
            if ( !AttachConsole(ATTACH_PARENT_PROCESS) )  // Attach to a parent process console (-1)
                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
可爱的代码。它展示了与我的应用程序相同的问题。我启动一个命令提示符,从那里运行程序(App.exe someargs),但没有任何文本输出。然而,如果我运行 cmd /c App.exe someargs,它似乎可以工作(???)。 - Merlyn Morgan-Graham
1
根据 Raymond Chen 博客文章中描述的限制条件,它对我有效。你是用 /t:winexe 还是 /t:exe 进行编译?它们展示不同的行为,但对我来说都“有效”。 - Cheeso
1
AllocConsole和AttachConsole的原型并不完全正确,虽然它可能起作用,但所有的Windows API都使用BOOL而不是c99 bool(1字节)或C#的bool,因此本机代码和托管代码之间返回的数据类型不匹配。我在.NET 1.1中发现了一个由于这个问题而导致的微妙错误,对于更高版本的CLR,我不确定。避免这种情况的最好方法是使用[return: MarshalAs(UnmanagedType.Bool)]。 - zhaorufei

2

我曾遇到类似的情况:无法让Windows应用程序在编程附加的控制台中输出任何内容。最终发现,在AttachConsole之前我使用了一次Console.WriteLine,这会影响接下来的所有操作。


2
谢谢你的提示,但是如果你看一下我的最小复现代码,你会发现我在一个不先执行Console.WriteLine的程序中仍然存在这个问题。正如其他人所说,这可能是环境问题。在我的情况下,可能是什么原因导致了这个问题,我也不知道。我将尝试在另一台计算机上运行我的示例代码,看看是否可以解决它。 - Merlyn Morgan-Graham

1
我遇到了类似的问题。更为复杂的是,我在使用带PRISM的WPF时,需要在“CLI模式”下抑制“Shell”的创建。但我离题了...
这是我找到的唯一有效的解决方法。
    [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!");
    }

所有对Console.WriteLine()的调用都会在InitConsole()之后输出到CLI窗口。
希望这可以帮到您。

我现在不得不在我的所有示例中使用“如StackOverflow所示”。/高尔夫鼓掌 - JJS

1

我没有看到我们的实现之间有任何显著的区别。就我所知,以下是我在我的应用程序中使用的内容,并且它可以正常工作。我还创建了一个示例WPF应用程序,它也可以正常工作。

我怀疑你的问题出在其他地方。很抱歉我不能提供更多帮助。

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

得到那些了解这应该可行的人的确认,相较于一个小时前我已经升级了。感谢您的贡献 :) - Merlyn Morgan-Graham
重新阅读您的问题后,我意识到(感谢@CodeInChaos的评论),实际上需要一个Windows应用程序一个控制台应用程序。要实现这一点,您需要像@Cheeso的答案中演示的那样AllocConsole - Dennis
我会像他的答案一样去做。 我只是在尝试让AllocConsole正常工作。 我会解决好适应问题(其他人似乎在替我担心,哈哈)。 在这里我不介意有坏行为,也不会向客户提交它,所以别担心。 虽然考虑到所有的反对意见,也许我应该把这个东西变成控制台应用程序并打完收工了。但当大家都说有一个应该可以工作的解决方案,而它对于我却无效时,我真的感到非常困扰 :) - Merlyn Morgan-Graham

1

我遇到了同样的问题,看起来当以管理员模式运行cmd.exe时,AttachConsole()调用成功,但Console.Write()Console.WriteLine()不起作用。如果你以普通方式(非管理员)运行cmd.exe,一切似乎都正常。


谢谢!我会(最终)检查一下这个,如果这是问题的原因,我会将此答案标记为已接受。 - Merlyn Morgan-Graham
嗯,虽然标题栏中显示“管理员:C:\Windows\system32\cmd.exe”,但我在运行cmd.exe时没有遇到这个问题。然而,我的应用程序与cmd.exe并发运行,因此下一个命令提示符会在我的程序产生输出之前出现。糟糕。 - Qwertie
对我来说,这是唯一正确的答案!我的所有普通命令提示符都是以提升的方式运行的。在这种情况下,似乎AttachConsole(-1)成功了,但Console.WriteLine()没有输出任何内容。从提升的命令提示符中运行应用程序会显示输出。因此,在某个级别上,Windows 决定不允许应用程序实际写入继承的提升控制台。 - JonBrave

1

我的应用程序当前版本(针对 .NET 4.0)遇到了同样的问题,但我确信 AttachConsole(-1) 在早期版本(针对 .NET 2.0)中按预期工作。

我发现只要从我的应用程序的 .exe.config 文件中删除(自定义的)TraceListener,就可以立即获得控制台输出,尽管我还不知道为什么。

也许这也是吞噬您控制台输出的原因...

更新

实际上,在我的自定义跟踪侦听器的 c'tor 中有一个 Console.WriteLine() 混淆了事情。 删除此行后,AttachConsole(-1) 后的控制台输出恢复正常。


1
我遇到了同样的问题。当我启动已构建的 .exe 文件时,一切都很好,但在 VS 中无法运行。
解决方法:
  1. 检查 启用 VS 托管进程
  2. 以管理员身份运行 VS。
也许这可以帮助其他人解决这个问题。

0

我也遇到了同样的问题。我使用gflags.exe(Windows调试工具的一部分)来将一个命令行WPF应用程序附加到vsjitdebugger.exe上(请参见this帖子)。只要我的应用程序与vsjitdebugger.exe耦合,控制台就不会输出任何内容。

从我分离我的应用程序开始,从我启动应用程序的控制台中恢复了输出。



0

+1

我遇到了同样的问题。使用各种不同版本的AllocConsoleAttachConsole都无法显示控制台输出。

请检查您是否在项目配置中禁用了启用Visual Studio托管进程选项。启用此选项会神奇地使所有控制台消息按预期显示。我正在运行VS2010和.NET4,但this post建议这个“功能”在VS2012中仍然存在。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接