Как я могу определить, нажмите ли любую клавишу, чтобы продолжить. . . будет отображаться?

При запуске консольного приложения в Visual Studio, в зависимости от ваших настроек, оно добавит подсказку после выхода из программы:

Нажмите любую клавишу для продолжения . . .

Я нашел, как определить, работаю ли я под отладчиком (используйте Debugger.IsAttached), но это бесполезно. Нажмите CTRL-F5, чтобы Начать без отладки устанавливает этот флаг на false, но по-прежнему показывает приглашение.

Я хочу обнаружить это, потому что хочу отображать собственное сообщение и ждать нажатия клавиши, но не удваивать проверки нажатия клавиш.

Я не хочу возиться с моими общими настройками Visual Studio. Если я смогу отключить его для этого проекта таким образом, чтобы его можно было проверить в системе управления версиями, это тоже сработает.

Какой механизм используется для добавления этого приглашения и как его обнаружить?

Или как отключить его для каждого проекта и проверить это изменение в системе управления версиями?


person Merlyn Morgan-Graham    schedule 18.09.2011    source источник


Ответы (7)


Добавьте следующий код в консольное приложение:

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 ваше приложение запускается со следующей командной строкой при запуске без отладки:

"%comspec%" /c ""имя файла вашего приложения" и пауза"

Поэтому невозможно достичь цели полностью управляемыми способами; потому что он НЕ находился в домене приложения.

Здесь мы используем управляемый способ WMI для перечисления процессов и инкапсулируем неуправляемые WINAPI для завершения ProcessThread, потому что ProcessThread обычно не прерывается; он предоставляется как что-то только для чтения.

Как упоминалось выше, приложение было создано с помощью конкретной командной строки; у него будет подпись один поток создает один процесс, поэтому мы использовали метод Single() для извлечения этого потока и его завершения.

Когда мы запускаем приложение в существующей командной строке, это тот же самый сценарий Запуск без отладки. Кроме того, при Начать отладку процесс приложения создается devenv.exe. У него много потоков, мы знаем это и не будем прерывать ни один поток, просто подскажите и дождитесь нажатия клавиши. Эта ситуация аналогична запуску приложения двойным щелчком или из контекстного меню. Таким образом, процесс приложения создается системной оболочкой, обычно Explorer.exe, и он также имеет множество потоков.

На самом деле, если мы можем успешно прервать поток, это означает, что у нас есть права на уничтожение родительского процесса. Но нам НЕ это нужно. Нам просто нужно прервать единственный поток, процесс автоматически завершается системой, когда у него больше нет потоков. Убить родительский процесс, определив, что вызывающий процесс — %comspec%, — еще один способ сделать то же самое, но это опасная процедура. Поскольку процесс, порождающий приложение, может иметь другие потоки, которые имеют любое количество потоков, создают процесс, соответствующий %comspec%. Вы можете убить критическую работу процесса по небрежности или просто усложнить проверку того, безопасно ли убивать процесс. Поэтому я считаю, что один поток создает один процесс как сигнатуру нашего родительского процесса, который можно безопасно убить/прервать.

WMI является современным, некоторые из WINAPI могут стать устаревшими в будущем. Но настоящая причина этой композиции в ее простоте. Старое Tool Help Library такое же сложное, как и способы преобразования ProcessThread в System.Threading.Thread. С помощью LINQ и методов расширения мы можем сделать код более простым и семантическим.

person Ken Kin    schedule 28.02.2013
comment
Выглядит многообещающе. Примет через некоторое время после назначения награды. - person Merlyn Morgan-Graham; 28.02.2013
comment
А.. Спасибо. Награда начата мной, я могу присуждать только другим, я ищу лучший ответ. - person Ken Kin; 28.02.2013
comment
Вы определяете, но никогда не используете метод GetChildProcesses. Вероятно, это остатки кода вашего собственного приложения, которое вы скопировали сюда. Могу ли я удалить его? - person Stefan Monov; 25.07.2016
comment
Кроме того, почему вы называете свой метод Abort перед Write и ReadKey? Я называю это после них, и это работает, как и ожидалось. Если вы вызовете его перед ними, приложение никогда не успеет записать и прочитать. - person Stefan Monov; 25.07.2016
comment
И еще кое-что. Ваш код работает немного медленно. После того, как я нажму любую клавишу, перед закрытием консоли будет задержка около 500 мс. Было бы неплохо увидеть, что это улучшилось. - person Stefan Monov; 25.07.2016
comment
@StefanMonov Спасибо за комментарий. Вы правы, GetChildProcesses здесь не используется, просто демонстрация обратной функциональности против GetParentProcess. - person Ken Kin; 22.08.2016
comment
@StefanMonov Я думаю, что причина Abort заключается в том, что манипуляция с консолью не указана в ответе, если это не так, то .. сам отладчик vs предложит и будет ждать нажатия клавиши, прерывая поток до того, как ваше приложение манипулирует консолью, подавляя выполнение того же самого. дважды — один выполняется вашим приложением, а другой — отладчиком. С другой стороны, если ваше приложение не было создано отладчиком, Abort завершится ошибкой с исключением и проглочено, просто запросите и дождитесь нажатия клавиши. - person Ken Kin; 22.08.2016
comment
@StefanMonov Это очень специфичный для приложения вопрос, я не уверен, как он может быть полезен в будущем, особенно если Microsoft решит изменить даже небольшую часть всего механизма, это может сделать решение недействительным. Поэтому я бы посоветовал выяснить, как это работает, и попытаться написать как можно меньше кода и не заморачиваться с производительностью, или вы можете вместо этого попытаться написать какой-нибудь полностью нативный код и не использовать WMI. - person Ken Kin; 22.08.2016

