前三章涵蓋服務架構、安裝、啟動、基本控制處理。本章聚焦**高階應用開發者必須面對的核心難題**:服務停止時的競態條件、控制處理程序與服務主執行緒的正確協作模式、狀態回報時機、避免「服務無回應」誤判。內容基於實戰驗證,極具實務價值。
架構回顧:
CtrlHandler),接收停止/暫停/繼續等控制碼。**必須極速返回** (毫秒級),否則阻塞同進程其他服務的控制處理。ServiceMain),執行實際業務邏輯。錯誤做法:在 CtrlHandler 直接呼叫 TerminateThread 強殺服務執行緒 → 資源洩漏、DLL 無通知、資料損毀。
正確做法:CtrlHandler **通知** 服務執行緒停止,服務執行緒**優雅結束**並回報最終狀態。
實驗:修改服務,CtrlHandler 收到 SERVICE_CONTROL_PAUSE 立即回報 SERVICE_PAUSE_PENDING,啟動新執行緒 Sleep 5 秒後回報 SERVICE_PAUSED。在兩個命令提示字元視窗快速連續執行 net pause MyService。
SERVICE_PAUSE_PENDING (第一次請求) SERVICE_PAUSE_PENDING (第二次請求!服務已在暫停中卻又收到請求) SERVICE_PAUSED (第一次完成) SERVICE_PAUSED (第二次完成)
根因:SCM **不阻擋** 重複控制碼。net.exe 繞過「服務」管理工具的 UI 禁用邏輯,直接送請求給 SCM。SCM 轉發給 CtrlHandler,服務未同步狀態導致重複處理。
// 全域/類別成員
enum ServiceState { RUNNING, PAUSING, PAUSED, STOPPING, STOPPED };
volatile ServiceState g_desiredState = RUNNING; // 原子變數或 Interlocked
HANDLE g_hStateChangeEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 手動重設事件
VOID WINAPI CtrlHandler(DWORD dwControl) {
switch (dwControl) {
case SERVICE_CONTROL_PAUSE:
if (InterlockedCompareExchange((LONG*)&g_desiredState, PAUSING, RUNNING) == RUNNING)
SetEvent(g_hStateChangeEvent); // 通知服務執行緒
break;
case SERVICE_CONTROL_CONTINUE:
if (InterlockedCompareExchange((LONG*)&g_desiredState, RUNNING, PAUSED) == PAUSED)
SetEvent(g_hStateChangeEvent);
break;
case SERVICE_CONTROL_STOP:
if (InterlockedCompareExchange((LONG*)&g_desiredState, STOPPING, RUNNING) == RUNNING ||
InterlockedCompareExchange((LONG*)&g_desiredState, STOPPING, PAUSED) == PAUSED)
SetEvent(g_hStateChangeEvent);
break;
case SERVICE_CONTROL_INTERROGATE:
// 立即回報當前狀態
SetServiceStatus(g_hStatus, &g_status);
break;
}
// **不在此呼叫 SetServiceStatus 回報 PENDING 狀態** (見原則二)
}
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv) {
// 註冊 CtrlHandler、初始化狀態結構...
g_status.dwCurrentState = SERVICE_START_PENDING;
SetServiceStatus(g_hStatus, &g_status);
// 初始化業務資源...
g_status.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(g_hStatus, &g_status);
// 主迴圈:等待狀態變更事件
while (g_desiredState != STOPPING) {
DWORD wait = WaitForSingleObject(g_hStateChangeEvent, 1000); // 定期輪詢業務邏輯
ResetEvent(g_hStateChangeEvent);
// 處理業務邏輯 (非阻塞、可中斷)
DoWorkIteration();
// 檢查並執行狀態轉換
switch (g_desiredState) {
case PAUSING:
PauseWork(); // 停止接收新工作、等待進行中工作完成
g_status.dwCurrentState = SERVICE_PAUSED;
SetServiceStatus(g_hStatus, &g_status);
g_desiredState = PAUSED; // 更新為穩定狀態
break;
case RUNNING: // 從 PAUSED 回復
ResumeWork();
g_status.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(g_hStatus, &g_status);
break;
case STOPPING:
// 迴圈條件會結束,跳出執行清理
break;
}
}
// 清理資源、關閉控制代碼、釋放記憶體...
g_status.dwCurrentState = SERVICE_STOPPED;
g_status.dwWin32ExitCode = NO_ERROR;
SetServiceStatus(g_hStatus, &g_status); // 最後一次回報
}
InterlockedCompareExchange 確保控制碼冪等。SetEvent,微秒級返回,不阻塞 SCM。PENDING 狀態由服務執行緒在**真正開始處理**時回報,而非控制碼到達時虛報。SERVICE_CONTROL_USER+X) 只需擴充狀態機。嘗試在 CtrlHandler 直接 SuspendThread 服務執行緒以實現「暫停」:**極度危險**。
SuspendThread 後無法喚醒處理停止邏輯,只能 TerminateThread (再次違反原則)。結論:暫停 = 業務層面停止接收新工作 + 等待進行中工作完成,**非執行緒層面暫停**。
SCM 發送控制碼後,預期服務在合理時間內 (預設 ~30 秒,可由 dwWaitHint 延長) 回報新狀態。若服務主執行緒忙於長時間運算/阻塞 I/O,無法處理 g_hStateChangeEvent,SCM 彈出「服務未回應」對話框。
WSAPoll/select)、運算切分為小單元並定期 WaitForSingleObject(event, 0) 檢查狀態。WaitForSingleObject(g_hStateChangeEvent, INFINITE) 處理狀態轉換並呼叫 SetServiceStatus,主執行緒專心業務。但需妥善同步共享資料。PENDING 時設定 dwWaitHint = 60000 (60秒) + dwCheckPoint 遞增,告知 SCM 仍在處理中。
#include
#include
#include
#define SVC_NAME TEXT("MyDemoService")
#define SVC_DISPLAY TEXT("My Demo Service")
SERVICE_STATUS_HANDLE g_hStatus = NULL;
SERVICE_STATUS g_status = {0};
HANDLE g_hStopEvent = NULL;
HANDLE g_hStateChangeEvent = NULL;
volatile LONG g_desiredState = 0; // 0=RUNNING, 1=STOPPING
VOID WINAPI LogEvent(LPCTSTR fmt, ...) {
HANDLE h = RegisterEventSource(NULL, SVC_NAME);
if (h) {
TCHAR buf[1024]; va_list ap; va_start(ap, fmt); _vstprintf(buf, fmt, ap); va_end(ap);
LPCTSTR msgs[1] = { buf };
ReportEvent(h, EVENTLOG_INFORMATION_TYPE, 0, 0, NULL, 1, 0, msgs, NULL);
DeregisterEventSource(h);
}
}
VOID WINAPI CtrlHandler(DWORD ctrl) {
switch (ctrl) {
case SERVICE_CONTROL_STOP:
if (InterlockedCompareExchange(&g_desiredState, 1, 0) == 0)
SetEvent(g_hStateChangeEvent);
break;
case SERVICE_CONTROL_INTERROGATE:
SetServiceStatus(g_hStatus, &g_status);
break;
}
}
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv) {
g_hStatus = RegisterServiceCtrlHandler(SVC_NAME, CtrlHandler);
if (!g_hStatus) return;
g_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
g_status.dwCurrentState = SERVICE_START_PENDING;
g_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
g_status.dwCheckPoint = 1; g_status.dwWaitHint = 30000;
SetServiceStatus(g_hStatus, &g_status);
g_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
g_hStateChangeEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
// 初始化...
LogEvent(TEXT("Service initializing..."));
Sleep(2000); // 模擬初始化
g_status.dwCurrentState = SERVICE_RUNNING;
g_status.dwCheckPoint = 0; g_status.dwWaitHint = 0;
SetServiceStatus(g_hStatus, &g_status);
LogEvent(TEXT("Service started."));
// 主迴圈
while (g_desiredState == 0) {
DWORD wait = WaitForSingleObject(g_hStateChangeEvent, 1000);
if (wait == WAIT_OBJECT_0) ResetEvent(g_hStateChangeEvent);
// DoWork()...
}
// 清理
LogEvent(TEXT("Service stopping..."));
CloseHandle(g_hStopEvent);
CloseHandle(g_hStateChangeEvent);
g_status.dwCurrentState = SERVICE_STOPPED;
g_status.dwWin32ExitCode = NO_ERROR;
SetServiceStatus(g_hStatus, &g_status);
LogEvent(TEXT("Service stopped."));
}
void InstallService() {
SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
TCHAR path[MAX_PATH]; GetModuleFileName(NULL, path, MAX_PATH);
SC_HANDLE svc = CreateService(scm, SVC_NAME, SVC_DISPLAY, SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
path, NULL, NULL, NULL, NULL, NULL);
CloseServiceHandle(svc); CloseServiceHandle(scm);
}
int _tmain(int argc, TCHAR* argv[]) {
if (argc == 2 && _tcscmp(argv[1], TEXT("install")) == 0) {
InstallService(); return 0;
}
SERVICE_TABLE_ENTRY dispatch[] = { { SVC_NAME, ServiceMain }, { NULL, NULL } };
StartServiceCtrlDispatcher(dispatch);
return 0;
}
InterlockedCompareExchange + 單一執行緒狀態機,消除競態。SERVICE_STOPPED 回報前,所有資源必須釋放、控制代碼關閉、記憶體歸還。遵循此架構,您的服務將在壓力測試、快速重複控制、系統關機、叢集容錯移轉等極端場景下保持穩定、可控、可觀測。