【问题标题】:Using AttachConsole, user must hit enter to get regular command line使用 AttachConsole,用户必须按 Enter 才能获得常规命令行
【发布时间】:2009-08-20 10:31:58
【问题描述】:

我有一个既可以作为 winform 运行,也可以从命令行运行的程序。如果从命令行调用它,我会调用 AttachConsole(-1) 以附加到父控制台。

但是,在我的程序结束后,用户必须按回车键才能返回标准命令提示符(“c:\>”)。有没有办法避免这种需要?

谢谢。 我可以将它包装在一个 cmd 文件中以避免该问题,但我想从我的 exe 中完成。

【问题讨论】:

标签: c# console


【解决方案1】:

尝试在您的 exe 退出之前添加此行...

System.Windows.Forms.SendKeys.SendWait("{ENTER}");

有点小技巧,但是当我遇到这个问题时,我能找到最好的方法。

【讨论】:

  • 大声笑,这确实很明显,但在谷歌搜索之前我无法弄清楚:P
  • 这看起来很糟糕,但它似乎是唯一明显的方法。谢谢。
  • 这似乎只在控制台处于前台时才有效......否则'Enter'会被发送到前台的任何应用程序。
  • 警告:我在从任务计划程序调用我的混合 winforms/console 应用程序并使用 SendKeys.SendWait stackoverflow.com/questions/22436352/… 时遇到了这个问题stackoverflow.com/questions/4479214/… 也见这里stackoverflow.com/questions/4479214/…
  • Rob 的方法有些危险,因为对活动窗口使用了 SendKey。我在下面提供的解决方案使用 PostMessage 将 Enter 发送到正确的进程。
【解决方案2】:

无论控制台窗口是在前台、后台还是最小化,这是解决 Enter 键问题的最安全的技巧。您甚至可以在多个控制台窗口中运行它。

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

namespace WindowsAndConsoleApp
{
  static class Program
  {
    const uint WM_CHAR = 0x0102;
    const int VK_ENTER = 0x0D;

    [DllImport("kernel32.dll")]
    static extern bool AttachConsole(int dwProcessId);
    private const int ATTACH_PARENT_PROCESS = -1;

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool FreeConsole();

    [DllImport("kernel32.dll")]
    static extern IntPtr GetConsoleWindow();

    [DllImport("user32.dll")]
    static extern int SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

    [STAThread]
    static void Main(string[] args)
    {
        if (args.Length > 0)
        {
            // Do this first.
            AttachConsole(ATTACH_PARENT_PROCESS);

            Console.Title = "Console Window - Enter Key Test";
            Console.WriteLine("Getting the handle of the currently executing console window...");
            IntPtr cw = GetConsoleWindow();
            Console.WriteLine($"Console handle: {cw.ToInt32()}");
            Console.WriteLine("\nPut some windows in from of this one...");
            Thread.Sleep(5000);
            Console.WriteLine("Take your time...");
            Thread.Sleep(5000);
            Console.WriteLine("Sending the Enter key now...");

            // Send the Enter key to the console window no matter where it is.
            SendMessage(cw, WM_CHAR, (IntPtr)VK_ENTER, IntPtr.Zero);

            // Do this last.
            FreeConsole();
        }
        else
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
  }
}

【讨论】:

  • 这是帮助解决我的问题的答案。我知道这已经快半年了,但谢谢!
  • 太棒了!这完全解决了我的问题!在树莓派的 WOA Deployer 中实现。非常感谢!
【解决方案3】:

Rob L 的方法有点危险,因为它会将 Enter 发送到活动窗口。更好的方法是将 Enter 实际发送到正确的进程(控制台)。

方法如下

    internal static class NativeMethods
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        internal static extern bool AllocConsole();

        [DllImport("kernel32.dll", SetLastError = true)]
        internal static extern bool FreeConsole();

        [DllImport("kernel32", SetLastError = true)]
        internal static extern bool AttachConsole(int dwProcessId);

        [DllImport("user32.dll")]
        internal static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll", SetLastError = true)]
        internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

        [DllImport("User32.Dll", EntryPoint = "PostMessageA")]
        internal static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);

        internal const int VK_RETURN = 0x0D;
        internal const int WM_KEYDOWN = 0x100;
    }