Похоже, это приглашение предоставляется командой pause. Эта команда автоматически добавляется Visual Studio.

Когда вы запускаете свой проект вне Visual Studio, нет причин «обнаруживать» эту команду. Можете смело предположить, что он не будет добавлен в вашу программу. Это означает, что вы можете добавить любое приглашение, например:

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

См. этот вопрос.

person s_hewitt    schedule 18.09.2011
comment
Хороший ответ о механизме, хотя оказывается, что это не самая полезная часть моего вопроса для меня, если я не найду способ обнаружить, что это происходит. Знаете ли вы, как определить, упаковано ли мое приложение таким образом? Я хотел бы обнаружить это, потому что я не хочу отключать функцию VS и не хочу, чтобы мне дважды предлагали убить мое приложение. - person Merlyn Morgan-Graham; 18.09.2011
comment
Я сделал правку, это поможет? Я недостаточно знаком с командной строкой и C#, чтобы знать, можете ли вы определить, была ли вызвана текущая программа с pause в качестве параметра. - person s_hewitt; 18.09.2011

Вот кусок кода, который должен это сделать:

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;
}
person Simon Mourier    schedule 01.03.2013
comment
Мне нравится этот ответ лучше, чем принятый, так как он не требует ожидания ~ 500 мс после нажатия любой клавиши. - person Stefan Monov; 25.07.2016

Это сообщение не имеет ничего общего с вашей программой. Вы можете добавить в свою программу все, что захотите, и она будет работать так же, как если бы вы запускали ее из командной строки.

Visual Studio отображает его, чтобы показать вам, что его выполнение завершено, поэтому вы знаете, что оно завершилось правильно. Если вы хотите пропустить его, вы можете попробовать «запустить без отладки» (или что-то в этом роде; чуть ниже «запустить с отладчиком»).

person Rook    schedule 18.09.2011
comment
Ctrl-F5 — это ярлык для запуска без отладки. Отредактирую мой вопрос, чтобы уточнить. - person Merlyn Morgan-Graham; 18.09.2011
comment
@Merlyn - Тогда попробуйте другой - я знаю, что он пропускает нажатие любой клавиши на одном из двух (с/без отладки). Это должно в любом случае. - person Rook; 18.09.2011
comment
Так что, хотя я и согласен с Доктором, у меня болит рука, когда я это делаю. Ну, не делай этого, мне также любопытно, возможно ли это и как определить, что подсказка будет отображаться. - person Merlyn Morgan-Graham; 18.09.2011
comment
@Merlyn - Насколько мне известно, он всегда будет отображаться при использовании одного из них и никогда при использовании другого варианта (без отладки). Я не знаю, как вы могли это обнаружить, но, имея в виду вышеизложенное, я также не знаю, почему вы это сделаете. С какой целью? - person Rook; 18.09.2011
comment
Не отображается, если запускаю под отладчиком. Однако это не соответствует моему стандартному рабочему процессу, потому что работа под отладчиком тяжела. Я делаю это, когда это полезно, и не делаю, когда это не так (в 80% случаев). - person Merlyn Morgan-Graham; 18.09.2011
comment
@Merlyn - предполагается (я думаю), что если вы отлаживаете программу, вы уже знаете, когда она закончится ... - person Rook; 18.09.2011

Это вывод команды «пауза», добавленной визуальной студией. Если вы видите это, программа завершена. Что приводит к вопросу, например, может ли приложение обнаружить, что оно само завершено. Я думаю, это не логично.

person Mert Gülsoy    schedule 02.03.2013
comment
Цель состоит в том, чтобы сделать обратное - определить, размещены ли вы не в VS, поэтому должен представить механизм приостановки и избежать двойной паузы, если вы размещены в VS. - person Merlyn Morgan-Graham; 03.03.2013

Это приглашение выдается, когда система команд («пауза»)

используется.

Даже я столкнулся с этой проблемой. Вы можете либо использовать функцию _getch() в conio.h для ожидания нажатия клавиши.

Таким образом, вы можете использовать следующий код:

      cout<<"your message here"
      _getch()

Это будет ждать нажатия клавиши и отобразит ваше собственное приглашение.

person IcyFlame    schedule 02.03.2013

Не поможет обнаружить, но добавит такое же приглашение при запуске с подключенным отладчиком (F5), если вы добавите это в конец Main:

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

На практике это будет делать то же самое, что и Ctrl + F5 с & pause.

person Ilya Chernomordik    schedule 19.02.2019