Подтвердить что ты не робот

Почему закрытие консоли, запущенной с помощью AllocConsole, вызывают выход моего приложения? Могу ли я изменить это поведение?

Что я хочу, так это то, что консольное окно просто уходит, а еще лучше, что оно скрыто, но я хочу, чтобы мое приложение продолжало работать. Это возможно? Я хочу иметь возможность использовать Console.WriteLine и иметь консоль в качестве окна вывода. Я хочу, чтобы иметь возможность скрывать и показывать его, и я не хочу, чтобы все приложение умирало только потому, что консоль была закрыта.

ИЗМЕНИТЬ

код:

internal class SomeClass {

    [DllImport("kernel32")]
    private static extern bool AllocConsole();

    private static void Main() {
        AllocConsole();
        while(true) continue;
    }
}

РЕДАКТИРОВАТЬ 2

Я попробовал принятое решение здесь [Capture console exit С#], по предложению в комментариях по этому вопросу. Код примера прослушивается тем, что DLLImport должен быть "kernel32.dll" или "kernel32", а не "Kernel32". Сделав это изменение, я получаю сообщение для моего обработчика для CTRL_CLOSE_EVENT, когда я нажимаю X в окне консоли. Однако вызов FreeConsole и/или возвращение true не препятствует завершению приложения.

4b9b3361

Ответ 1

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

Единственным обходным решением, которое я знаю, является использование функции SetConsoleCtrlHandler, которая позволяет вам зарегистрировать функцию обработчика для Ctrl + C и Ctrl + Break, а также системные события, такие как пользователь, закрывающий окно консоли, выход пользователя из системы или завершение работы системы. В документации говорится, что если вы заинтересованы в игнорировании этих событий, вы можете передать null для первого аргумента. Например:

[DllImport("kernel32")]
static extern bool SetConsoleCtrlHandler(HandlerRoutine HandlerRoutine, bool Add);

delegate bool HandlerRoutine(uint dwControlType);

static void Main()
{
    AllocConsole();
    SetConsoleCtrlHandler(null, true);
    while (true) continue;
}

Это отлично работает для сигналов Ctrl + C и Ctrl + Break (что в противном случае заставило бы ваше приложение также прекратить работу), но оно не работает для того, которое вы запрашиваете about, который является CTRL_CLOSE_EVENT, сгенерированным системой, когда пользователь закрывает окно консоли.

Честно говоря, я не знаю, как это предотвратить. Даже образец в SDK фактически не позволяет игнорировать CTRL_CLOSE_EVENT. Я попробовал это в небольшом тестовом приложении, и он подает звуковой сигнал, когда вы закрываете окно и печатает сообщение, но процесс все равно заканчивается.

Возможно, более тревожно, документация заставляет меня думать, что это невозможно предотвратить:

Система генерирует сигналы CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT и CTRL_SHUTDOWN_EVENT, когда пользователь закрывает консоль, выходит из системы или выключает систему, чтобы процесс имел возможность очистить до завершения. Функции консоли или любые функции времени выполнения C, вызывающие функции консоли, могут не работать надежно при обработке любого из трех сигналов, упомянутых ранее. Причина в том, что некоторые или все внутренние процедуры очистки консоли могут быть вызваны перед выполнением обработчика сигнала процесса.

Это последнее предложение, которое бросается в глаза. Если консольная подсистема сразу же начинает очистку после себя в ответ на попытку пользователя закрыть окно, возможно, невозможно остановить ее после факта.

(По крайней мере, теперь вы понимаете проблему. Может быть, кто-то еще может прийти к решению!)

Ответ 2

К сожалению, вы ничего не можете сделать, чтобы действительно изменить это поведение.

Консольные окна являются "специальными" в том, что они размещены другим процессом и не позволяют выполнять подкласс. Это ограничивает вашу способность изменять свое поведение.

Из того, что я знаю, ваши два варианта:

1. Отключите кнопку закрытия. Вы можете сделать это со следующим фрагментом кода:

HWND hwnd = ::GetConsoleWindow();
if (hwnd != NULL)
{
   HMENU hMenu = ::GetSystemMenu(hwnd, FALSE);
   if (hMenu != NULL) DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
}

2. Прекратите использование консолей в целом и реализуйте свое собственное решение для вывода текста.

Вариант № 2 является более сложным вариантом, но обеспечит вам наибольший контроль. Я нашел статью о CodeProject, которая реализует консольное приложение с использованием расширенного элемента управления редактирования для отображения текста (у богатых элементов управления редактирования есть возможность передавать текст, например, консоль, поэтому они хорошо подходят для такого рода приложений).

Ответ 3

При закрытии окна консоли, полученного с помощью AllocConsole или AttachConsole, соответствующий процесс завершится. От этого не избежать.

До Windows Vista закрытие окна консоли приведет к тому, что пользователь спросит его, должен ли процесс быть завершен или нет, но Windows Vista и более поздние версии не предоставляют такого диалога, и процесс завершается.

Одним из возможных решений для решения этой проблемы является полное отсутствие AttachConsole и достижение желаемой функциональности с помощью других средств.

Например, в случае, описанном OP, консольное окно было необходимо для вывода некоторого текста на консоли с использованием статического класса Console.

Это может быть достигнуто очень легко, используя межпроцессную связь. Например, консольное приложение может быть разработано для работы в качестве эхо-сервера.

namespace EchoServer
{
    public class PipeServer
    {
        public static void Main()
        {
            var pipeServer = new NamedPipeServerStream(@"Com.MyDomain.EchoServer.PipeServer", PipeDirection.In);
            pipeServer.WaitForConnection();

            StreamReader reader = new StreamReader(pipeServer);

            try
            {
                int i = 0;
                while (i >= 0)
                {
                    i = reader.Read();
                    if (i >= 0)
                    {
                        Console.Write(Convert.ToChar(i));
                    }
                }
            }
            catch (IOException)
            {
                //error handling code here
            }
            finally
            {
                pipeServer.Close();
            }
        }
    }
} 

а затем вместо выделения/присоединения консоли к текущему приложению, эхо-сервер может быть запущен из приложения, а выходной поток Console's можно перенаправить для записи на сервер каналов.

class Program
{
    private static NamedPipeClientStream _pipeClient;

    static void Main(string[] args)
    {
        //Current application is a Win32 application without any console window
        var processStartInfo = new ProcessStartInfo("echoserver.exe");

        Process serverProcess = new Process {StartInfo = processStartInfo};
        serverProcess.Start();

        _pipeClient = new NamedPipeClientStream(".", @"Com.MyDomain.EchoServer.PipeServer", PipeDirection.Out, PipeOptions.None);
        _pipeClient.Connect();
        StreamWriter writer = new StreamWriter(_pipeClient) {AutoFlush = true};
        Console.SetOut(writer);

        Console.WriteLine("Testing");

        //Do rest of the work. 
        //Also detect that the server has terminated (serverProcess.HasExited) and then close the _pipeClient
        //Also remember to terminate the server process when current process exits, serverProcess.Kill();
        while (true)
            continue;
    }
}

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

Ответ 4

Я хотел показать вывод IronPython script в консоли, которую я хотел контролировать из своей программы. Поэтому я сделал это так же, как это было предложено здесь, но если бы было несколько сценариев, мне понадобилось бы несколько консольных окон и в соответствии с http://msdn.microsoft.com/en-us/library/windows/desktop/ms681944(v=vs.85).aspx Windows-программы не могут иметь более одной оболочки (Позор вам, Microsoft!).

Это был прерыв сделки. Я должен был отклонить решение, и теперь я перенаправляю вывод IronPython на самозаписываемый оконный класс, который имитирует оболочку. Видеть http://www.codeproject.com/Articles/335909/Embedding-a-Console-in-a-C-Application

Ответ 5

Использовать FreeConsole вместо AllocConsole

Define..

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

Использование..

public void ExecuteCommandSync(object command, String PATH,bool redirect)
        {
            try
            {
                //AllocConsole();
                FreeConsole();
                ProcessStartInfo pw = new ProcessStartInfo();

                pw.FileName = @"cmd.exe";

                pw.UseShellExecute = false;
                pw.RedirectStandardInput = redirect;


                pr.StartInfo = pw;

                pr.Start();
                pr.StandardInput.WriteLine(@"cd "+ PATH);
                pr.StandardInput.WriteLine(@""+command);


            }