我正在尝试通过Windows上的关闭按钮检查控制台何时关闭。我读到了SetConsoleCtrlHandler,我以为我会使用它,但我想在主函数中做一些清理。我将举一个小例子来描述我想为更大的程序做些什么。
BOOL CtrlHandler( DWORD fdwCtrlType )
{
switch( fdwCtrlType )
{
//Cleanup exit
case CTRL_CLOSE_EVENT:
bool* programIsOn = &???; //How do I pass the address to that variable in this function?
*programIsOn = false;
return( TRUE );
default:
return FALSE;
}
}
int main(){
MyObject obj = new MyObject();
bool programIsOn = true;
//How do I pass the address of programIsOn here?
if(!SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE )){
cout << "Could not set CtrlHandler. Exiting." << endl;
return 0;
}
while(programIsOn){
//...
}
//CLEANUP HERE
delete obj;
return 0;
}
我希望在我的程序通过控制台关闭事件关闭时执行清理,但是如果我只是关闭控制台,则main函数不会终止并被强制停止。我想把programIsOn的地址传递给CtrlHandler回调,但我不知道如何在不使用全局变量的情况下这样做。
答案 0 :(得分:5)
TL; DR:正确处理此控制信号很复杂。除非绝对必要,否则不要打扰任何“清理”。
系统在您的应用程序中创建一个新线程(请参阅Remarks),然后使用该线程执行您注册的处理函数。这会立即引发一些问题并迫使您走向特定的设计方向。 也就是说,你的程序突然变得多线程,带来了所有复杂性。只是在处理程序中将'program should stop'(全局)布尔变量设置为true是行不通的;这必须以线程感知的方式完成。
此处理程序带来的另一个复杂因素是,它返回程序的那一刻会根据对ExitProcess
的调用而终止。这意味着处理程序应该以线程感知的方式等待程序完成。排队下一个复杂问题,操作系统只允许10 seconds在程序终止之前响应处理程序。
我认为,最重要的问题是,所有这些问题都会迫使您的程序以非常特殊的方式进行设计,这可能会渗透到您代码的每个角落。
您的程序不必清理它使用的任何句柄,对象,锁或内存:当程序退出时,这些都将被Windows清除。 因此,清理代码应仅包含那些需要发生且不会发生的操作,例如写入日志文件的末尾,删除临时文件等。 事实上,建议不执行此类清理工作,因为它只会减慢应用程序的关闭速度,并且在“意外终止”情况下很难正确处理;旧新事物有一个wonderful post,它也与这种情况有关。
这里有两种一般选择来处理剩余的清理:
问题1存在以下问题:很难确定要执行的清理(因为这取决于主程序当前正在执行的位置),并且在引擎仍在运行时它正在执行此操作。数字2表示主应用程序中的每一段代码都需要知道终止的可能性并且有短路代码来处理这样的代码。
因此,如果您真的必须,必然,绝对地执行一些额外的清理,请选择方法2.添加一个全局变量,如果C ++ 11可用,最好是std::atomic<bool>
,并使用它来跟踪程序是否应该退出。让处理程序将其设置为true
// Shared global variable to track forced termination.
std::atomic<bool> programShouldExit = false;
// In the console handler:
BOOL WINAPI CtrlHandler( DWORD fdwCtrlType )
{
...
programShouldExit = true;
Sleep(10000); // Sleep for 10 seconds; after this returns the program will be terminated if it hasn't already.
}
// In the main application, regular checks should be made:
if (programShouldExit.load())
{
// Short-circuit execution, such as return from function, throw exception, etc.
}
您可以选择自己喜欢的短路方法,例如抛出异常并使用RAII模式来保护资源。 在控制台处理程序中,只要我们认为可以逃脱,我们就会睡觉(这并不重要);希望主线程将退出然后导致应用程序退出。如果没有,则睡眠结束,处理程序返回,应用程序关闭,或操作系统变得不耐烦并终止进程。
结论:不要打扰清理。即使你喜欢做某些事情,比如删除临时文件,我建议你不要这样做。这真的不值得麻烦(但这是我的看法)。如果你真的必须,那么使用线程安全的方法通知主线程它必须退出。修改所有运行时间较长的代码以处理退出状态,并修改所有其他代码以处理运行时间较长的代码的故障。例如,可以使用例外和RAII来使其更易于管理。
这就是为什么我觉得这是一个非常糟糕的设计选择,源于遗留代码。只是能够处理“退出请求”需要你跳过箍。