C++ SetConsoleCtrlHandler,在没有全局变量的情况下传递数据进行清理

C++ SetConsoleCtrlHandler, passing data for cleanup without globals

我正在尝试通过 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: 正确处理这个控制信号很复杂。除非绝对必要,否则不要理会任何 'clean-up'。

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

此处理程序带来的另一个复杂情况是,当它 returns 程序根据对 ExitProcess 的调用而终止时。这意味着处理程序应该再次以线程感知的方式等待程序完成。排队下一个并发症,其中 OS 只给你 10 seconds 在程序终止之前响应处理程序。

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

您的程序没有必要清理它使用的任何句柄、对象、锁或内存:当您的程序退出时,这些都将被 Windows 清理。 因此,您的清理代码应该只包含那些需要发生而不会发生的操作,例如写入日志文件的末尾、删除临时文件等。 事实上,建议 不要 执行此类清理,因为它只会减慢应用程序的关闭速度,并且在 'unexpected termination' 情况下很难正确处理;旧新事物有一个关于它的 wonderful post 也与这种情况有关。

对于处理剩余清理的方式,这里有两种一般选择:

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

1 号的问题是很难确定要执行什么清理(因为这取决于主程序当前正在执行的位置)并且它正在这样做 'while the engine is still running'。数字 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 模式来保护资源。 在控制台处理程序中,只要我们认为可以逃脱,我们就会休眠(这并不重要);希望到那时主线程已经退出,导致应用程序退出。如果没有,要么睡眠结束,处理程序 returns 并关闭应用程序,要么 OS 变得不耐烦并终止进程。


结论:不要为清理而烦恼。即使有一些您喜欢做的事情,例如删除临时文件,我也建议您不要这样做。这真的不值得麻烦(但这是我的意见)。如果你真的必须,那么使用线程安全的方式来通知主线程它必须退出。修改所有 longer-运行 代码以处理退出状态和所有其他代码以处理 longer-运行 代码的失败。例如,异常和 RAII 可用于使其更易于管理。


这就是为什么我觉得这是一个非常糟糕的设计选择,源于遗留代码。仅仅能够处理 'exit request' 就需要你跳过篮球。