基於命令列的程式在當今各個作業系統上都應用得非常廣泛,但是這些程式沒有GUI,所以使用者要終止它們時通常都是按"Ctrl+C"或用系統命令直接結束進程,這樣可能會造成資料丟失或者一些特殊資源沒有釋放,因此須要編寫一段代碼在程式被終止前作一些自己的結束工作。
ANSI C已經提供了一個標準函數signal來捕獲命令列的中斷訊號。
這是該函數在Linux下GCC中的原形:
typedef
void
(
*
sighandler_t)(
int
);
sighandler_t signal(
int
signum, sighandler_t handler);
這是該函數在Microsoft C中的原形:
void
(__cdecl
*
signal(
int
sig,
void
(__cdecl
*
func ) (
int
[,
int
] ))) (
int
);
以上兩個函式宣告表面上看是乎不同,其實是一樣的。
下面介紹該函數的用法。
第一個參數signum為要捕獲的訊號值,ANSI C定義了以下訊號值。
SIGABRT |
程式調用了abort函數 |
SIGFPE |
浮點數錯誤 |
SIGILL |
違例的調用 |
SIGINT |
使用者按了CTRL+C |
SIGSEGV |
違例的記憶體訪問 |
SIGTERM |
使用者調用了exit函數 |
第二個參數handler是一個指向捕獲訊號回呼函數的指標,如果產生了signum指定的訊號將執行handler指向的回呼函數。
如果singal函數執行成功則返回一個指向上次指定的訊號捕獲函數的指標,如果執行失敗則返回SIG_ERR。
通常要判斷程式被終止只須要捕獲SIGABRT、SIGINT和SIGTERM這三個訊號就行了。
現在來看一個例子:
#include
<
stdio.h
>
#include
<
singal.h
>
int
end
=
0
;
/**
* 中斷訊號處理函數。
*/
void
signal_handler(
int
sig )
{
switch
( sig )
{
case
SIGINT:
case
SIGABRT:
case
SIGTERM:
end
=
1
;
//
標記為結束
break
;
default
:
//
其它訊號的處理
break
;
}
}
int
main(
int
argc,
char
*
argv[] )
{
if
( signal(SIGINT,signal_handler)
==
SIG_ERR )
return
-
1
;
//
設定處理函數失敗
while
(
!
end )
//
等待結束
{
#ifdef WIN32
_sleep(
1000
);
#
else
sleep(
1
);
#endif
printf(
"
wait...
"
);
}
printf(
"
end.
"
);
return
0
;
}
將以上代碼編譯為可執行程式,運行起來然後按Ctrl+C看看有什麼效果吧。
但是要注意的是在Windows下命令列也是一個視窗,如果使用者直接把命令列視窗關閉的話是不會觸發中斷訊號的,所以Windows API提供了一個函數SetConsoleCtrlHandler用來接收Windows控制訊號。
這是該函數的原形:
BOOL WINAPI SetConsoleCtrlHandler( PHANDLER_ROUTINE HandlerRoutine, BOOL Add );
參數HandlerRoutine為接收控制訊號的回呼函數。參數Add決定是添加還是刪除回呼函數,這一點和signal函數是不同的,通過SetConsoleCtrlHandler可以添加多個接收控制訊號的回呼函數。
接收控制訊號的回呼函數原形:
BOOL WINAPI HandlerRoutine( DWORD dwCtrlType );
參數dwCtrlType為控制訊號的類型,是下列值之一。
| CTRL_C_EVENT |
使用者按了Ctrl+C。 |
| CTRL_BREAK_EVENT |
使用者按了CTRL+BREAK。 |
| CTRL_CLOSE_EVENT |
控制台視窗被關閉。 |
| CTRL_LOGOFF_EVENT |
目前使用者登出。 |
| CTRL_SHUTDOWN_EVENT |
系統關閉。 |
如果回呼函數返回非0值,系統將不再處理這個控制訊號。如果回呼函數返回0,系統就將控制訊號交給下一個回呼函數處理。
SetConsoleCtrlHandler函數和signal的用法大致相同,這裡就不多說了。