前文已介紹服務啟動與 ServiceMain,本文聚焦於最後一個核心組件——CtrlHandler 回呼函數。當呼叫 RegisterServiceCtrlHandler 時,SCM 會保存此函數位址。服務控制程式(SCP)呼叫 Win32 API 要求 SCM 發送控制碼給服務,預定義控制碼如下:
| 控制碼 | 含義 |
|---|---|
| SERVICE_CONTROL_STOP | 要求服務停止。需 SERVICE_STOP 存取權。 |
| SERVICE_CONTROL_PAUSE | 要求服務暫停。需 SERVICE_PAUSE_CONTINUE 存取權。 |
| SERVICE_CONTROL_CONTINUE | 要求暫停的服務繼續。需 SERVICE_PAUSE_CONTINUE 存取權。 |
| SERVICE_CONTROL_INTERROGATE | 要求服務立即向 SCM 更新狀態。需 SERVICE_INTERROGATE 存取權。 |
| SERVICE_CONTROL_SHUTDOWN | 系統關機通知,服務執行最小清理動作。預設逾時約 20 秒(NT 4.0),Win2000 預設 10 秒,可透過登錄檔 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WaitToKillServiceTimeout(毫秒)調整。 |
| SERVICE_CONTROL_PARAMCHANGE | (Win2000) 要求服務重讀啟動參數。 |
| SERVICE_CONTROL_NETBINDCHANGE | (Win2000) 網路綁定變更通知。 |
| SERVICE_CONTROL_NETBINDREMOVE | (Win2000) 網路綁定元件移除通知。 |
| SERVICE_CONTROL_NETBINDENABLE | (Win2000) 網路綁定啟用通知。 |
| SERVICE_CONTROL_NETBINDDISABLE | (Win2000) 網路綁定停用通知。 |
此外,服務亦可接受 128~255 之間的使用者自訂控制碼。
收到 STOP、PAUSE、CONTINUE 時,必須呼叫 SetServiceStatus 確認並指定處理所需時間。例如收到停止請求:
dwCurrentState 設為 SERVICE_STOP_PENDING,讓 SCM 知悉已接收。dwCheckPoint 和 dwWaitHint 回報預估進度,必要時週期性呼叫 SetServiceStatus 更新。SetServiceStatus,將 dwCurrentState 設為 SERVICE_STOPPED,並將 dwCheckPoint、dwWaitHint 歸零。暫停/繼續同理。dwCurrentState 設為當前狀態,dwCheckPoint、dwWaitHint 歸零,呼叫 SetServiceStatus 即可。收到自訂碼時執行對應動作,除非該動作強制改變服務狀態(如觸發停止),否則不呼叫 SetServiceStatus。若 CtrlHandler 執行超過 30 秒未返回,SCM 會回報錯誤。解決方案:在 CtrlHandler 中建立新執行緒處理耗時操作,主函數迅速返回並回報 SERVICE_STOP_PENDING;新執行緒完成後再設為 SERVICE_STOPPED。若耗時可預估,仍建議用 dwCheckPoint/dwWaitHint 機制。
服務程式可呼叫 CreateService 將資訊寫入 SCM 資料庫:
SC_HANDLE CreateService(
SC_HANDLE hSCManager, // SCM 資料庫控制碼
LPCTSTR lpServiceName, // 服務名稱
LPCTSTR lpDisplayName, // 顯示名稱
DWORD dwDesiredAccess, // 存取權限
DWORD dwServiceType, // 服務類型
DWORD dwStartType, // 啟動類型
DWORD dwErrorControl, // 錯誤控制
LPCTSTR lpBinaryPathName, // 執行檔路徑
LPCTSTR lpLoadOrderGroup, // 載入順序群組
LPDWORD lpdwTagId, // 標識符(驅動程式用)
LPCTSTR lpDependencies, // 相依服務/群組
LPCTSTR lpServiceStartName, // 啟動帳號
LPCTSTR lpPassword // 帳號密碼
);
SC_HANDLE OpenSCManager(
LPCTSTR lpMachineName, // 目標電腦名稱,NULL 為本機,遠端需以 "\\" 開頭
LPCTSTR lpDatabaseName, // 資料庫名稱,通常傳 NULL(預設 SERVICES_ACTIVE_DATABASE)
DWORD dwDesiredAccess // 存取權限
);
常用權限:SC_MANAGER_ALL_ACCESS(需管理員權限)、SC_MANAGER_CONNECT、SC_MANAGER_ENUMERATE_SERVICE、SC_MANAGER_QUERY_LOCK_STATUS。返回控制碼可用 CloseServiceHandle 關閉。
SERVICE_ALL_ACCESS。SERVICE_WIN32_OWN_PROCESS(獨立進程)、SERVICE_WIN32_SHARE_PROCESS(共用進程)、SERVICE_INTERACTIVE_PROCESS(與桌面互動,不建議)。SERVICE_AUTO_START(自動)、SERVICE_DEMAND_START(手動)、SERVICE_DISABLED(停用)。另有兩項供驅動程式使用。IGNORE(記錄繼續)、NORMAL(記錄彈窗繼續)、SEVERE(嘗試最後正確配置重啟)、CRITICAL(嚴重,嘗試最後正確配置仍失敗則啟動失敗)。"d:\my share\myservice.exe" 寫成 "\"d:\my share\myservice.exe\""。NULL。SC_GROUP_IDENTIFIER 前綴。無相依傳 NULL。Domain\User 或 .\User(本機)。NULL 表使用 LocalSystem。SERVICE_INTERACTIVE_PROCESS 必須用 LocalSystem。密碼為 NULL(系統帳號)或空字串(無密碼)。服務基礎原理到此告一段落,但實務上仍有許多進階主題(如服務復原、觸發啟動、Event Tracing 等)待探討,敬請期待後續文章。