How to make a console app exit gracefully when it is closed - c#

I wrote this little program to demonstrate the point of the question:
using System;
using System.IO;
using System.Threading;
class Program
{
static void Main(string[] args)
{
using (var disp = new MyDisp())
{
using (var ewhLocalExit = new EventWaitHandle(false, EventResetMode.ManualReset))
{
Console.WriteLine("Enter Ctrl+C to terminate the app.");
Console.CancelKeyPress += (_, e) =>
{
e.Cancel = true;
ewhLocalExit.Set();
};
ewhLocalExit.WaitOne();
}
}
File.AppendAllText("Log.txt", "Terminated.\n");
}
}
class MyDisp : IDisposable
{
public MyDisp()
{
File.AppendAllText("Log.txt", "Started.\n");
}
public void Dispose()
{
File.AppendAllText("Log.txt", "Disposed.\n");
}
}
When I run it and press Ctrl+C, I see "Started.Disposed.Terminated." in Log.txt
When I run it and close it with the mouse, I see only "Started."
How do I make exit gracefully, so that I at least could see "Disposed." ?

You can use DLLImport to import SetConsoleControlHandler and use it to register an event handler for the closed event (and others), here's an example snippet that shows it working (it will write closed in Log.txt whem you click the X to close the console):
class Program
{
[DllImport("Kernel32")]
public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);
// A delegate type to be used as the handler routine
// for SetConsoleCtrlHandler.
public delegate bool HandlerRoutine(CtrlTypes CtrlType);
// An enumerated type for the control messages
// sent to the handler routine.
public enum CtrlTypes
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
{
if (ctrlType == CtrlTypes.CTRL_CLOSE_EVENT)
File.AppendAllText(#"Log.txt", "closed");
return true;
}
private static void Main(string[] args)
{
SetConsoleCtrlHandler(new HandlerRoutine(ConsoleCtrlCheck), true);
Console.WriteLine("Close me");
Console.ReadLine();
}
}
Source

When you close your console app with the mouse by clicking the X button you are asking to have the process killed.
The Wi32 api has a SetConsoleControlHandler that allows you to specify a handler for when various things happen. If the handler is called with CTRL_CLOSE_EVENT then you know that someone is trying to kill your application.
There's an example of how to use this API here

Related

How to share static event from static class with multiple threads

I am writing a C# Script control (WinForms). This is based on Dockpanelsuite, Scintilla, CSharpScriptingLibrary and a lot of code I wrote/collected in the last year. I want to include a prebuild "Logging" and a "ProgressBar" feature. So far I got everything to work as expected, except the fact it wont work correctly when using Timers.
The code written in the editor is compiled at runtime, the "Programm"-type is loaded and the "Main"-method is invoked. If the created assembly includes a "Log"-type the "LogMessage"-event will be connected to a method in the hosting script control, which forwards the messages to the Output-window.
As you can see the "Hello World" message is written to the Output-window, but each time the timer fires the LogMessage event in the static Log-Class is null.
Any idea what I have to change to share the LogMessage event from the UI thread with the Timer thread?
Programm.cs
using System;
using System.Timers;
namespace ScriptControl
{
public class Programm
{
Timer timer = null;
int step = 0, steps = 10;
public void Main()
{
Log.Write("Hello World");
try
{
timer = new Timer();
timer.Interval = 1000;
timer.Elapsed += TimerHandler;
timer.Enabled = true;
}
catch(Exception ex)
{
Log.Write(ex);
}
}
private void TimerHandler(object sender, object args)
{
if(this.step == 1) timer.Enabled = false;
Log.Write(step++.ToString());
Progress.Set(this.step, steps);
}
}
}
Log.cs
public delegate void LogHandler(object message);
public static class Log
{
public static event LogHandler LogMessage;
public static void Write(object message)
{
if(LogMessage != null)
LogMessage(message);
else
System.Windows.Forms.MessageBox.Show("LogMessage is null");
}
}

How to monitor another .exe and perform action if it closes

Currently have a program that opens another program. I need to perform an action should that program close. I am pretty new to C# so could use some help getting me pointed in the correct direction.
EDIT: So I am able to get the current form to open the external program. This form then needs to close and another form needs to have a function to perform an action should the loader.exe close. This is what I have in the first form. How do I code the other form to know if this program has ended.
public static class Program
{
public static bool OpenManager { get; set; }
public static int DeskNumber { get; set; }
/// The main entry point for the application.
[STAThread]
static void Main(string[] arguments)
{
/// Do not move this!
OpenManager = false;
MYvariable = 0;
/// ----------------
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Loader());
if (OpenManager)
{
if (MYvariable == 1)
{
System.Diagnostics.Process startProgram = System.Diagnostics.Process.Start("loader.exe", "-r 52");
startProgram.EnableRaisingEvents = true;
Application.Run(new Manager());
Assuming you use the Process class to start the other program, you can subscribe to the Exited event of the Process instance:
Process myProcess = Process.Start("myprogram.exe", "arguments");
myProcess.EnableRaisingEvents = true;
myProcess.Exited += (sender, e) =>
{
// Do what you want to do when the process exited.
}
Or, to do it more explicit, declare an explicit event handler that is called when the process finishes:
public void OnProcessExited(object sender, EventArgs e)
{
// Do what you want to do when the process exited.
}
public void StartProcess()
{
Process myProcess = Process.Start("myprogram.exe", "arguments");
myProcess.EnableRaisingEvents = true;
myProcess.Exited += OnProcessExited;
}
if you are using the Process class within c# to open and keep a track on the opened program you can handle its exited event like so:
App.Exited += AppOnExited;
private static void AppOnExited(object sender, EventArgs eventArgs)
{
// Do your action here
}
App being the name of your process object.
dont forget you may need to remove the handler when your done like so:
App.Exited -= AppOnExited;
If the application returns an int code you can use:
Process P = Process.Start("App.exe");
P.WaitForExit();
int code = P.ExitCode;
An application that has return:
public static int Main(string[] args)
{
return (int);
}
Will set P.ExitCode to the return value when it closes.

C# ProcessExit Event Handler not triggering code

I have a small function I want called when my process exits, however, upon closing (via the red exit button) the function will not execute.
My code is as follows:
namespace Client
{
class Program
{
static void Main(string[] args)
{
var callback = new InstanceContext(new ClientCallback());
var client = new MyServiceClient(callback);
client.Open();
client.Register();
AppDomain.CurrentDomain.ProcessExit += (sender, EventArgs) =>
{
client.checkOut();
};
Console.WriteLine("Press a key to exit");
Console.ReadKey();
client.checkOut();
client.Close();
}
}
}
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
HandlerRoutine hr = new HandlerRoutine(InspectControlType);
SetConsoleCtrlHandler(hr, true);
Console.WriteLine("Click on Windows Close Button");
Console.ReadLine();
}
[DllImport("Kernel32")]
public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);
public delegate bool HandlerRoutine(ControlTypes CtrlType);
public enum ControlTypes
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
private static bool InspectControlType(ControlTypes ctrlType)
{
Console.WriteLine("Hello You choose to end this program.Do you really want to close? :" + ctrlType.ToString());
Console.ReadLine();
return true;
}
}
}
Source

C# Console app Environment.Exit(0) not recognised as a shutdown event

I have a simple library that overrides a console's shutdown events to complete various tasks before closing itself. This works whenever a user or external program closes the console (ctrl+C, close window, etc), however, when the console app uses Environment.Exit() it closes without being recognised as one of the shutdown events.
Here is the code for the shutdown handler:
namespace ShutdownLibrary
{
public class ConsoleHandler
{
public bool ExitSystem = false; // Optional assistance for implementation.
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);
private delegate bool EventHandler(CtrlType sig);
private EventHandler _handler;
/// <summary>
/// Windows events to listen for.
/// </summary>
public enum CtrlType
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT = 1,
CTRL_CLOSE_EVENT = 2,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT = 6,
}
private bool Handler(CtrlType sig)
{
//Old .NET threading
var result = Task.Factory.StartNew<bool>(ShutdownTasks);
result.Wait();
return result.Result;
}
/// <summary>
/// Starts an EventHandler for console windows & calls overridable ShutdownTasks() when console is closed by any means.
/// </summary>
public void StartShutdownEventHandler()
{
_handler += new EventHandler(Handler);
SetConsoleCtrlHandler(_handler, true);
}
/// <summary>
/// Overridable method for tasks to action before the program is disposed.
/// </summary>
/// <returns>True when complete. False when not implemented.</returns>
public virtual bool ShutdownTasks()
{
ExitSystem = false;
return false;
}
}
}
The app itself consumes the shutdown handler (ConsoleHandler) like this:
class ManagementServer : ServerManager
{
internal class ShutdownManager : ConsoleHandler
{
public override bool ShutdownTasks()
{
base.ShutdownTasks();
for (var i = 5; i >= 0; i--)
{
Thread.Sleep(1000);
Log.WriteToLog(string.Format("Management Server Shutting down in {0}", i));
}
Console.WriteLine("Good bye!");
return true;
}
}
//Override from ServerManager
public override void ShutDownManagementServer()
{
base.ShutDownManagementServer();
Environment.Exit(0);
}
static void Main(string[] args)
{
var sm = new ShutdownManager();
sm.StartShutdownEventHandler();
var manager = new ManagementServer();
manager.StartServer();
}
}
I think the CtrlType enum in the ConsoleHandler class is the main culprit, but I can't seem to figure out what values are missing.
Any ideas?
Exit does not perform a graceful shutdown. Exit is similar to you pulling the power cord of your PC, whereas start->shutdown is graceful and informs the running apps the system is shutting down.
When you issue the Exit command, no message is sent to the running apps. You need to use application.Shutdown instead.
see what's difference between Environment.Exit() and Application.Shutdown()?
edit: sorry for not noticing it was a console app. You can try handling the AppDomain.ProcessExit event instead. But honestly I do not think this event work. This event is known not to trigger if a rude exit command (like end task) is issued. Exit is very rude.

Stuck on GenerateConsoleCtrlEvent in C# with console apps

I'm having the hardest time trying to get this to work, hoping one of you has done this before.
I have a C# console app that is running a child process which inherits its console. I want a ctrl-c caught by the outer app to be passed along to the inner app so that it can have a chance to shut down nicely.
I have some very simple code. I start a Process, then poll it with WaitForExit(10). I also have a CancelKeyPress handler registered, which sets a bool to true when it fires. The polling loop also checks this, and when it's true, it calls GenerateConsoleCtrlEvent() (which I have mapped through pinvoke).
I've tried a lot of combinations of params to GenerateConsoleCtrlEvent(). 0 or 1 for the first param, and either 0 or the child process's ID for the second param. Nothing seems to work. Sometimes I get a false back and Marshal.GetLastWin32Error() returns 0, and sometimes I get true back. But none cause the child app to receive a ctrl-c.
To be absolutely sure, I wrote a test C# app to be the child app which prints out what's going on with it and verified that manually typing ctrl-c when it runs does properly cause it to quit.
I've been banging my head against this for a couple hours. Can anyone give me some pointers on where to go with this?
Not so sure this is a good approach. This only works if the child process is created with the CREATE_NEW_PROCESS_GROUP flag for CreateProcess(). The System.Diagnostics.Process class however does not support this.
Consider using the return value from the Main() method. There is already a unique value defined in the Windows SDK for Ctrl+C aborts, STATUS_CONTROL_C_EXIT or 0xC000013A. The parent process can get that return code from the Process.ExitCode property.
Did you have any luck with this? My understanding is that when you press CTRL+C in a console, by default all the processes attached to the console receive it, not just the parent one. Here's an example:
Child.cs:
using System;
public class MyClass
{
public static void CtrlCHandler(object sender, ConsoleCancelEventArgs args)
{
Console.WriteLine("Child killed by CTRL+C.");
}
public static void Main()
{
Console.WriteLine("Child start.");
Console.CancelKeyPress += CtrlCHandler;
System.Threading.Thread.Sleep(4000);
Console.WriteLine("Child finish.");
}
}
Parent.cs:
using System;
public class MyClass
{
public static void CtrlCHandler(object sender, ConsoleCancelEventArgs args)
{
Console.WriteLine("Parent killed by CTRL+C.");
}
public static void Main()
{
Console.CancelKeyPress += CtrlCHandler;
Console.WriteLine("Parent start.");
System.Diagnostics.Process child = new System.Diagnostics.Process();
child.StartInfo.UseShellExecute = false;
child.StartInfo.FileName = "child.exe";
child.Start();
child.WaitForExit();
Console.WriteLine("Parent finish.");
}
}
Output:
Y:\>parent
Parent start.
Child start.
Parent killed by CTRL+C.
Child killed by CTRL+C.
^C
Y:\>parent
Parent start.
Child start.
Child finish.
Parent finish.
So I wouldn't have thought you'd need to do anything special. However, if you really need to generate CTRL+C events yourself, things might not be so easy. I'm not sure about the problems you describe, but as far as I can tell you can only send CTRL+C events to all the processes attached to a console window. If you detach a process, you can't send it CTRL+C events. If you want to be selective in which processes to send the CTRL+C events, you seem to need to create new console windows for every one. I've no idea if there's some way to do it without visible windows or when you want to redirect I/O using pipes.
Here is my solution for sending ctrl-c to a process. FYI, I never got GenerateConsoleCtrlEvent to work.
Rather than using GenerateConsoleCtrlEvent, here is how I have found to send CTRL-C to a process. FYI, in this case, I didn't ever need to find the group process ID.
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
public class ConsoleAppManager
{
private readonly string appName;
private readonly Process process = new Process();
private readonly object theLock = new object();
private SynchronizationContext context;
private string pendingWriteData;
public ConsoleAppManager(string appName)
{
this.appName = appName;
this.process.StartInfo.FileName = this.appName;
this.process.StartInfo.RedirectStandardError = true;
this.process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
this.process.StartInfo.RedirectStandardInput = true;
this.process.StartInfo.RedirectStandardOutput = true;
this.process.EnableRaisingEvents = true;
this.process.StartInfo.CreateNoWindow = true;
this.process.StartInfo.UseShellExecute = false;
this.process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
this.process.Exited += this.ProcessOnExited;
}
public event EventHandler<string> ErrorTextReceived;
public event EventHandler ProcessExited;
public event EventHandler<string> StandartTextReceived;
public int ExitCode
{
get { return this.process.ExitCode; }
}
public bool Running
{
get; private set;
}
public void ExecuteAsync(params string[] args)
{
if (this.Running)
{
throw new InvalidOperationException(
"Process is still Running. Please wait for the process to complete.");
}
string arguments = string.Join(" ", args);
this.process.StartInfo.Arguments = arguments;
this.context = SynchronizationContext.Current;
this.process.Start();
this.Running = true;
new Task(this.ReadOutputAsync).Start();
new Task(this.WriteInputTask).Start();
new Task(this.ReadOutputErrorAsync).Start();
}
public void Write(string data)
{
if (data == null)
{
return;
}
lock (this.theLock)
{
this.pendingWriteData = data;
}
}
public void WriteLine(string data)
{
this.Write(data + Environment.NewLine);
}
protected virtual void OnErrorTextReceived(string e)
{
EventHandler<string> handler = this.ErrorTextReceived;
if (handler != null)
{
if (this.context != null)
{
this.context.Post(delegate { handler(this, e); }, null);
}
else
{
handler(this, e);
}
}
}
protected virtual void OnProcessExited()
{
EventHandler handler = this.ProcessExited;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
protected virtual void OnStandartTextReceived(string e)
{
EventHandler<string> handler = this.StandartTextReceived;
if (handler != null)
{
if (this.context != null)
{
this.context.Post(delegate { handler(this, e); }, null);
}
else
{
handler(this, e);
}
}
}
private void ProcessOnExited(object sender, EventArgs eventArgs)
{
this.OnProcessExited();
}
private async void ReadOutputAsync()
{
var standart = new StringBuilder();
var buff = new char[1024];
int length;
while (this.process.HasExited == false)
{
standart.Clear();
length = await this.process.StandardOutput.ReadAsync(buff, 0, buff.Length);
standart.Append(buff.SubArray(0, length));
this.OnStandartTextReceived(standart.ToString());
Thread.Sleep(1);
}
this.Running = false;
}
private async void ReadOutputErrorAsync()
{
var sb = new StringBuilder();
do
{
sb.Clear();
var buff = new char[1024];
int length = await this.process.StandardError.ReadAsync(buff, 0, buff.Length);
sb.Append(buff.SubArray(0, length));
this.OnErrorTextReceived(sb.ToString());
Thread.Sleep(1);
}
while (this.process.HasExited == false);
}
private async void WriteInputTask()
{
while (this.process.HasExited == false)
{
Thread.Sleep(1);
if (this.pendingWriteData != null)
{
await this.process.StandardInput.WriteLineAsync(this.pendingWriteData);
await this.process.StandardInput.FlushAsync();
lock (this.theLock)
{
this.pendingWriteData = null;
}
}
}
}
}
Then, in actually running the process and sending the CTRL-C in my main app:
DateTime maxStartDateTime = //... some date time;
DateTime maxEndDateTime = //... some later date time
var duration = maxEndDateTime.Subtract(maxStartDateTime);
ConsoleAppManager appManager = new ConsoleAppManager("myapp.exe");
string[] args = new string[] { "args here" };
appManager.ExecuteAsync(args);
await Task.Delay(Convert.ToInt32(duration.TotalSeconds * 1000) + 20000);
if (appManager.Running)
{
// If stilll running, send CTRL-C
appManager.Write("\x3");
}
For details, please see Redirecting standard input of console application and Windows how to get the process group of a process that is already running?

Categories