2012-03-08 9 views
5

Il seguente programma genera un errore su "Console.ReadKey()".Come chiudere/aprire la console in C# usando le chiamate Win32?

Come posso riattivare la console dopo disabilitarlo?

using System; 
    using System.Threading; 
    using System.Runtime.InteropServices; 

    namespace ConsoleApplication 
    { 
     class Program 
     { 
      static void Main(string[] args) 
      { 
       ThreadPool.QueueUserWorkItem((o) => 
       { 
        Thread.Sleep(1000); 
        IntPtr stdin = GetStdHandle(StdHandle.Stdin); 
        CloseHandle(stdin); 
       }); 
       Console.ReadLine(); 
       Console.Write("ReadLine() successfully aborted by background thread.\n"); 
       Console.Write("[any key to exit]"); 
       Console.ReadKey(); // Throws an exception "Cannot read keys when either application does not have a console or when console input has been redirected from a file. Try Console.Read." 
      } 

      // P/Invoke: 
      private enum StdHandle { Stdin = -10, Stdout = -11, Stderr = -12 }; 
      [DllImport("kernel32.dll")] 
      private static extern IntPtr GetStdHandle(StdHandle std); 
      [DllImport("kernel32.dll")] 
      private static extern bool CloseHandle(IntPtr hdl); 
     } 
    } 

Extra per Esperti

Se vi state chiedendo, ho bisogno di essere in grado di uccidere un thread in background in esecuzione ReadLine(), all'interno di C#. Questo sembra essere l'unico modo (thread.Abort non funzionerà perché ReadLine() viene eseguito in profondità nelle viscere del sistema operativo, nel codice non gestito). Ci sono molte discussioni su questo argomento su StackOverflow, nessuno ha mai scoperto (o pubblicato) un metodo soddisfacente per interrompere Console.ReadLine(). Penso che questo codice sia sulla strada giusta, se solo potessimo riabilitare la console dopo averla disabilitata.

+0

Se chiudi la maniglia ... Dovresti aprirla manualmente. Considerando che hai appena chiuso il Gestore non c'è garanzia, puoi usare di nuovo lo stesso Handle. Sei sicuro di aver chiuso la maniglia corretta? Devo credere che ci sia un modo più semplice per abortire una Readline e poi chiudere la maniglia insanguinata dello standard input. –

+0

@Ramhound Mi piacerebbe che esistesse un modo più semplice per abortire ReadLine. Ho provato tutto - .Abort, invio chiavi all'applicazione (che funziona solo per il focus corrente), ecc. Se c'è un modo migliore, sono tutto orecchie. – Contango

+0

@Ramhound L'unico altro modo in cui posso pensare è scrivere un clone di ReadLine in C# gestito al 100%, quindi .Abort funzionerà, che è certamente più complesso rispetto a poche righe di codice Win32. – Contango

risposta

0

Non so se aiuta, ma quando ho avuto bisogno di essere in grado di scrivere alla console in una forma vittoria app, ho usato questa classe:

public class ConsoleHelper 
{ 
    /// <summary> 
    /// Allocates a new console for current process. 
    /// </summary> 
    [DllImport("kernel32.dll")] 
    public static extern Boolean AllocConsole(); 

    /// <summary> 
    /// Frees the console. 
    /// </summary> 
    [DllImport("kernel32.dll")] 
    public static extern Boolean FreeConsole(); 
} 

chiamata AllocConsole per creare una console e quindi è possibile scrivere (e leggere da) esso. Quindi chiama FreeConsole quando hai finito.

+1

Grazie per il suggerimento. Ho provato questo. Se si chiama FreeConsole(), quindi AllocConsole(), qualsiasi chiamata ReadLine() esistente genererà un'eccezione "Si è tentato di leggere o scrivere memoria protetta, che spesso indica che l'altra memoria è danneggiata.". Questa eccezione non può essere catturata da un try/catch in C#. Ha anche un effetto collaterale negativo: perde qualsiasi cronologia nella console poiché avvia effettivamente una nuova console. Mi chiedo se c'è un altro metodo? – Contango

6

Utilizzare PostMessage per inviare [ENTER] nel processo in corso:

class Program 
    { 
     [DllImport("User32.Dll", EntryPoint = "PostMessageA")] 
     private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam); 

     const int VK_RETURN = 0x0D; 
     const int WM_KEYDOWN = 0x100; 

     static void Main(string[] args) 
     { 
      Console.Write("Switch focus to another window now to verify this works in a background process.\n"); 

      ThreadPool.QueueUserWorkItem((o) => 
      { 
       Thread.Sleep(4000); 

       var hWnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle; 
       PostMessage(hWnd, WM_KEYDOWN, VK_RETURN, 0); 
      }); 

      Console.ReadLine(); 

      Console.Write("ReadLine() successfully aborted by background thread.\n"); 
      Console.Write("[any key to exit]"); 
      Console.ReadKey(); 
     } 
    } 

Questa risposta funziona anche intorno al fatto che chiamare .Abort su ReadLine() non funziona, come il codice è in esecuzione in non gestito codice in profondità nel kernel di Windows.

Questa risposta è superiore a tutte le risposte che funzionano solo se l'attuale processo ha il focus, come SendKeys e Input Simulator.

Questa risposta è superiore al metodo di chiusura dell'impugnatura della console corrente, poiché l'atto di chiudere l'handle della console corrente determina le chiamate future a ReadLine() generando un errore.

+2

Questa è la soluzione che ho preferito - grazie! –

+0

Ho usato questa soluzione, ma se esegui un progetto con CTRL + F5 (visual studio 2017) non funzionerà, se con F5 o solo eseguibile sarà ok – Nikita

Problemi correlati