首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Windows 服務開發基礎 (一):架構核心、SCM/SCP/服務三角關係、ServiceMain/CtrlHandler 詳細解析

     編輯:關於C++

什麼是 Windows 服務?

Windows 服務 (舊稱 NT 服務) 是**無使用者介面、長時間執行、可在使用者登入前啟動、支援系統帳戶權限**的 Win32 應用程式。典型例子:資料庫引擎 (SQL Server)、Web 伺服器 (IIS)、防毒掃描器、備份代理、列印佇列、Windows Update。

服務三大核心組件

組件角色執行身分關鍵點
SCM (Service Control Manager)服務控制管理器 — 核心權威LocalSystem (services.exe)啟動/停止/暫停服務、維護註冊資料庫 (HKLM\SYSTEM\CurrentControlSet\Services)、RPC 介面支援遠端管理
服務程式實際業務邏輯 (您的 .exe)LocalSystem / NetworkService / LocalService / 指定用戶帳戶包含一或多個服務、實作 ServiceMain/CtrlHandler、向 SCM 回報狀態
SCP (Service Control Program)服務控制程式 — 管理介面互動使用者 (管理員)services.msc, sc.exe, net.exe, PowerShell, 自訂管理工具 — 透過 SCM RPC 介面操作服務

服務程式架構:一個 .exe 可載多個服務

services.exe (SCM) 啟動服務程式進程 → 進程主執行緒呼叫 StartServiceCtrlDispatcher 傳入 SERVICE_TABLE_ENTRY 陣列 (服務名稱 + ServiceMain 指標) → SCM 為**每個服務**建立一條**專屬執行緒**執行對應的 ServiceMain。因此單一進程天然多執行緒。

三大關鍵函式詳細規格

1. 進程入口點 : main() / wmain() (非 WinMain)

  • 服務無 UI,主控台子系統 (/SUBSYSTEM:CONSOLE) 即可。
  • 職責:解析命令列 (如 install, remove, debug)、建立 SERVICE_TABLE_ENTRY、呼叫 StartServiceCtrlDispatcher、等待所有服務結束、進程清理。

2. ServiceMain : 服務專屬執行緒入口


VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv) {
  // dwArgc: 參數個數 (含服務名稱)
  // lpszArgv: 參數字串陣列 (啟動參數可由 SCM 傳遞)
}

生命週期:

  1. 註冊控制處理程序:g_hStatus = RegisterServiceCtrlHandler(szServiceName, CtrlHandler)。
  2. 設定初始狀態 SERVICE_START_PENDING,呼叫 SetServiceStatus。
  3. 執行初始化 (載入設定、開啟連接埠、啟動工作執行緒、建立同步物件)。
  4. 初始化成功 → SERVICE_RUNNING;失敗 → SERVICE_STOPPED + 錯誤碼。
  5. **進入主迴圈**:等待停止事件、處理業務、定期檢查控制碼 (或由專用執行緒處理)。
  6. 收到停止通知 → 清理資源 → SERVICE_STOPPED → 返回 (執行緒結束)。

3. CtrlHandler : SCM 主執行緒呼叫 (必須極速返回)


VOID WINAPI CtrlHandler(DWORD dwControl) {
  switch (dwControl) {
    case SERVICE_CONTROL_STOP:      // 停止
    case SERVICE_CONTROL_PAUSE:     // 暫停
    case SERVICE_CONTROL_CONTINUE:  // 繼續
    case SERVICE_CONTROL_INTERROGATE: // 狀態查詢
    case SERVICE_CONTROL_SHUTDOWN:  // 系統關機
    case SERVICE_CONTROL_PARAMCHANGE: // 參數變更
    case SERVICE_CONTROL_NETBINDADD/REMOVE/ENABLE/DISABLE: // 網路綁定變更
    // 自訂控制碼: SERVICE_CONTROL_USER + 128 ~ 255
  }
  // 關鍵:僅設定旗標/觸發事件,**不**執行耗時操作、**不**直接呼叫 SetServiceStatus 回報 PENDING (見進階章節)
}

服務安裝與註冊資料庫 (HKLM\SYSTEM\CurrentControlSet\Services)

安裝本質:在上述路徑建立以服務名稱命名的子鍵,設定關鍵值:

值名稱類型說明
ImagePathREG_EXPAND_SZ服務執行檔完整路徑 (含參數),如 "C:\MySvc\Svc.exe"
DisplayNameREG_SZ顯示名稱 (services.msc 顯示)
DescriptionREG_SZ描述文字
StartREG_DWORD啟動類型: 2=AUTO, 3=DEMAND, 4=DISABLED, 0=BOOT, 1=SYSTEM
ErrorControlREG_DWORD錯誤控制: 0=IGNORE, 1=NORMAL, 2=SEVERE, 3=CRITICAL
TypeREG_DWORD服務類型: 0x10=WIN32_OWN_PROCESS, 0x20=WIN32_SHARE_PROCESS, 0x100=INTERACTIVE_PROCESS (舊)
ObjectNameREG_SZ執行帳戶: LocalSystem, NT AUTHORITY\NetworkService, NT AUTHORITY\LocalService, 域\用戶
DependOnServiceREG_MULTI_SZ相依服務名稱清單
FailureActionsREG_BINARY失敗復原動作 (重啟、執行程式、重開機)

安裝程式碼範例 (C / C++ Builder)


