LinuxやFreeBSDといったUNIXで、コンソールプログラムでユーザから入力された Cntl+CやTERMといったシグナルは、signal(2)でシグナルハンドラを指定できる。
しかしWindows2000以降のWindowsでは、MSDNのsignal関数のページによると、 コンソールプログラムでのSIGINT割込みはサポートされないとある。 そこで、Windows2000以降におけるSIGINTの代替になるものを探してみたところ SetConsoleCtrlHandlerという関数があった。サンプルコードを以下に示す。
#include <windows.h>
#include <wincon.h>
#include <iostream>
// ハンドラ関数
BOOL handler(DWORD ctrlChar){
if(CTRL_C_EVENT == ctrlChar){
std::cout<< "Ctrl+C pressed"<< std::endl;
return FALSE;
}
int main(void){
::SetConsoleCtrlHandler(handler, TRUE);
while(true)
::Sleep(100);
return 0;
}
やっていることは、基本的にsignal(2)と変わらない。::SetConsoleCtrlHandlerは、第二引数がTRUEであれば、第一引数で指定したハンドラを追加し、FALSEなら削除する。
ハンドラは、以下のプロトタイプを持つ。
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType);
dwCtrlTypeには、Table.1に示す割込みの種類が代入される。
| dwCtrlType | 意味 |
|---|---|
| CTRL_C_EVENT | Cntl+Cを受け取った |
| CTRL_BREAK_EVENT | Cntl+BREAKを受け取った |
| CTRL_CLOSE_EVENT | ユーザがコンソールを閉じた |
| CTRL_LOGOFF_EVENT | ユーザがログオフしようとして、システムが全コンソールプロセスに送信したシグナルを受け取った |
| CTRL_SHUTDOWN_EVENT | ユーザがシャットダウンしようとして、システムが全コンソールプロセスに送信したシグナルを受け取った |
戻り値としてFALSEを返すと、次のハンドラが呼ばれる。すなわちプロセスが強制終了される。なのでシグナルを処理するだけで終了する必要がない時は、TRUEを返す。
実際、この仕組みを使うときは終了処理がしたいときだろう。例えばハードウェアのハンドルやネットワーク接続閉じるときだろう。クラスで隠蔽されたハードウェアやネットワーク接続のインスタンスをグローバル変数にしておけば問題ないが、あんまりやりたくない。そういった時はCommandパターンを使うとよいだろう。終了処理が必要なクラスは、Terminatorクラスを継承しておく。Terminatorクラスはterminateという純粋仮想関数だけを持つクラスで、terminate関数は各クラスでオーバーライドし、終了処理を記述する。 std::list<Terminator*>型の変数をグローバル変数にしておき、Terminatorのサブクラスのインスタンスを、このグローバル変数にpushしておく。 あとはハンドラで、listに入っている全ての要素のterminate関数を呼ぶことで、必要な終了処理を行うことができる。サンプルを以下に示す。
#include <list>
class Terminator{
public:
void terminate(void) = 0;
};
typedef std::list<Terminator*> terminator_list;
terminator_list gTerminators; // Terminatorポインタのリスト
// 何か終了処理が必要なデバイス
class Device : public Terminator{
private:
HANDLE hDevice;
....
public:
// terminate関数で終了処理を記述
void terminate(void){::CloseHandle(hDevice);}
....
};
// シグナルハンドラ
BOOL WINAPI SignalHandler(DWORD dwCtrlType){
// gTerminatorsに入っている全ての要素のterminate関数を呼ぶ
for(terminator_list::iterator i=gTerminators.begin();
i != gTerminators.end(); i++)
{
(*i)->terminate();
}
}
void main(void){
// デバイスをインスタンス化
Device dev1;
Device dev2;
Device dev3;
// インスタンスのポインタをリストに入れる
gTerminators.push_back(&dev1);
gTerminators.push_back(&dev2);
gTerminators.push_back(&dev3);
// シグナルハンドラを追加
::SetConsoleCtrlHandler(SignalHandler, TRUE);
// デバイスを使って、割込みが入るまで繰り返し処理
while(true){
....
::Sleep(100);
}
}