--剪辑--

            bool attached = false;

            // Get uppermost window process
            IntPtr ptr = NativeMethods.GetForegroundWindow();
            int u;
            NativeMethods.GetWindowThreadProcessId(ptr, out u);
            Process process = Process.GetProcessById(u);

            if (string.Compare(process.ProcessName, "cmd", StringComparison.InvariantCultureIgnoreCase) == 0)
            {
                // attach to the current active console
                NativeMethods.AttachConsole(process.Id);
                attached = true;
            }
            else
            {
                // create new console
                NativeMethods.AllocConsole();
            }

            Console.Write("your output");

            NativeMethods.FreeConsole();

            if (attached)
            {
                var hWnd = process.MainWindowHandle;
                NativeMethods.PostMessage(hWnd, NativeMethods.WM_KEYDOWN, NativeMethods.VK_RETURN, 0);
            }

此解决方案基于此处的代码构建:

http://www.jankowskimichal.pl/en/2011/12/wpf-hybrid-application-with-parameters/

【讨论】:

  • 实际上,不要同时创建作为 GUI 和控制台应用程序的程序。阅读此内容:stackoverflow.com/questions/493536/…
  • 我同意 Rob 的解决方案是不安全的,但您的答案似乎通过使用 GetForegroundWindow 遇到了同样的问题。也许使用 API 来查看哪个进程启动了您的应用,或者使用 GetConsoleProcessList。
【解决方案4】:

聚会已经很晚了,这些年来有很多建议,但由于我最近自己解决了这个问题,通过拼接来自不同帖子的一堆信息,我想我会在此处发布解决方案,因为它具有最相关的标题。

此解决方案无需使用Enter 键或模拟按键即可工作。我唯一无法完全解决的问题是在您的应用程序启动时从父控制台拦截Enter。我认为这是不可能的,因为它发生在你有机会拦截它之前;但是,有一个合理的准解决方法。

在深入研究代码之前,以下是我们需要做的事情的顺序:

  1. 附加到父控制台
  2. 捕获父控制台输出的当前提示文本
  3. 通过用空格覆盖父控制台的提示来清除它(不确定是否可以防止这种情况发生)
  4. 正常与控制台交互
  5. 通过编写我们在 #2 中捕获的内容来恢复父控制台的先前提示

这是它在使用中的样子:

using System;
using System.Windows.Forms;

public static void Main(string[] args)
{
    if (args.Length > 0)
    {
        using (new ConsoleScope())
        {
            Console.WriteLine("I now own the console");
            Console.WriteLine("MUA HA HA HA HA HA!!!");
        }
    }
    else
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());
    }
}

...现在是代码。这比我想要的要多,但这是我可以为帖子所做的简洁。愿这有助于其他人尝试同样的事情。尽情享受吧!

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

public sealed class ConsoleScope : IDisposable
{
    const int ATTACH_PARENT_PROCESS = -1;
    const int STD_OUTPUT_HANDLE = -11;
    readonly bool createdNewConsole;
    readonly string prompt;
    bool disposed;

    public ConsoleScope()
    {
        if (AttachParentConsole())
        {
            prompt = CaptureParentConsoleCurrentPrompt();
        }
        else
        {
            AllocConsole();
            createdNewConsole = true;
        }
    }

    ~ConsoleScope() => CleanUp();

    public void Dispose()
    {
        CleanUp();
        GC.SuppressFinalize(this);
    }

    static string CaptureParentConsoleCurrentPrompt()
    {
        var line = (short)Console.CursorTop;
        var length = (short)Console.CursorLeft;
        var noPrompt = line == 0 && length == 0;

        if (noPrompt)
        {
            return default;
        }

        return ReadCurrentLineFromParentConsoleBuffer(line, length);
    }

