2013-04-22 17 views
19

Sembra un compito impossibile. Assolutamente nulla ho trovato opere. La domanda è come chiudere in modo pulito un'applicazione console avviata con Process.Start che è stata avviata senza alcuna finestra della console e senza utilizzare shell execute: (ProcessStartInfo.CreateNoWindow = true; ProcessStartInfo.UseShellExecute = false;).Come chiudere correttamente un'app per console avviata con Process.Start?

È dato che l'applicazione che si avvia si spegne "in modo pulito" se riceve un segnale ctrl-c o ctrl-break, ma sembra che non ci sia modo di inviarne uno che funzioni (in particolare GenerateConsoleCtrlEvent).

  • Process.Kill non funziona. Lascia i file corrotti dietro a causa di un improvviso annullamento del processo da parte di .
  • Process.CloseMainWindow non funziona. In questo caso non c'è una finestra principale, quindi la funzione restituisce false e non fa nulla.
  • Chiamando EnumThreadWindows su tutti i thread per il processo e inviando un WM_CLOSE a ogni finestra non fa nulla, e non ci sono comunque finestre di thread.
  • GenerateConsoleCtrlEvent non funziona. È utile solo per i processi nello stesso gruppo (su cui .NET non ti dà alcun controllo), con un indesiderato effetto collaterale di chiudere comunque il processo di chiamata. La funzione non consente di specificare un id di processo.

Chiunque può fornire codice che accetta un oggetto "Processo" avviato con i parametri sopra che si traduce in un nuovo arresto del processo avviato senza influenzare il processo chiamante sarà contrassegnato come risposta. Usa 7z.exe (7-zip archiver) come app di console di esempio, che inizia a comprimere un file di grandi dimensioni e lascerà un file corrotto non finito dietro se non terminato in modo pulito.

Fino a quando qualcuno fornisce un esempio funzionale o un codice che porta a un esempio funzionale, questa domanda non ha risposta. Ho visto dozzine di persone a fare questa domanda e decine di risposte online, e nessuna di queste funzioni. .NET sembra non fornire supporto per chiudere in modo pulito un'applicazione console dato il suo id di processo, il che è strano considerando che è stato avviato con un oggetto .NET Process. Parte del problema è l'impossibilità di creare un processo in un nuovo gruppo di processi, il che rende inutile l'uso di GenerateConsoleCtrlEvent. Ci deve essere una soluzione a questo.

+0

Mi sembra la soluzione corretta è 'GenerateConsoleCtrlEvent'. Quindi devi solo capire come creare un processo in un nuovo gruppo. Se ciò non è possibile utilizzando il wrapper di processo .NET, passare alla funzione nativa di Win32. Nessun danno nell'usare P/Invoke, lo stai già facendo per 'GenerateConsoleCtrlEvent' comunque. –

+0

Non sto già usando GenerateConsoleCtrlEvent (non funziona), e preferirei non usare P/Invoke. La struttura SECURITY_ATTRIBUTES usata da CreateProcess ha un puntatore che mi richiede di compilare con 'unsafe', cosa che non sto facendo. Un altro problema con CreateProcess è che non riesco a utilizzare Process.EnableRaisingEvents e l'evento Exited in modo affidabile, poiché il processo potrebbe terminare prima che io abbia la possibilità di chiamare Process.GetProcessById per creare un'istanza di .NET Process per assegnare il gestore, che normalmente sarebbe fatto prima di chiamare Process.Start. Sono costretto a riscrivere la maggior parte della logica in Win32. – Triynko

+0

E non funziona comunque, come ho detto. Nonostante la creazione del processo con CREATE_NEW_PROCESS_GROUP, GenerateConsoleCtrlEvent non ha alcun effetto sul gruppo, come identificato dall'ID di processo. Ho provato a passare la costante per CTRL + C, quindi CTRL + INTERR e non funziona. Inoltre, insieme a CREATE_NEW_PROCESS_GROUP, ho provato entrambi i flag per CREATE_NO_WINDOW o DETACHED_PROCESS (separatamente, poiché si escludono a vicenda), e mentre entrambi raggiungono un processo senza console che non interferisce con la finestra della console dell'applicazione principale, GenerateConsoleCrtlEvent il metodo non funziona. – Triynko

risposta

2

Questo è un po 'tardi, quindi si potrebbe non utilizzare più, ma forse sarà aiutare gli altri ...

Stai overthinking questo. Il problema è che puoi solo segnalare un'interruzione da un processo che condivide la stessa console: la soluzione dovrebbe essere piuttosto ovvia.

Creare un progetto di console. Questo progetto lancerà l'applicazione di destinazione solito modo:

var psi = new ProcessStartInfo(); 
psi.FileName = @"D:\Test\7z\7z.exe"; 
psi.WorkingDirectory = @"D:\Test\7z\"; 
psi.Arguments = "a output.7z input.bin"; 
psi.UseShellExecute = false; 

var process = Process.Start(psi); 

UseShellExecute è la parte importante - questo assicura che le due applicazioni stanno andando a condividere la stessa console.

Questo consente di inviare la pausa per la vostra applicazione di supporto, che verrà passato all'applicazione ospitata così:

Console.CancelKeyPress += (s, e) => e.Cancel = true; 
Thread.Sleep(1000); 
GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, 0); 

Questo romperà l'applicazione ospitato un secondo dopo che è stato avviato. Facilmente, in sicurezza Il CancelKeyPress non è richiesto: l'ho messo solo lì per rendere evidente che è possibile interrompere il processo in hosting e continuare a funzionare. Nella vera applicazione di aiuto, questo potrebbe essere usato per alcune notifiche o qualcosa del genere, ma non è realmente necessario.

