服務控制管理器 (SCM) 透過 RegisterServiceCtrlHandler 註冊的回調函數 `CtrlHandler` 向服務發送控制碼。服務必須在這裡解析指令、更新狀態、協調工作線程完成相應操作。本文詳細解析所有預定義控制碼、狀態回報機制、關機處理邏輯與進階配置。
| 控制碼常數 | 數值 | 含義與要求 |
|---|---|---|
SERVICE_CONTROL_STOP | 0x0001 | 請求停止服務。需 SERVICE_STOP 權限。服務必須回報 SERVICE_STOP_PENDING 並最終轉為 SERVICE_STOPPED。 |
SERVICE_CONTROL_PAUSE | 0x0002 | 請求暫停服務。需 SERVICE_PAUSE_CONTINUE 權限。回報 SERVICE_PAUSE_PENDING → SERVICE_PAUSED。 |
SERVICE_CONTROL_CONTINUE | 0x0003 | 請求恢復暫停服務。需 SERVICE_PAUSE_CONTINUE 權限。回報 SERVICE_CONTINUE_PENDING → SERVICE_RUNNING。 |
SERVICE_CONTROL_INTERROGATE | 0x0004 | 請求立即回報當前狀態。需 SERVICE_INTERROGATE 權限。直接回報當前 dwCurrentState,dwCheckPoint=0, dwWaitHint=0。 |
SERVICE_CONTROL_SHUTDOWN | 0x0005 | 系統關機通知。服務執行最小清理,**無需回報狀態** (系統即將關閉)。預設超時 20 秒 (NT4) / 10 秒 (Win2K+),可調 HKLM\SYSTEM\CurrentControlSet\Control\WaitToKillServiceTimeout (毫秒)。 |
SERVICE_CONTROL_PARAMCHANGE | 0x0006 | Win2K+:請求重讀啟動參數。需 SERVICE_PAUSE_CONTINUE。 |
SERVICE_CONTROL_NETBINDCHANGE | 0x0007 | Win2K+:網路綁定變更,需重讀綁定資訊。 |
SERVICE_CONTROL_NETBINDREMOVE | 0x0008 | Win2K+:網路綁定組件移除,需解除綁定。 |
SERVICE_CONTROL_NETBINDENABLE | 0x0009 | Win2K+:禁用綁定被啟用,需添加綁定。 |
SERVICE_CONTROL_NETBINDDISABLE | 0x000A | Win2K+:綁定被禁用,需移除綁定。 |
| 用戶自定義 | 128-255 | 服務自定義控制碼範圍,擴展業務邏輯 (如重載配置、觸發備份、切換日誌級別)。 |
SetServiceStatus 與 SERVICE_STATUS 結構typedef struct _SERVICE_STATUS {
DWORD dwServiceType; // SERVICE_WIN32_OWN_PROCESS 等
DWORD dwCurrentState; // 當前狀態 (見下表)
DWORD dwControlsAccepted; // 接受的控制碼位掩碼
DWORD dwWin32ExitCode; // 錯誤碼 (NO_ERROR 或錯誤)
DWORD dwServiceSpecificExitCode; // 服務特定錯誤碼
DWORD dwCheckPoint; // 進度檢查點 (遞增)
DWORD dwWaitHint; // 預估等待時間 (毫秒)
} SERVICE_STATUS;
| 狀態常數 | 值 | 含義 |
|---|---|---|
SERVICE_STOPPED | 0x0001 | 已停止 |
SERVICE_START_PENDING | 0x0002 | 啟動中 |
SERVICE_STOP_PENDING | 0x0003 | 停止中 |
SERVICE_RUNNING | 0x0004 | 正在運行 |
SERVICE_CONTINUE_PENDING | 0x0005 | 恢復中 |
SERVICE_PAUSE_PENDING | 0x0006 | 暫停中 |
SERVICE_PAUSED | 0x0007 | 已暫停 |
VOID WINAPI CtrlHandler(DWORD dwControl) {
switch (dwControl) {
case SERVICE_CONTROL_STOP:
// 1. 立即回報 STOP_PENDING,告知 SCM 已收到
g_status.dwCurrentState = SERVICE_STOP_PENDING;
g_status.dwCheckPoint = 1;
g_status.dwWaitHint = 30000; // 預估 30 秒內完成
SetServiceStatus(g_statusHandle, &g_status);
// 2. 通知工作線程優雅退出 (事件/標誌/IOCP 完成端口)
SignalWorkerThreadsToStop();
// 3. (可選) 週期性更新進度
// while (WorkersStillRunning()) {
// g_status.dwCheckPoint++;
// SetServiceStatus(g_statusHandle, &g_status);
// Sleep(1000);
// }
// 4. 所有清理完成,最終回報 STOPPED
g_status.dwCurrentState = SERVICE_STOPPED;
g_status.dwCheckPoint = 0;
g_status.dwWaitHint = 0;
g_status.dwWin32ExitCode = NO_ERROR;
SetServiceStatus(g_statusHandle, &g_status);
break;
case SERVICE_CONTROL_PAUSE:
// 類似邏輯:PAUSE_PENDING → 通知線程暫停 → PAUSED
break;
case SERVICE_CONTROL_CONTINUE:
// CONTINUE_PENDING → 通知線程恢復 → RUNNING
break;
}
}
case SERVICE_CONTROL_INTERROGATE:
// 直接回報當前狀態,無需改變 CheckPoint/WaitHint
SetServiceStatus(g_statusHandle, &g_status);
break;
case SERVICE_CONTROL_SHUTDOWN:
// **不要**呼叫 SetServiceStatus 回報狀態!SCM 不會等待。
// 僅執行「最小必要清理」:刷新關鍵數據到磁碟、關閉資料庫連線、釋放獨佔資源。
// 避免:網路請求、複雜計算、等待子進程、UI 彈窗。
PerformEmergencyCheckpoint(); // 如: FlushViewOfFile, CommitTransaction
break;
WaitToKillServiceTimeout註冊表路徑:HKLM\SYSTEM\CurrentControlSet\Control
值名:WaitToKillServiceTimeout (REG_SZ,毫秒字串,預設 "10000" = 10 秒)
**注意**:此為全局設定,影響所有服務。服務自身不可程式化修改此值 (需管理員權限),但可在安裝腳本中建議調整。
// 服務啟動時聲明接受的控制碼 (含自定義 128-255)
g_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE
| SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PARAMCHANGE
| SERVICE_ACCEPT_NETBINDCHANGE
| (1 << (MY_CUSTOM_RELOAD_CONFIG - 128)); // 自定義位
// CtrlHandler 中處理
case MY_CUSTOM_RELOAD_CONFIG: // 定義為 128
ReloadConfigurationFile();
// 可選:回報 INTERROGATE 風格狀態,或不回報
break;
SetServiceStatus**,否則 SCM 視為掛起並強制終止。dwCheckPoint 遞增**、**dwWaitHint 合理估算** (含緩衝),避免 SCM 超時誤殺。SetServiceStatus。dwWin32ExitCode 用系統錯誤碼;服務私有錯誤用 dwServiceSpecificExitCode。ServiceMain 初始化階段,PARAMCHANGE 觸發重讀。