    static string ReadCurrentLineFromParentConsoleBuffer(short line, short length)
    {
        var itemSize = Marshal.SizeOf(typeof(CHAR_INFO));
        var buffer = Marshal.AllocHGlobal(length * itemSize);
        var encoding = Console.OutputEncoding;
        var text = new StringBuilder(capacity: length + 1);
        var coordinates = default(COORD);
        var textRegion = new SMALL_RECT
        {
            Left = 0,
            Top = line,
            Right = (short)(length - 1),
            Bottom = line,
        };
        var bufferSize = new COORD
        {
            X = length,
            Y = 1,
        };

        try
        {
            if (!ReadConsoleOutput(GetStdOutputHandle(), buffer, bufferSize, coordinates, ref textRegion))
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }

            var array = buffer;

            for (var i = 0; i < length; i++)
            {
                var info = Marshal.PtrToStructure<CHAR_INFO>(array);
                var chars = encoding.GetChars(info.CharData);

                text.Append(chars[0]);
                array += itemSize;
            }
        }
        finally
        {
            Marshal.FreeHGlobal(buffer);
        }

        // now that we've captured the current prompt, overwrite it with spaces
        // so that things start where the parent left off at
        Console.SetCursorPosition(0, line);
        Console.Write(new string(' ', length));
        Console.SetCursorPosition(0, line - 1);

        return text.ToString();
    }

    void CleanUp()
    {
        if (disposed)
        {
            return;
        }

        disposed = true;
        RestoreParentConsolePrompt();

        if (createdNewConsole)
        {
            FreeConsole();
        }
    }

    void RestoreParentConsolePrompt()
    {
        var text = prompt;

        if (!string.IsNullOrEmpty(text))
        {
            // this assumes the last output from your application used
            // Console.WriteLine or otherwise output a CRLF. if it didn't,
            // you may need to add an extra line here
            Console.Write(text);
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    struct CHAR_INFO
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
        public byte[] CharData;
        public short Attributes;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct COORD
    {
        public short X;
        public short Y;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct SMALL_RECT
    {
        public short Left;
        public short Top;
        public short Right;
        public short Bottom;
    }

    // REF: https://docs.microsoft.com/en-us/windows/console/allocconsole
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AllocConsole();

    // REF: https://docs.microsoft.com/en-us/windows/console/attachconsole
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AttachConsole(int dwProcessId);

    // REF: https://docs.microsoft.com/en-us/windows/console/freeconsole
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool FreeConsole();

    static bool AttachParentConsole() => AttachConsole(ATTACH_PARENT_PROCESS);

    // REF: https://docs.microsoft.com/en-us/windows/console/readconsoleoutput
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool ReadConsoleOutput(IntPtr hConsoleOutput, IntPtr lpBuffer, COORD dwBufferSize, COORD dwBufferCoord, ref SMALL_RECT lpReadRegion);

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

    static IntPtr GetStdOutputHandle() => GetStdHandle(STD_OUTPUT_HANDLE);
}

【讨论】:

  • 这是一个有用的响应,也是我见过的第一个处理擦除/恢复命令提示符的响应。但它仍然远非完美。当您的程序从批处理文件(或任何其他具有多个步骤的工具)运行时,它不会“阻止”父级,因此后续命令将执行 - 并可能生成它们自己的冲突输出 - 而您的仍在运行。为了演示,运行:yourprogram.exe &amp; echo hello &amp; echo there
  • 有趣。从未尝试过,因为我不需要 scripting 之类的支持或命令链接。是否有建议的方法可以解决这种情况?
【解决方案5】:

好的,我没有解决方案,但这似乎是因为 cmd.exe 没有等待启动的进程,而对于普通的控制台应用程序,cmd.exe 会等到应用程序退出。我不知道是什么让 cmd.exe 决定在应用程序上等待或不等待,普通的 Windows 窗体应用程序刚刚启动,而 cmd.exe 不等待它退出。也许这个提示会触发某人!在此期间,我会更深入地挖掘。

【讨论】:

    【解决方案6】:

    在退出可执行文件之前尝试调用FreeConsole 函数。

    【讨论】:

    • 我已经试过了,好像不行。无论如何,谢谢。
    【解决方案7】:

    这对我来说是最简单的解决方案:

    myapp.exe [params] | ECHO.
    

    【讨论】:

      猜你喜欢
      • 2012-06-04
      • 1970-01-01
      • 1970-01-01
      • 2017-06-05
      • 1970-01-01
      • 1970-01-01
      • 2015-10-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多