【问题标题】:How can I detect if "Press any key to continue . . ." will be displayed?如何检测是否“按任意键继续......”会显示?
【发布时间】:2011-09-18 01:26:26
【问题描述】:

在 Visual Studio 中运行控制台应用程序时,根据您的设置,它会在程序退出后添加提示:

按任意键继续。 . .

我找到了如何检测我是否在调试器下运行(使用Debugger.IsAttached),但这没有帮助。按 CTRL-F5 以 Start without Debugging 将此标志设置为 false,但 仍然显示提示。

我想检测到这一点,因为我想显示自己的消息并等待按键,但不加倍按键检查。

我不想弄乱我的常规 Visual Studio 设置。如果我可以以一种可以签入源代码管理的方式为该项目禁用它,那也可以。

使用什么机制来附加这个提示,我如何检测它?

或者我如何在每个项目中禁用它,并将此更改检查到源代码管理中?

【问题讨论】:

    标签: visual-studio ide console-application visual-studio-debugging


    【解决方案1】:

    将以下代码添加到控制台应用程序:

    public static class Extensions {
        [DllImport("kernel32.dll")]
        static extern IntPtr OpenThread(uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
    
        [DllImport("kernel32.dll")]
        static extern bool TerminateThread(IntPtr hThread, uint dwExitCode);
    
        public static Process GetParentProcess(this Process x) {
            return (
                from it in (new ManagementObjectSearcher("root\\CIMV2", "select * from Win32_Process")).Get().Cast<ManagementObject>()
                where (uint)it["ProcessId"]==x.Id
                select Process.GetProcessById((int)(uint)it["ParentProcessId"])
                ).First();
        }
    
        public static IEnumerable<Process> GetChildProcesses(this Process x) {
            return (
                from it in (new ManagementObjectSearcher("root\\CIMV2", "select * from Win32_Process")).Get().Cast<ManagementObject>()
                where (uint)it["ParentProcessId"]==x.Id
                select Process.GetProcessById((int)(uint)it["ProcessId"])
                );
        }
    
        public static void Abort(this ProcessThread x) {
            TerminateThread(OpenThread(1, false, (uint)x.Id), 1);
        }
    }
    

    然后像这样修改你的代码:

    class Program {
        static void Main(String[] args) {
            // ... (your code might goes here)
    
            try {
                Process.GetCurrentProcess().GetParentProcess().Threads.Cast<ProcessThread>().Single().Abort();
            }
            catch(InvalidOperationException) {
            }
    
            Console.Write("Press ONLY key to continue . . . ");
            Console.ReadKey(true);
        }
    }
    

    所以,我们期待的一切现在都完成了。我认为这是一种解决方法。它在Windows XP SP3 下工作,我猜它可以在较新的Windows 操作系统上工作。在 Visual Studio 下,应用程序始终是一个衍生进程。在较旧的 Visual C++ 6.0 中,它由 IDE 通过调用 VCSPAWN.EXE 生成;在 Visual Studio 2010 中,您的应用程序在 Start without Debugging 时使用以下命令行运行:

    "%comspec%" /c ""你的应用文件名" & pause"

    所以不可能以完全管理的方式实现目标;因为它在应用程序域下NOT。

    这里我们使用WMI的托管方式枚举进程,并封装非托管WINAPIs来终止ProcessThreads,因为ProcessThread不应该被正常中止;它像只读的东西一样提供。

    如上所述,应用程序是使用特定命令行生成的;它会有单个线程创建单个进程签名,因此我们使用Single() 方法检索该线程并终止它。

    当我们在现有的命令提示符下启动应用程序时,它与 Start without Debugging 的场景相同。此外,当开始调试时,应用程序进程由devenv.exe创建。它有很多线程,我们知道并且不会中止任何线程,只是提示并等待按键。这种情况类似于通过双击或从上下文菜单启动应用程序。这样,应用程序进程是由系统外壳创建的,通常是Explorer.exe,它也有很多线程。

    事实上,如果我们能够成功中止线程,则意味着我们有权限杀死父进程。但我们确实不需要需要。我们只需要中止唯一的线程,当没有更多线程时,系统会自动终止进程。通过识别调用进程是%comspec% 来杀死父进程是另一种做同样事情的方法,但这是一个危险的过程。因为生成应用程序的进程可能有其他线程,这些线程具有任意数量的线程,因此创建了一个匹配%comspec% 的进程。您可能会粗心地杀死一个关键的进程工作,或者只是增加检查进程是否可以安全杀死的复杂性。所以我认为单个线程创建单个进程作为我们父进程的签名,可以安全地杀死/中止。

    WMI 是现代的,某些WINAPIs 将来可能会被弃用。但这种组合的真正原因是它的简单性。旧的Tool Help Library 就像将ProcessThread 转换为System.Threading.Thread 的方法一样复杂。使用 LINQ 和扩展方法,我们可以使代码更简单、更语义化。

    【讨论】:

    • 看起来很有希望。分配赏金后将接受一段时间。
    • 啊..谢谢。赏金由我发起,只能奖励给别人,我正在寻找更好的答案。
    • 您定义但从不使用 GetChildProcesses 方法。可能是您在此处复制的您自己的应用程序代码的剩余部分。我可以删除它吗?
    • 另外,为什么将Abort 方法之前 称为Write 和ReadKey?我在他们之后调用它,它按预期工作。如果您在他们之前调用它,应用程序将永远无法进行读写操作。
    • 还有一件事。您的代码运行速度有点慢。按任意键后,控制台关闭前大约有 500 毫秒的延迟。很高兴看到这种情况有所改善。
    【解决方案2】:

    听起来这个提示是由pause 命令提供的。此命令由 Visual Studio 自动添加。

    当您在 Visual Studio 之外运行项目时,没有理由“检测”此命令。您可以放心地假设它不会被添加到您的程序中。这意味着您可以继续添加任何您想要的提示,类似于:

    Console.WriteLine("Press any key...");
    Console.Read();
    

    See this question.

    【讨论】:

    • 关于机制的好答案,但事实证明这不是我的问题对我最有帮助的部分,除非我找到一种方法来检测它正在发生。你知道如何检测我的应用程序是否以这种方式包装吗?我想检测到这一点,因为我不想禁用 VS 功能,也不想被提示两次以终止我的应用程序。
    • 我进行了编辑,有帮助吗?我对命令行和C#不够熟悉,不知道你是否可以确定当前程序是否以pause作为参数调用。
    【解决方案3】:

    这是一段应该做的代码:

    class Program
    {
        static void Main(string[] args)
        {
            // do your stuff
    
            if (!WasStartedWithPause())
            {
                Console.WriteLine("Press any key to continue . . . ");
                Console.ReadKey(true);
            }
        }
    }
    
    public static bool WasStartedWithPause()
    {
        // Here, I reuse my answer at http://stackoverflow.com/questions/394816/how-to-get-parent-process-in-net-in-managed-way
        Process parentProcess = ParentProcessUtilities.GetParentProcess();
    
        // are we started by cmd.exe ?
        if (string.Compare(parentProcess.MainModule.ModuleName, "cmd.exe", StringComparison.OrdinalIgnoreCase) != 0)
            return false;
    
        // get cmd.exe command line
        string cmdLine = GetProcessCommandLine(parentProcess);
    
        // was it started with a pause?
        return cmdLine != null & cmdLine.EndsWith("& pause\"");
    }
    
    public static string GetProcessCommandLine(Process process)
    {
        if (process == null)
            throw new ArgumentNullException("process");
    
        // use WMI to query command line
        ManagementObjectCollection moc = new ManagementObjectSearcher("SELECT CommandLine FROM Win32_Process WHERE ProcessId=" + process.Id).Get();
        foreach (ManagementObject mo in moc)
        {
            return (string)mo.Properties["CommandLine"].Value;
        }
        return null;
    }
    

    【讨论】:

    • 我比接受的答案更喜欢这个答案,因为它不需要在按下任何键后等待约 500 毫秒。
    【解决方案4】:

    该消息与您的程序无关。您可以将任何您喜欢的内容添加到您的程序中,并且它的执行方式与您从命令提示符运行它的方式相同。

    Visual Studio 显示它的目的是为了向您展示它的执行已完成运行,因此您知道它正确结束。如果你想跳过它,你可以尝试“在不调试的情况下运行”(或类似的东西;它就在“使用调试器运行”的下方)。

    【讨论】:

    • Ctrl-F5 是“无调试启动”的快捷方式。将编辑我的问题以澄清。
    • @Merlyn - 然后尝试另一个 - 我知道它会跳过两者之一(带/不带调试)上的“按任意键”。无论如何都应该这样做。
    • 因此,虽然我同意“医生,我这样做时我的手臂会受伤”“好吧,不要那样做”的观点,但我也很好奇这是否可能以及如何进行检测将显示提示。
    • @Merlyn - 据我所知,它总是在使用其中一个时显示,而在使用另一个(不带调试)选项时永远不会显示。我不知道您如何检测到它,但是考虑到上述情况,我也不知道您为什么会检测到它。有什么目的?
    • 在调试器下运行时不显示。不过,这不符合我的标准工作流程,因为在调试器下运行非常繁重。我会在有用的时候做,在没用的时候不做(80% 的时间)。
    【解决方案5】:

    这是 Visual Studio 添加的“暂停”命令的输出。如果你看到这个,程序就结束了。这就涉及到一个问题,比如应用程序是否有可能检测到它自己已经结束。我认为这不合逻辑。

    【讨论】:

    • 目标是做相反的事情 - 检测你是否不是由 VS 托管的,因此应该提供一个暂停机制,如果你是由 VS 托管的,则避免两次暂停。
    【解决方案6】:

    这个提示是在执行命令时给出的 系统(“暂停”)

    已使用。

    即使我也遇到过这个问题。您可以使用 conio.h 下的函数 _getch() 来等待按键。

    所以你可以使用下面的代码:

          cout<<"your message here"
          _getch()
    

    这将等待按键并显示您自己的提示。

    【讨论】:

      【解决方案7】:

      无助于检测,但如果您将其添加到Main 的末尾,则会在附加调试器 (F5) 的情况下运行时添加相同的提示:

      if (Debugger.IsAttached)
      {
          Console.Write("Press any key to continue . . . ");
          Console.ReadKey();
      }
      

      实际上它的作用与 Ctrl + F5 对&amp; pause 的作用相同

      【讨论】:

        猜你喜欢
        • 2010-11-29
        • 2020-03-14
        • 1970-01-01
        • 1970-01-01
        • 2020-08-10
        • 1970-01-01
        • 1970-01-01
        • 2013-11-10
        • 2015-01-23
        相关资源
        最近更新 更多