C++SetConsoleCtrlHandler,在不使用全局变量的情况下传递用于清理的数据

C++ SetConsoleCtrlHandler, passing data for cleanup without globals

本文关键字:用于 数据 情况下 C++SetConsoleCtrlHandler 全局变量      更新时间:2023-10-16

我正试图通过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;
}

当我的程序通过控制台关闭事件关闭时,我想执行清理,但是如果我只是关闭控制台,主功能不会终止,而是被迫停止。我曾想过将programIsOn的地址传递给CtrlHandler回调,但我不知道如何在不使用全局变量的情况下做到这一点。

TL;DR:正确处理此控制信号很复杂。除非绝对必要,否则不要麻烦进行任何"清理"。

系统会在应用程序中创建一个新线程(请参阅备注),然后用于执行您注册的处理程序函数。这会立即导致一些问题,并迫使您朝着特定的设计方向前进。也就是说,你的程序突然变成了多线程的,带来了所有的复杂性。在处理程序中将"program should stop"(全局)布尔变量设置为true是行不通的;这必须以线程感知的方式来完成。

这个处理程序带来的另一个复杂情况是,当它返回时,程序会根据对ExitProcess的调用而终止。这意味着处理程序应该再次以线程感知的方式等待程序完成。对下一个复杂情况进行排队,在程序终止之前,操作系统只给你10秒的时间来响应处理程序。

我认为,这里最大的问题是,所有这些问题都迫使你的程序以一种非常特殊的方式设计,这种方式可能渗透到你代码的每一个角落。

你的程序没有必要清理它使用的任何句柄、对象、锁或内存:当你的程序退出时,这些都会被Windows清理掉。因此,您的清理代码应该只包含那些需要发生的操作,否则就不会发生,例如写入日志文件的末尾、删除临时文件等。事实上,建议不要执行这样的清理,因为这只会减慢应用程序的关闭速度,而且在"意外终止"的情况下很难正确处理;《新旧事物》有一篇精彩的帖子,也与这种情况有关。

这里有两种处理剩余清理的一般选择:

  1. 处理程序例程完成所有清理,或者
  2. 主应用程序完成所有清理工作

第1个问题是,很难确定要执行什么清理(因为这取决于主程序当前执行的位置),而且它是在"发动机仍在运行时"执行的。数字2意味着主应用程序中的每一段代码都需要意识到终止的可能性,并有短路代码来处理这种情况。


因此,如果您真的必须、必须、绝对地执行一些额外的清理,请选择方法2。添加一个全局变量,最好是std::atomic<bool>(如果C++11对您可用),并使用它来跟踪程序是否应该退出。让处理程序将其设置为真正的

// 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使其更易于管理。


这就是为什么我觉得这是一个非常糟糕的设计选择,源于遗留代码。仅仅是能够处理"退出请求"就需要你克服重重困难。