Вопрос по console-application, c#, console, winapi, .net – Почему закрытие консоли, которая была запущена с AllocConsole, приводит к закрытию всего моего приложения? Могу ли я изменить это поведение?

27

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

EDIT

Код:

internal class SomeClass {

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

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

EDIT 2

Я попробовал принятое решение здесь [Захватить консоль выхода C # ], согласно предложению в комментариях к этому вопросу. Код примера содержит ошибки: DLLImport должен быть «kernel32.dll» или «ядро32», а не «ядро32». После внесения этого изменения я получаю сообщение для моего обработчика для CTRL_CLOSE_EVENT, когда я нажимаю X в окне консоли. Однако вызов FreeConsole и / или возврат true не препятствует завершению приложения.

Достаточно много усилий, чтобы создать окна параметров для настройки шрифтов и цветов и т. Д., А затем подключить все это для работы с текстовым полем. Консольная подсистема Windows уже существует. Если я не могу найти готовое решение, я уверен, что это то, что я в конечном итоге сделаю, но это кажется немного глупым делать это. Kelsie
@DanielHilgarth Весь смысл в том, чтобы иметь консольное приложение, которое работает, не показывая консоль пользователю, который запускает приложение. Это может быть буквальноany код, который работает в программе. Servy
Ну, после редактирования 2 я, по крайней мере, знаю одно: что я не знаю ответа! Я также немного встревожен, поскольку, хотя мне никогда не приходилось этого делать, я всегда думал, что смогу освободиться в ответ на это событие, если у меня тоже так получилось, поэтому я действительно надеюсь, что ответ есть. Jon Hanna
Я согласен с Дэниелом, нам нужно увидеть критические строки кода, которые воспроизводят эту проблему. То, как я понимаю вопрос, этого не должно происходить. ВызовFreeConsole долженnot закончить всю заявку / процесс. Вы должны быть в состоянии позвонитьFreeConsole а потом позвонюAllocConsole или жеAttachConsole. Cody Gray♦
Я думаю, что это вариантstackoverflow.com/questions/474679/capture-console-exit-c-sharp с добавлением, что вы хотите вызывать FreeConsole при обработке этого события. Jon Hanna

Ваш Ответ

5   ответов
10

При закрытии окна консоли получено с помощью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;
    }
}

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

2

Я хотел показать вывод скрипта IronPython в консоли, которой я хотел управлять из моей программы. Я сделал это так же, как было предложено здесь - но если бы было несколько сценариев, мне потребовалось бы несколько окон консоли и в соответствии с 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

23

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

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

Из того, что я знаю, у вас есть два варианта:

1. Disable the close button altogether. You can do this with the following code fragment:
HWND hwnd = ::GetConsoleWindow();
if (hwnd != NULL)
{
   HMENU hMenu = ::GetSystemMenu(hwnd, FALSE);
   if (hMenu != NULL) DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
}
2. Stop using consoles altogether, and implement your own text output solution.

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

27

Ах, да, это одно из предостережений при использовании консольной подсистемы Windows. Когда пользователь закрывает окно консоли (независимо от того, как была распределена консоль),all of the processes that are attached to the console are terminated, Такое поведение имеет очевидный смысл для консольных приложений (т. Е. Тех, которые специально нацелены на консольную подсистему, в отличие от стандартных приложений Windows), но в таких случаях, как ваше, это может быть серьезной проблемой.

Единственный обходной путь, о котором я знаю, - это использоватьSetConsoleCtrlHandler function, который позволяет зарегистрировать функцию-обработчик для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 сигналы (которые в противном случае могли бы привести к прекращению работы вашего приложения), но он не работает для того, о котором вы спрашиваете, а именно:CTRL_CLOSE_EVENT, генерируется системой, когда пользователь закрывает окно консоли.

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

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

The system generates CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT signals when the user closes the console, logs off, or shuts down the system so that the process has an opportunity to clean up before termination. Console functions, or any C run-time functions that call console functions, may not work reliably during processing of any of the three signals mentioned previously. The reason is that some or all of the internal console cleanup routines may have been called before executing the process signal handler.

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

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

«Честно говоря, я не знаю, как это предотвратить». Можете ли вы обмануть и вызвать FreeConsole в самом обратном вызове, чтобы консоль больше не ассоциировалась с этим процессом, а когда обратный вызов возвращается, не существует процесса для уничтожения?
Кажется, что есть способ. Например, Far Manager может по-прежнему запрашивать сохранение, когда я нахожусь в редакторе, когда я закрываю окно консоли. Его код с открытым исходным кодом, поэтому тот, кто знает C ++ лучше меня, сможет понять, как они это делают.
-2

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

Определить ..

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


            }
Это просто неправильно, и даже не имеет отношения к вопросу.

Похожие вопросы