void InstallService(const char* svcName, const char* displayName, const char* exePath) {
  SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
  if (!hSCM) throw Exception("OpenSCManager 失敗: " + IntToStr(GetLastError()));

  SC_HANDLE hSvc = CreateService(
    hSCM, svcName, displayName, SERVICE_ALL_ACCESS,
    SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
    exePath, NULL, NULL, NULL, NULL, NULL
  );
  if (!hSvc) {
    DWORD err = GetLastError();
    CloseServiceHandle(hSCM);
    throw Exception("CreateService 失敗: " + IntToStr(err));
  }
  // 可選:設定描述
  SERVICE_DESCRIPTION sd = { (LPSTR)"My custom service description" };
  ChangeServiceConfig2(hSvc, SERVICE_CONFIG_DESCRIPTION, &sd);

  CloseServiceHandle(hSvc);
  CloseServiceHandle(hSCM);
}

服務帳戶與權限模型

帳戶類型權限範圍網路存取典型用途
LocalSystem (預設)本機完整權限 (相當於核心模式)**無**網路身分 (無法存取網路共享、SQL 遠端)本機系統服務、驅動程式、硬體抽象層
NetworkService有限本機權限 (類似 Users 群組)**有**網路身分 (電腦帳戶 DOMAIN\MACHINE$)需網路存取的服務 (IIS 應用程式集區、更新服務)
LocalService有限本機權限 (匿名登入)**無**網路身分 (匿名)不需網路、低權限原則服務
指定用戶帳戶依用戶權限依用戶權限需特定資源權限、Kerberos 委派、SQL 整合驗證

最佳實務:遵循**最小權限原則**,優先使用 NetworkService/LocalService,避免 LocalSystem 除非必要。指定用戶帳戶需管理密碼輪換 (Managed Service Accounts / Group Managed Service Accounts 可自動化)。

事件日誌記錄:服務的「黑盒子」

服務無 UI,除錯/審計全靠事件日誌。


void LogEvent(const char* fmt, ...) {
  HANDLE h = RegisterEventSource(NULL, "MyService");
  if (h) {
    char buf[1024]; va_list ap; va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap);
    LPCSTR msgs[1] = { buf };
    ReportEvent(h, EVENTLOG_INFORMATION_TYPE, 0, 0, NULL, 1, 0, msgs, NULL);
    DeregisterEventSource(h);
  }
}
  • 類型:EVENTLOG_ERROR_TYPE, WARNING_TYPE, INFORMATION_TYPE, AUDIT_SUCCESS/FAILURE。
  • 事件檢視器 → Windows Logs → Application/System 查看。
  • 生產環境建議:結構化日誌 (JSON)、集中收集 (Windows Event Forwarding, Syslog, Elastic/Logstash)。

除錯技巧:以主控台模式執行

在 main() 偵測參數 debug 或 /console,直接呼叫 ServiceMain (模擬 SCM),輸出日誌至主控台,方便單步除錯。


int main(int argc, char* argv[]) {
  if (argc > 1 && strcmp(argv[1], "debug") == 0) {
    // 模擬 SCM 環境
    SERVICE_TABLE_ENTRY table[] = { { "MyService", ServiceMain }, { NULL, NULL } };
    // 此處不呼叫 StartServiceCtrlDispatcher,直接在主執行緒跑 ServiceMain
    // 需自行處理 Ctrl+C 信號模擬停止
    ServiceMain(0, NULL);
    return 0;
  }
  if (argc > 1 && strcmp(argv[1], "install") == 0) { InstallService(...); return 0; }
  if (argc > 1 && strcmp(argv[1], "remove") == 0) { RemoveService(...); return 0; }

  SERVICE_TABLE_ENTRY table[] = { { "MyService", ServiceMain }, { NULL, NULL } };
  StartServiceCtrlDispatcher(table);
  return 0;
}

常見新手錯誤與避坑指南

錯誤現象修正
使用 WinMain / GUI 子系統服務啟動後立即結束、SCM 報錯 1053改用 main()、/SUBSYSTEM:CONSOLE
ServiceMain 內阻塞不返回SCM 認為啟動逾時 (預設 30秒),強制終止服務快速完成初始化並進入主迴圈,或設定 dwWaitHint、dwCheckPoint 定期回報
CtrlHandler 執行耗時操作同進程其他服務控制碼無回應、SCM 認為服務掛起CtrlHandler 僅設旗標/觸發事件,毫秒級返回
未呼叫 SetServiceStatus 回報 RUNNING服務永遠停留在 START_PENDING,SCM 終止初始化成功後務必回報 RUNNING
硬編碼服務名稱/路徑安裝多實例衝突、部署困難從命令列/設定檔/資源讀取
忽略 64 位元 / UAC / Session 0 隔離Vista+ 服務在 Session 0,無法互動桌面 (UI、訊息盒失效)不建立 UI、使用 WTSSendMessage 或命名管道/IPC 與使用者進程通訊

測試環境與工具鏈

  • OS: Windows 2000 / XP / 2003 / Vista / 7 / 8 / 10 / 11 (本文範例通用)。
  • 編譯器: Visual C++ 6.0 ~ VS2022, C++ Builder 5 ~ 11, MinGW, GCC。
  • 管理工具: services.msc, sc.exe (查詢/啟動/停止/設定), regedit (檢視註冊鍵值), Event Viewer (查看日誌), Process Explorer (檢視服務進程、執行緒、控制代碼)。
  • 除錯: VS 偵錯 → 附加至進程 (服務啟動後);或主控台模式直接單步。

結語與下一步

掌握 SCM/服務/SCP 三角關係、ServiceMain/CtrlHandler 分工、狀態回報流程、安裝註冊鍵值、帳戶權限模型,即具備開發穩健 Windows 服務的基礎。下一階段將深入:服務停止競態條件、暫停/繼續語意、非同步 I/O 架構 (IOCP)、恢復動作設定、事件日誌最佳實務、安裝程式整合 (WiX/Inno Setup)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved