The requirements I'm up against
About 12 people are using this application, but we only want to allow 4 to close the application through traditional methods (Alt+F4, File > Exit, Close)
If any other method is used (TaskManager, WindowsShutdown) or one of the allowed users close the application, we need to perform some clean up (Closing out some connection channels)
The Code I've used to satisfy said requirements
private void formClosing(object sender, FormClosingEventArgs e)
{
// If a user is allowed to close the application, an empty file (filename)
// will be in the root directory of the application.
if(e.CloseReason == CloseReason.UserClosing && !File.Exists("filename"))
{
e.Cancel = true;
return;
}
// Cleanup
}
The Problem
If a user (not allowed to close) attempts to close the application through traditional methods, then attempts to close using Task Manager the CloseReason enum doesn't seem to reset itself, thus causing Task Manager to pop the prompt to force close, preventing the application from cleaning up.
The Question
Is this a bug, or am I missing something, something that will reset the CloseReason after the FormClosing event has been cancelled.
.NET Reflector is your friend when working out how WinForms is operating.
The Form class has an internal field called closeReason and this is used when generating the event parameter that you examine in the Closing event. This internal field is set in four different places that I can find. These are...
1, The Form.Close() method sets the closeReason = UserClosing.
This makes sense as making a manual call to the Form.Close() method is usually the result of some user action, such as a File->Exit menu option being selected by the user. Clearly this is a user action.
2, The WM_SYSCOMMAND (SC_CLOSE) sets the closeReason = UserClosing.
The WndProc of the Form processes the SC_CLOSE system command by setting the closeReason to UserClosing and the lets the default window proc execute and close the application. This makes sense as this SC_CLOSE is sent when the user presses the window close chrome button or selected the close option from right clicking the title bar. Both are user actions and so setting the closeReason to UserClosing appears correct.
3, WndProc processes message WM_CLOSE (0x10) with closeReason = TaskManagerClosing
WM_CLOSE is sent by task manager and other applications to close a window and if the closeReason is currently equal to None it updates it to TaskManagerClosing. Note this issue with it being updated only if it is None as I think this is a problem for you.
4, WndProc processes messages 0x11 and 0x16 with closeReason = WindowsShutDown
This is not very interesting as you do not care about this scenario but it is just standard processing of shut down messages.
So the core problem you are having is that at no point is the closeReason being reset back to None when you cancel the Closing event. Therefore point number 3 above will never correctly update the value to TaskManagerClosing if that occurs after your cancel. As the closeReasson is an internal field you cannot update it directly. But you can cheat and this is an approach I have used myself in the past. You need to use reflection to get access to the internal field and then reset it to None when you set Cancel=true in your event handler.
I have not tested this code but you need something along the lines of...
PropertyInfo pi = typeof(Form).GetProperty("CloseReason",
BindingFlags.Instance |
BindingFlags.SetProperty |
BindingFlags.NonPublic);
pi.SetValue(this, CloseReason.None, null);
I think you cannot keep your process from shutting down if it's initiated by task manager (that is, OS... he's the 'big boss', it doesn't make sense that you can deny it something like closing your program).
The next best thing is to record the state of the application, and then instantiate another instance of your process with some startup options to take over the state you left. The OS would kill your process, but you will start another one immediately.
Also, if the user clicks in TaskManager "go to process" in the app list, and from there ends process, I don't think you'll be receiving any event at all...
Maybe it would be best if you had a windows service that's running behind the scenes and that keeps track that an instance is running. This way, users probably won't be aware that such process exists since it's not their application, and you can use that keep track of application shutdown.
Related
I have two .NET applications:
parent-app.exe (WPF)
child-app.exe (WinForms)
The first application starts the second one
parent-app.exe → child-app.exe
by means of the class System.Diagnostics.Process.
When user clicks a button on the interface of the parent-app.exe, I start the process of child-app.exe immediately. Because child-app.exe is a .NET application, is takes some time before user could see the window (especially, on slow systems).
I want to show the user an intermediate (possibly dialog) window from parent-app.exe. This dialog window should say that user action is being processed and he should wait for the window of child-app.exe to show up.
Questions:
How can I check from parent-app.exe visibility state of the window of child-app.exe?
Here is the longer question. How would you implement this system of showing intermediate window by taking into account the restriction
that both programs use .NET?
As suggested here, you can try calling the Process.WaitForInputIdle method to wait for the MainWindowHandle to be created or periodically calling Process.Refresh and check if MainWindowHandle returns a valid handle. Perhaps this is enough for you, otherwise you can get additional information with the handle. In WinForms you could probably do something like this:
Form window = (Form)Control.FromHandle(process.MainWindowHandle);
I suspect there are similar solutions for other frameworks.
Update
I wrote this small sample, it is untested and I don't know if it works reliably (it may deadlock), but it should hint at some things you can try:
namespace SomeNS
{
using System.Diagnostics;
using System.Windows.Forms;
public static class SomeClass
{
static void SomeMethod()
{
Process process = Process.Start("child-app.exe");
// YourDialog is your dialog implementation inheriting from System.Windows.Window
YourDialog dlg = new YourDialog();
dlg.Loaded += (sender, e) =>
{
process.WaitForInputIdle();
Form window = (Form)Control.FromHandle(process.MainWindowHandle);
// Do something with the child app's window
dlg.Close();
};
dlg.ShowDialog();
}
}
}
I am using the following code to handle taskkill on my process:
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
private class TestMessageFilter : IMessageFilter
{
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == /*WM_CLOSE*/ 0x10)
{
MessageBox.Show("I'm shutting down");
var mailService = new MailService();
mailService.SendEmail("Test from application exit");
//Application.Exit();
return true;
}
return false;
}
}
and then
static void Main(string[] args)
{
Application.AddMessageFilter(new TestMessageFilter());
Application.Run();
}
The MessageBox pops up and the email is sent when I do taskkill /im MyProcess.exe. However this does not happen on windows shutdown.
Does Windows kill processes forcefully on shutdown or is it me who's missing something?
That you can see WM_CLOSE at all in an IMessageFilter is quite accidental and an implementation detail of taskkill.exe. You normally only see posted messages, WM_CLOSE is normally sent. I think you see taskkill.exe first trying to ask nicely, only using the sledge-hammer when the app doesn't respond fast enough. Task Manager used to do this as well but doesn't anymore in later Windows versions.
And no, it certainly can't work on a Windows shutdown. It sends a WM_QUERYENDSESSION message to a window to announce the shutdown.
Lots of good reasons to make this a service instead. But as long as you want to do it this way then you need a window to see that message. Subscribe its FormClosing event, the e.CloseReason property tells you why it is closing down. You'll see CloseReason.WindowsShutDown. You just need to keep the window invisible to keep it equivalent to what you have, override the SetVisibleCore() method as shown in this post.
The general pattern operating systems use is to request that running processes terminate gracefully, then kill them if they don't terminate within a timeout. Some applications will pop up a save changes prompt on shutdown, but they may not actually appear to the user - Windows can display a "shutting down" screen and say that some processes are still running - do you want to kill them and shut down anyway?
I believe if you're using Windows Forms you can achieve the same result as you have here with the Closing/Closed events*, so you could see if either of those fire by saving some text to a file (and make sure to flush it), and also see if you get any other message codes in PreFilterMessage in the same way.
*You could get either Closing or Closed or both, so check both. e.g. you can get a Closed without a prior Closing.
Can we work together to come up with something that works for control-c, control-break, log off, window X button pressed, etc?
Here is what I have so far:
class Program
{
private static ConsoleEventHandlerDelegate consoleHandler;
delegate bool ConsoleEventHandlerDelegate(CtrlTypes eventCode);
static void Main(string[] args)
{
consoleHandler = new ConsoleEventHandlerDelegate(ConsoleCtrlCheck);
SetConsoleCtrlHandler(consoleHandler, true);
System.Diagnostics.Process.GetCurrentProcess().Exited
+= delegate(object sender, EventArgs e)
{
GeneralManager.Stop();
};
Console.CancelKeyPress += delegate(object sender,
ConsoleCancelEventArgs e)
{
e.Cancel = false;
GeneralManager.Stop();
};
GeneralManager.Start();
}
private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
{
switch (ctrlType)
{
case CtrlTypes.CTRL_C_EVENT:
Console.WriteLine("CTRL+C received!");
GeneralManager.Stop();
break;
case CtrlTypes.CTRL_BREAK_EVENT:
isclosing = true;
Console.WriteLine("CTRL+BREAK received!");
GeneralManager.Stop();
break;
case CtrlTypes.CTRL_CLOSE_EVENT:
Console.WriteLine("Program being closed!");
GeneralManager.Stop();
break;
case CtrlTypes.CTRL_LOGOFF_EVENT:
case CtrlTypes.CTRL_SHUTDOWN_EVENT:
Console.WriteLine("User is logging off!");
GeneralManager.Stop();
break;
}
return true;
}
#region unmanaged
[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleEventHandlerDelegate
handlerProc, bool add);
public delegate bool HandlerRoutine(CtrlTypes CtrlType);
public enum CtrlTypes
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
#endregion
}
Two problems:
In the Managed Control-Break handler, if we set e.Cancel = true it fails with an exception for .Net4. This is noted in the MSDN article with no work-around: http://msdn.microsoft.com/en-us/library/system.consolecanceleventargs.cancel.aspx
I don't know how to cancel the close in the ConsoleCtrlCheck. I get a second or two to do some cleanup, but I'd rather cancel and make sure it all gets done properly.
UPDATE:
Thanks for the replies. Upvoted both. Will wait to see if anyone can come up with a reply that directly solves what I asked for, otherwise will accept one of the "use NT services" answers.
I need to wait for pending user requests to complete, disconnect them cleanly, run a few queries on the database to reflect the change(s) in state and so forth. It's a TCP server.
Then don't run it as a Console or any other kind of Client app.
Just run it as a Windows (NT) Service and the only events you'll have to worry about are Power loss and a stop signal.
Use a UPS and make sure you can close in a reasonable timespan.
I have not tried to do this kind of thing with a console app, but you may do better with a Windows Forms (or WCF app). They will give you a FormClosing event which is cancellable. Alternately, use a Windows Service if you are writing a network service, it provides an interface to cleanly stop your application.
If you are really keen on a console app, perhaps a try {} finally {} clause around all your code or something more exotic like a critical finaliser may allow you to run clean up code. But this is really not the right tool for the job.
And there are cases which you cannot prevent you app being closed, eg: power failure, or Task Manager kill command (and if an app didn't close via the X, Task Manager is the first tool I'd reach for).
So, code your service application such that all client requests are logged to a transaction log (like SQL server does). If you are unexpectedly interrupted (by whatever circumstance) anything which has happened up until that point is in the log. When your service next starts, replay that log.
One of your things to log will be "I was shutdown cleanly at time T". If you restart and don't find that item at the end of your log, you know something went wrong, and you can take whatever action is required.
If you need to know what your service is doing, use one of the many logging frameworks to pipe events to a second app, which just displays activity.
I spent couple hours looking at this and as I don't have time now to build a working code; as while it's probably short, getting it right would take a while. I'll just give you link to the various stuff that's needed to get this done:
http://pastebin.com/EzX3ezrf
Summarizing the lessons from the code in the paste:
Need a message pump to handle some/all of WM_QUERYENDSESSION, WM_ENDSESSION, CTRL_SHUTDOWN_EVENT (in c# SystemEvents.SessionEnding may cover some/all of these)
Easiest way to get a message pump is to make it a hidden form/window app, but I recall it's possible to build as a console app and add a message pump also. I didn't include that code in the paste though.
"If an application must block a potential system shutdown, it can call the ShutdownBlockReasonCreate function"
As AllocConsole is used to create the console, you need to use SetConsoleCtrlHandler and use ExitThread(1) in the handler. This is a "hack" that kills off the thread that would close the console otherwise. It's used in FarManager. see interf.cpp for example
You need to also initialize and clean up the console when using AllocConsole.
Pressing CTRL+C is reported to mess up the input. I'm not sure if FarManager is handling this scenario. There's some code in the CTRL_BREAK_EVENT handler in interf.cpp that I'm not sure what it does.
FarManager also handles WM_POWERBROADCAST, probably to do with suspending
If all that isn't enough (should be), you can also add the console into another process and IPC your messages to it like shown here. Why does closing a console that was started with AllocConsole cause my whole application to exit? Can I change this behavior?
RMTool can be used to simulate logoff/shutdown messages for testing: http://download.microsoft.com/download/d/2/5/d2522ce4-a441-459d-8302-be8f3321823c/LogoToolsv1.0.msi
MSDN has some C# code also at microsoft.win32.systemevents.sessionending.aspx
and microsoft.win32.systemevents.aspx (hidden form example)
The mischel.com/pubs/consoledotnet/consoledotnet.zip has a sample winTest project with AllocConsole being used and some of the events handled.
I have a C# winform application that during its work opens another Winform process. The other process has UI of its own.
When I close the parent application, I want the other application to be closed automatically.
How can I achieve that?
Thanks
If you are using Process.Process there is the CloseMainWindow method. If you keep a reference to the object you can use it later.
Here's the relevant page in the MSDN
and the relevant code:
// Close process by sending a close message to its main window.
myProcess.CloseMainWindow();
// Free resources associated with process.
myProcess.Close();
There are several different options. I would suggest that you have your application keep track of the processes that it starts:
private Stack<Process> _startedProcesses = new Stack<Process>();
private void StartChildProcess(string fileName)
{
Process newProcess = new Process();
newProcess.StartInfo = new ProcessStartInfo(fileName); ;
newProcess.Start();
_startedProcesses.Push(newProcess);
}
When the application closes, you can call a method that will close all started child processes that are still running. You can use this either with the Kill method or by calling the CloseMainWindow and Close methods. CloseMainWindow/Close will perform a more graceful close (if you start Notepad and there are unsaved changes, Kill will lose them, CloseMainWindow/Close will make notepad ask if you want to save):
private void CloseStartedProcesses()
{
while (_startedProcesses.Count > 0)
{
Process process = _startedProcesses.Pop();
if (process != null && !process.HasExited)
{
process.CloseMainWindow();
process.Close();
}
}
}
The most graceful way to do this is probably to send a window message to the main from of the other process. You can get the handle of this main form simply using the Process.MainWindow.Handle property (I assume you are using the Process class, and then just use the PostMessage Win API call to send a message with a custom ID to the main window of this "child" process. Then, the message loop of the other process can easily detect this message (by overriding the WndProc method) and perform a proper shutdown accordingly. An alternative would be to send the standard WM_CLOSE method, which would mean you would just have to unload the application from the handler of the Form.Closed event, but may perhaps allow you less control (over whether to cancel the shutdown in certain situations).
In WPF App.Current.SessionEnding must return in a few seconds, otherwise the "application does not respond" window appears. So the user can't be asked in this event handler to save his data, because the user's response takes longer than a few seconds.
I thought a solution would be to cancel the logoff / shutdown / restart, and resume it when the user answered to the file save dialog.
ReasonSessionEnding _reasonSessionEnding;
App.Current.SessionEnding +=
new SessionEndingCancelEventHandler(Current_SessionEnding);
void Current_SessionEnding(object sender, SessionEndingCancelEventArgs e)
{
if (_dataModified)
{
e.Cancel = true;
_reasonSessionEnding = e.ReasonSessionEnding;
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(EndSession));
}
}
void EndSession()
{
if (SaveWithConfirmation()) // if the user didn't press Cancel
//if (_reasonSessionEnding = ReasonSessionEnding.Logoff)
// logoff
//else
// shutdown or restart ?
}
The problem is that ReasonSessionEnding does not tell me if Windows was shutting down or restarting (it does not differentiate between the two).
So, what should my program do on the session ending event ?
Should it even do anything, or doing nothing on this event is the standard ?
The user is asked to save his changes in my main form's OnClosing method, so he does not lose data, but I think that the "application does not respond" window does not suggest a normal workflow.
Canceling the shutdown is not desired I guess, because some of the other programs have been shut down already.
What seems to be the accepted way is to display the save as dialog regardless.
Cancelling the shutdown, then resuming it later is most certainly not an option, for the reason you state and various others.
Since simply discarding the data is unacceptable, there really is no other options.
Well, except to save the data to a temporary file, then automatically restoring them the next time the program is run. Rather like MS Word after it has crashed. Actually, the more I consider it, the better it sounds.
Edit: There's yet another avenue, namely to save continously, the way eg. MS OneNote does. What has struck me before is that, provided you implement decent multilevel undo in your application, the whole manual saving business is actually somewhat dated - an anachronism from the days when disk operations were expensive and error-prone, nowadays mostly old habit.
But I'm digressing. Anyway, it's probably not applicable to your application, since I imagine it needs to be implemented from the beginning.