I want to run some save routines when the computer suspends. Therefore I use the OnPowerChange-Event to detect when it suspends and resumes. Unfortunately me save routine needs 3-5 seconds to execute.
When I receive the suspend event the computer shuts down within 1-2 seconds and my routine isn't executed completely.
How can I prevent the suspend until my routine is finished?
SystemEvents.PowerModeChanged += OnPowerChange;
private void OnPowerChange(object s, PowerModeChangedEventArgs e)
{
switch (e.Mode)
{
case PowerModes.Resume:
switchEdifier(true);
break;
case PowerModes.Suspend:
switchEdifier(false);
break;
}
}
There are some unmanaged APIs that can help with this, specifically ShutdownBlockReasonCreate and ShutdownBlockReasonDestroy.
Its important to note that these two functions must be paired, when you call one, you have to make sure you call the other one (for example, in the event of an exception), otherwise the shutdown may be blocked indefinitely.
This will cause a dialog to show up telling the user what programs are blocking shutdown, and a reason for it. Its important that you get your work done quickly and get out, because the user has the option of hitting the "Force Shutdown" button, which they often use.
Here is an example using it:
[DllImport("user32.dll", SetLastError=true)]
static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);
[DllImport("user32.dll", SetLastError=true)]
static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);
//The following needs to go in a Form class, as it requires a valid window handle
public void BlockShutdownAndSave()
{
//If calling this from an event, you may need to invoke on the main form
//because calling this from a thread that is not the owner of the Handle
//will cause an "Access Denied" error.
try
{
ShutdownBlockReasonCreate(this.Handle, "You need to be patient.");
//Do your saving here.
}
finally
{
ShutdownBlockReasonDestroy(this.Handle);
}
}
Short strings for the reason are encouraged as the user typically won't read long messages. Something that grabs attention, like "Saving data" or "Flushing changes to disk". Just be mindful of the "do it anyway I'm an impatient user" button.
Related
Why does the following code sometimes causes an Exception with the contents "CLIPBRD_E_CANT_OPEN":
Clipboard.SetText(str);
This usually occurs the first time the Clipboard is used in the application and not after that.
This is caused by a bug/feature in Terminal Services clipboard (and possible other things) and the .NET implementation of the clipboard. A delay in opening the clipboard causes the error, which usually passes within a few milliseconds.
The solution is to try multiple times within a loop and sleep in between.
for (int i = 0; i < 10; i++)
{
try
{
Clipboard.SetText(str);
return;
}
catch { }
System.Threading.Thread.Sleep(10);
}
Actually, I think this is the fault of the Win32 API.
To set data in the clipboard, you have to open it first. Only one process can have the clipboard open at a time. So, when you check, if another process has the clipboard open for any reason, your attempt to open it will fail.
It just so happens that Terminal Services keeps track of the clipboard, and on older versions of Windows (pre-Vista), you have to open the clipboard to see what's inside... which ends up blocking you. The only solution is to wait until Terminal Services closes the clipboard and try again.
It's important to realize that this is not specific to Terminal Services, though: it can happen with anything. Working with the clipboard in Win32 is a giant race condition. But, since by design you're only supposed to muck around with the clipboard in response to user input, this usually doesn't present a problem.
I know this question is old, but the problem still exists. As mentioned before, this exception occurs when the system clipboard is blocked by another process. Unfortunately, there are many snipping tools, programs for screenshots and file copy tools which can block the Windows clipboard. So you will get the exception every time you try to use Clipboard.SetText(str) when such a tool is installed on your PC.
Solution:
never use
Clipboard.SetText(str);
use instead
Clipboard.SetDataObject(str);
I solved this issue for my own app using native Win32 functions: OpenClipboard(), CloseClipboard() and SetClipboardData().
Below the wrapper class I made. Could anyone please review it and tell if it is correct or not. Especially when the managed code is running as x64 app (I use Any CPU in the project options). What happens when I link to x86 libraries from x64 app?
Thank you!
Here's the code:
public static class ClipboardNative
{
[DllImport("user32.dll")]
private static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll")]
private static extern bool CloseClipboard();
[DllImport("user32.dll")]
private static extern bool SetClipboardData(uint uFormat, IntPtr data);
private const uint CF_UNICODETEXT = 13;
public static bool CopyTextToClipboard(string text)
{
if (!OpenClipboard(IntPtr.Zero)){
return false;
}
var global = Marshal.StringToHGlobalUni(text);
SetClipboardData(CF_UNICODETEXT, global);
CloseClipboard();
//-------------------------------------------
// Not sure, but it looks like we do not need
// to free HGLOBAL because Clipboard is now
// responsible for the copied data. (?)
//
// Otherwise the second call will crash
// the app with a Win32 exception
// inside OpenClipboard() function
//-------------------------------------------
// Marshal.FreeHGlobal(global);
return true;
}
}
Actually there could be another issue at hand. The framework call (both the WPF and winform flavors) to something like this (code is from reflector):
private static void SetDataInternal(string format, object data)
{
bool flag;
if (IsDataFormatAutoConvert(format))
{
flag = true;
}
else
{
flag = false;
}
IDataObject obj2 = new DataObject();
obj2.SetData(format, data, flag);
SetDataObject(obj2, true);
}
Note that SetDataObject is always called with true in this case.
Internally that triggers two calls to the win32 api, one to set the data and one to flush it from your app so it's available after the app closes.
I've seen several apps (some chrome plugin, and a download manager) that listen to the clipboard event. As soon as the first call hits, the app will open the clipboard to look into the data, and the second call to flush will fail.
Haven't found a good solution except to write my own clipboard class that uses direct win32 API or to call setDataObject directly with false for keeping data after the app closes.
Use the WinForms version (yes, there is no harm using WinForms in WPF applications), it handles everything you need:
System.Windows.Forms.Clipboard.SetDataObject(yourText, true, 10, 100);
This will attempt to copy yourText to the clipboard, it remains after your app exists, will attempt up to 10 times, and will wait 100ms between each attempt.
Ref. https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.clipboard.setdataobject?view=netframework-4.7.2#System_Windows_Forms_Clipboard_SetDataObject_System_Object_System_Boolean_System_Int32_System_Int32_
This happen to me in my WPF application. I got OpenClipboard Failed (Exception from HRESULT: 0x800401D0 (CLIPBRD_E_CANT_OPEN)).
i use
ApplicationCommands.Copy.Execute(null, myDataGrid);
solution is to clear the clipboard first
Clipboard.Clear();
ApplicationCommands.Copy.Execute(null, myDataGrid);
The difference between Cliboard.SetText and Cliboard.SetDataObject in WPF is that the text is not copied to the clipboard, only the pointer. I checked the source code. If we call SetDataObject(data, true) Clipoard.Flush() will also be called. Thanks to this, text or data is available even after closing the application. I think Windows applications only call Flush() when they are shutting down. Thanks to this, it saves memory and at the same time gives access to data without an active application.
Copy to clipboard:
IDataObject CopyStringToClipboard(string s)
{
var dataObject = new DataObject(s);
Clipboard.SetDataObject(dataObject, false);
return dataObject;
}
Code when app or window is closed:
try
{
if ((clipboardData != null) && Clipboard.IsCurrent(clipboardData))
Clipboard.Flush();
}
catch (COMException ex) {}
clipboardData is a window class field or static variable.
That's not a solution, just some additional information on how to reproduce it when all solutions work on your PC and fail somewhere else. As mentioned in the accepted answer - clipboard can be busy by some other app. You just need to handle this failure properly, to explain user somehow why it does not work.
So, just create a new console app with few lines below and run it. And while it is running - test your primary app on how it is handles busy clipboard:
using System;
using System.Runtime.InteropServices;
namespace Clipboard
{
class Program
{
[DllImport("user32.dll")]
private static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll")]
private static extern bool CloseClipboard();
static void Main(string[] args)
{
bool res = OpenClipboard(IntPtr.Zero);
Console.Write(res);
Console.Read();
CloseClipboard();
}
}
}
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'm attempting to pass messages between two applications - one of them is a plugin, and the other is a standalone configuration utility. When my plugin detects an event, I want to send a message to my utility and prompt the user to reconfigure.
The code I'm using is as follows:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hwnd, uint Msg, IntPtr wParam, IntPtr lParam);
private const int MESSAGE_UNAUTH = 0x401;
[... misc logic here, function def, etc]
Process[] processes = Process.GetProcessesByName("MyConfigurationApplication");
if (processes.Length > 0)
{
foreach (Process p in processes)
{
SendMessage(p.MainWindowHandle, MESSAGE_UNAUTH, IntPtr.Zero, IntPtr.Zero);
}
}
And then in my receiving process, I have the following code (I also defined MESSAGE_UNAUTH in this class):
protected override void WndProc(ref Message message)
{
if (message.Msg == MESSAGE_UNAUTH)
{
MessageBox.Show("Message received");
}
base.WndProc(ref message);
}
Things I have already verified with the debugger:
The message is getting sent. All the code in the Sender, including the SendMessage call, is executing.
The message is not getting received (obviously).
The WndProc() function is not getting called at all when the message is sent. It is, however, getting called a whole bunch of times when the configuration utility is launched (I'm assuming this is Windows' behavior).
I've gone through enough online tutorials to need eyedrops, and as far as I know, everything here is syntax-correct and "proper," but for some reason, between when I send the message and when the receiver's WndProc() should be called, black magic is happening.
Any ideas would be greatly appreciated.
Update: Using Marshal.GetLastWin32Error(), I am getting an error #1400, which appears to correspond to an invalid window handle. However, I don't see why that would be the case, since the process was found and we entered the for each loop successfully. The one caveat I can think of is that my configuration utility is presented as a taskbar icon, and doesn't necessarily have a visible window at all times - would this prevent p.MainWindowHandle from being valid? If so, how can I work around this to pass a message to the process instead of the window?
Update: Process.MainWindowHandle is 0, so it looks like that is indeed the problem - when the form in my configuration utility is not visible, no valid window handler is returned, even though my utility icon is visible in the notification bar. Is there any way I can send a message to the process, or even to the taskbar icon?
You can try to enumerate all windows associated with the process. See How to enumerate all windows belonging to a particular process using .NET?
Depending on the .NET framework you are using, this will help resolve your issues.
There was a bug in the old .NET frameworks (2.0 I think) where calling to Process.MainWindowHandle when the process starts up returns 0. Any subsequent call will also result in a 0. This is due to caching the main window handle, which should have been fixed in .NET 3.0 and later.
You might also try giving full trust to your WndProc which might help. Something like:
[System.Security.Permissions.PermissionSet( System.Security.Permissions.SecurityAction.Demand, Name="FullTrust")]
protected override void WndProc(ref Message m)
{
//...
}
On a side note, if you can change your implementation then I strongly suggest you go for better inter process communication means such as sockets, TCPChannel (which I think is replaced by WCF), named pipes...
The message might not be sent, it might be blocked. See here: When a message is blocked by UIPI the last error, retrieved with GetLastError, is set to 5 (access denied).
Use Windows Registermessage in bothe sender and receiver end will resolve the problem
Problem was that the process I was sending the message to only existed as a tooltip icon and not as an active, open window. Turns out the windows message functionality is designed for window-to-window messages, not process-to-process messages.
Solution was aforementioned kludgy system of file IO handlers.
I am using the below code to block the taskbar which is working perfectly.
But since my application is running in background, the only way to exit the application
is by killing the .exe from task manager. So while exiting like this, the blocked task bar remains at the same state. But actually it shud resume the taskbar on exiting the application.
The reason i am doing this is, it is a kiosk application.
what is the way to overcome this.
public class Taskbar
{
[DllImport("user32.dll")]
public static extern int FindWindow(string className, string windowText);
[DllImport("user32.dll")]
public static extern int ShowWindow(int hwnd, int command);
public const int SW_HIDE = 0;
public const int SW_SHOW = 1;
public int _taskbarHandle;
protected static int Handle
{
get
{
return FindWindow("Shell_TrayWnd", "");
}
}
public Taskbar()
{
_taskbarHandle = FindWindow("Shell_TrayWnd", "");
}
public static void Show()
{
ShowWindow(Handle, SW_SHOW);
}
public static void Hide()
{
ShowWindow(Handle, SW_HIDE);
}
}
Why not just use this implementation to run completely fullscreen?
http://www.codeproject.com/KB/cs/FullScreenDotNetApp.aspx
Like the others have said when you killin the application no.
Your post is a bit sparse on why you cannot close your application gracefully, so il suggest this method.
1)
Hotkeys ( http://www.codeproject.com/KB/system/Hotkeys.aspx ) that can be pressed that will close down your application gracefully. I personaly like this method, as i use hotkeys in many of my apps.
2)
Starting a seperate application that will wake up every XXX and check if the main application is running, if its not running run the Show code and then kill itself. This method is very simular to how viruses often work, so its tried and works :)
If your only way to exit is by killing it, then I am afraid you can't reset the property back to normal.
There can be a workaround to this.
Create a service which monitors your application through polling and when it finds your application as 'not running', it restores the TaskBar to normal.
There can be other similar workarounds to this but I can't think of a way of doing this from within your application given the limitation.
For exiting the application, i am registering a hot key combination and resuming the task bar and kill the process from taskbar programatically.
you can make your application check for the existance of a file (or any other thing you can control from outside and the app have access to it), if found: dispose & exit.
it's dirty but gives a little bit more control of how your application terminate than killing it from the task manager.
What is the best way to keep a console application open as long as the CancelKeyPress event has not been fired?
I would prefer to not use Console.Read or Console.ReadLine as I do not want to accept input. I just want to enable the underlying application to print to the console event details as they are fired. Then once the CancelKeyPress event is fired I want to gracefully shut down the application.
I'm assuming that "gracefully shut down the application" is the part you are struggling with here. Otherwise your application will automatically exit on ctrl-c. You should change the title.
Here's a quick demo of what I think you need. It could be refined a bit more with use of locking and Monitors for notification. I'm not sure exactly what you need though, so I'll just pose this...
class Program
{
private static volatile bool _s_stop = false;
public static void Main(string[] args)
{
Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
while (!_s_stop)
{
/* put real logic here */
Console.WriteLine("still running at {0}", DateTime.Now);
Thread.Sleep(3000);
}
Console.WriteLine("Graceful shut down code here...");
//don't leave this... demonstration purposes only...
Console.ReadLine();
}
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
//you have 2 options here, leave e.Cancel set to false and just handle any
//graceful shutdown that you can while in here, or set a flag to notify the other
//thread at the next check that it's to shut down. I'll do the 2nd option
e.Cancel = true;
_s_stop = true;
Console.WriteLine("CancelKeyPress fired...");
}
}
The _s_stop boolean should be declared volatile or an overly-ambitious optimizer might cause the program to loop infinitely.
The _s_stop boolean should be declared volatile in the example code, or an overly-ambitious optimizer might cause the program to loop infinitely.
There is already a handler bound to CancelKeyPress that terminates your application, the only reason to hook to it is if you want to intercept the event and prevent the app from closing.
In your situation, just put your app into an infinite loop, and let the built in event handler kill it. You may want to look into using something like Wait(1) or a background process to prevent it from using tons of CPU while doing nothing.
This may be what you're looking for:
http://msdn.microsoft.com/en-us/library/system.console.cancelkeypress.aspx
Simply run your program or codes without debugging, on your keyboard key in CTRL + f5 instead of F5(debugging).