Ora è necessario solo un modo per segnalare all'applicazione di supporto di emettere il comando di interruzione: il modo più semplice sarebbe utilizzare semplicemente un semplice input da console, ma questo potrebbe interferire con l'applicazione ospitata. Se questo non è un'opzione per voi, una semplice mutex funzionerà bene:

using (var mutex = Mutex.OpenExisting(args[0])) 
using (var processWaitHandle = new SafeWaitHandle(process.Handle, false)) 
using (var processMre = new ManualResetEvent(false) { SafeWaitHandle = processWaitHandle }) 
{ 
    var which = WaitHandle.WaitAny(new WaitHandle[] { mutex, processMre }); 

    if (which == 0) 
    { 
     Console.WriteLine("Got signalled."); 
     GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, 0); 
    } 
    else if (which == 1) 
    { 
     Console.WriteLine("Exitted normally."); 
    } 
} 

Questo attenderà sia per un segnale sul mutex, o per l'applicazione ospitata per uscire. Per avviare l'applicazione di supporto, tutto quello che dovete fare è questo:

var mutexName = Guid.NewGuid().ToString(); 
mutex = new Mutex(true, mutexName); 

var process = Process.Start(@"TestBreak.exe", mutexName); 

E di emettere la pausa, basta rilasciare il mutex:

mutex.ReleaseMutex(); 

Questo è tutto. Se hai bisogno di un controllo più stretto, usare qualcosa come una named pipe potrebbe essere un'opzione migliore, ma se hai solo bisogno del segnale break, un mutex andrà bene. Gli argomenti che devi passare possono essere passati come argomenti all'applicazione helper e potresti persino farlo funzionare con gli script di shell (basta usare l'applicazione helper per eseguire tutto ciò che è necessario eseguire e interrompere).

0

Ho passato diverse ore a cercare di capirlo da solo. Come hai detto, il web è pieno di risposte che semplicemente non funzionano. Molte persone suggeriscono di utilizzare GenerateConsoleCtrlEvent, ma non forniscono alcun contesto, forniscono solo frammenti di codice inutili. La soluzione seguente utilizza GenerateConsoleCtrlEvent, ma funziona. L'ho provato

Si noti che questa è un'app WinForms e il processo che sto avviando e fermando è FFmpeg. Non ho provato la soluzione con nient'altro. Sto usando qui FFmpeg per registrare un video e salvare l'output in un file chiamato "video.mp4".

Il seguente codice è il contenuto del mio file Form1.cs. Questo è il file che Visual Studio crea per te quando crei una soluzione WinForms.

using System; 
using System.ComponentModel; 
using System.Diagnostics; 
using System.Runtime.InteropServices; 
using System.Threading; 
using System.Windows.Forms; 

namespace ConsoleProcessShutdownDemo { 
    public partial class Form1 : Form { 

    BackgroundWorker worker; 
    Process currentProcess; 

    public Form1() { 
     InitializeComponent(); 
    } 

    private void Worker_DoWork(object sender, DoWorkEventArgs e) { 
     const string outFile = "video.mp4"; 

     var info = new ProcessStartInfo(); 
     info.UseShellExecute = false; 
     info.CreateNoWindow = true; 
     info.FileName = "ffmpeg.exe"; 
     info.Arguments = string.Format("-f gdigrab -framerate 60 -i desktop -crf 0 -pix_fmt yuv444p -preset ultrafast {0}", outFile); 
     info.RedirectStandardInput = true; 

     Process p = Process.Start(info); 

     worker.ReportProgress(-1, p); 
    } 

    private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { 
     currentProcess = (Process)e.UserState; 
    } 

    private void btnStart_Click(object sender, EventArgs e) { 
     btnStart.Enabled = false; 
     btnStop.Enabled = true; 

     worker = new BackgroundWorker(); 

     worker.WorkerSupportsCancellation = true; 
     worker.WorkerReportsProgress = true; 
     worker.DoWork += Worker_DoWork; 
     worker.ProgressChanged += Worker_ProgressChanged; 

     worker.RunWorkerAsync(); 

    } 

    private void btnStop_Click(object sender, EventArgs e) { 
     btnStop.Enabled = false; 
     btnStart.Enabled = true; 

     if (currentProcess != null) 
      StopProgram(currentProcess); 
    } 





    //MAGIC BEGINS 


    [DllImport("kernel32.dll", SetLastError = true)] 
    static extern bool AttachConsole(uint dwProcessId); 

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

    [DllImport("kernel32.dll", SetLastError = true)] 
    private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId); 

    [DllImport("Kernel32", SetLastError = true)] 
    private static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add); 

    enum CtrlTypes { 
     CTRL_C_EVENT = 0, 
     CTRL_BREAK_EVENT, 
     CTRL_CLOSE_EVENT, 
     CTRL_LOGOFF_EVENT = 5, 
     CTRL_SHUTDOWN_EVENT 
    } 

    private delegate bool HandlerRoutine(CtrlTypes CtrlType); 

    public void StopProgram(Process proc) { 

     int pid = proc.Id; 

     FreeConsole(); 

     if (AttachConsole((uint)pid)) { 

      SetConsoleCtrlHandler(null, true); 
      GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0); 

      Thread.Sleep(2000); 

      FreeConsole(); 

      SetConsoleCtrlHandler(null, false); 
     } 

     proc.WaitForExit(); 

     proc.Close(); 
    } 


    //MAGIC ENDS 
} 

}

Problemi correlati