¿Por qué cerrar una consola que se inició con AllocConsole hace que se cierre toda la aplicación? ¿Puedo cambiar este comportamiento?

Lo que quiero que suceda es que la ventana de la consola simplemente se va, o mejor aún que está oculta, pero quiero que mi aplicación siga funcionando. ¿Es eso posible? Quiero poder usar Console.WriteLine y hacer que la consola funcione como una ventana de salida. Quiero poder ocultarlo y mostrarlo, y no quiero que toda la aplicación muera solo porque la consola estaba cerrada.

EDITAR

Código:

internal class SomeClass { [DllImport("kernel32")] private static extern bool AllocConsole(); private static void Main() { AllocConsole(); while(true) continue; } } 

EDIT 2

Intenté la solución aceptada aquí [ Consola de captura exit C # ], según la sugerencia en los comentarios sobre esta pregunta. El código de ejemplo está bloqueado porque DLLImport debe ser “kernel32.dll” o “kernel32”, no “Kernel32”. Después de hacer ese cambio, recibo un mensaje para CTRL_CLOSE_EVENT en mi controlador cuando hago clic en la X en la ventana de la consola. Sin embargo, llamar a FreeConsole y / o devolver true no impide que la aplicación finalice.

Ah, sí, esta es una de las advertencias sobre el uso del subsistema de la consola de Windows. Cuando el usuario cierra la ventana de la consola (independientemente de cómo se asignó la consola), todos los procesos que están conectados a la consola finalizan . Ese comportamiento tiene un sentido obvio para las aplicaciones de consola (es decir, aquellas que se dirigen específicamente al subsistema de la consola, a diferencia de las aplicaciones estándar de Windows), pero puede ser un gran problema en casos como el suyo.

La única solución que conozco es utilizar la función SetConsoleCtrlHandler , que le permite registrar una función de controlador para Ctrl + C y Ctrl + Break , así como eventos del sistema como el usuario que cierra la ventana de la consola, el usuario que cierra la sesión, o el sistema se está apagando La documentación dice que si solo está interesado en ignorar estos eventos, puede pasar el null para el primer argumento. Por ejemplo:

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

Eso funciona perfectamente para las teclas Ctrl + C y Ctrl + Break (que de lo contrario habrían provocado que la aplicación finalizara), pero no funciona para la que estás preguntando, que es CTRL_CLOSE_EVENT , generada por el sistema cuando el usuario cierra la ventana de la consola.

Honestamente, no sé cómo prevenir eso. Incluso la muestra en el SDK no le permite ignorar el CTRL_CLOSE_EVENT . Lo probé en una pequeña aplicación de prueba, y emite un pitido cuando cierras la ventana e imprime el mensaje, pero el proceso aún termina.

Quizás lo más preocupante es que la documentación me hace pensar que no es posible evitar esto:

El sistema genera CTRL_CLOSE_EVENT , CTRL_LOGOFF_EVENT y CTRL_SHUTDOWN_EVENT cuando el usuario cierra la consola, cierra la sesión o apaga el sistema para que el proceso tenga la oportunidad de borrarse antes de la finalización. Es posible que las funciones de la consola, o cualquier función de C en tiempo de ejecución que llama a las funciones de la consola, no funcionen de manera confiable durante el procesamiento de cualquiera de las tres señales mencionadas anteriormente. La razón es que algunas o todas las rutinas de limpieza de la consola interna pueden haber sido llamadas antes de ejecutar el controlador de señal de proceso.

Es esa última frase que me llama la atención. Si el subsistema de la consola comienza a limpiarse inmediatamente después de sí mismo en respuesta al usuario que intenta cerrar la ventana, puede que no sea posible detenerlo después del hecho.

(Al menos ahora entiendes el problema. ¡Tal vez alguien más pueda venir con una solución!)

Lamentablemente, no hay nada que puedas hacer para alterar realmente este comportamiento.

Las ventanas de la consola son “especiales” porque están alojadas en otro proceso y no permiten la subclasificación. Esto limita su capacidad de modificar su comportamiento.

Por lo que sé, tus dos opciones son:

1. Desactive el botón de cerrar por completo. Puedes hacer esto con el siguiente fragmento de código:

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

2. Deje de usar consolas e implemente su propia solución de salida de texto.

La opción n. ° 2 es la opción más complicada pero le brindará el mayor control. Encontré un artículo en CodeProject que implementa una aplicación tipo consola que usa un rico control de edición para mostrar el texto (los controles de edición enriquecidos tienen la capacidad de transmitir texto como la consola, por lo que son adecuados para este tipo de aplicaciones).

Al cerrar la ventana de la consola obtenida utilizando AllocConsole o AttachConsole , el proceso asociado se cerrará. No hay escapatoria de eso.

Antes de Windows Vista, al cerrar la ventana de la consola, se presentaba un diálogo de confirmación para que el usuario le preguntara si el proceso debería finalizar o no, pero Windows Vista y más adelante no ofrecen ningún diálogo de este tipo y el proceso finaliza.

Una solución posible para evitar esto es evitar AttachConsole por completo y lograr la funcionalidad deseada a través de otros medios.

Por ejemplo, en el caso descrito por OP, la ventana de la consola era necesaria para generar texto en la consola usando la clase estática de la Console .

Esto se puede lograr muy fácilmente usando la comunicación entre procesos. Por ejemplo, una aplicación de consola puede desarrollarse para actuar como un servidor de eco

 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(); } } } } 

y luego, en lugar de asignar / adjuntar una consola a la aplicación actual, el servidor de eco puede iniciarse desde dentro de la aplicación y Console's secuencia de salida de Console's puede redirigirse para escribir en el servidor de pipa.

 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; } } 

Esta es solo una de las posibles soluciones. Básicamente, la solución consiste en asignar la ventana de la consola a su propio proceso para que pueda finalizar sin afectar el proceso principal.

Quería mostrar el resultado de un script de IronPython en una consola que quería controlar desde mi progtwig. Así que lo hice de forma similar a como se ha propuesto aquí, pero si hubiera varios scripts, habría necesitado varias ventanas de consola y de acuerdo con http://msdn.microsoft.com/en-us/library/windows/desktop/ms681944 (v = vs.85). Los progtwigs de Windows .aspx no pueden tener más de un shell (¡Qué vergüenza, Microsoft!).

Este fue el factor decisivo. Tuve que descartar la solución y ahora estoy redireccionando la salida de IronPython a una clase de ventana escrita por mí mismo que simula un shell. Ver http://www.codeproject.com/Articles/335909/Embedding-a-Console-in-aC-Application

Use FreeConsole en lugar de AllocConsole

Definir..

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

Uso..

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