Windows 服務架構核心組件
- 服務控制管理器 (SCM):管理服務資料庫、啟動/停止/查詢服務、維護服務狀態。
- 服務主程式:Console 應用程式,入口點
main() → StartServiceCtrlDispatcher 連接 SCM。
- 服務控制程式:GUI 應用程式,呼叫
OpenSCManager、CreateService、StartService、ControlService 管理服務生命週期。
核心 SCM API 速查表
| API | 用途 | 關鍵參數 |
OpenSCManager | 打開 SCM 資料庫 | 機器名、資料庫名、存取權限 (SC_MANAGER_CREATE_SERVICE 等) |
CreateService | 建立服務 | 服務名、顯示名、類型 (SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS)、啟動類型 (SERVICE_AUTO_START)、二進位路徑 |
OpenService | 打開既有服務 | 服務名、存取權限 |
StartService | 啟動服務 | 服務句柄、啟動參數 |
ControlService | 發送控制碼 | SERVICE_CONTROL_STOP/PAUSE/CONTINUE/INTERROGATE/SHUTDOWN |
QueryServiceStatus | 查詢服務狀態 | 填充 SERVICE_STATUS 結構 |
DeleteService | 刪除服務 | 需先停止服務 |
服務主程式三大核心函數
1. main():連接 SCM
void WINAPI ServiceMain(DWORD, LPTSTR*); // 前向宣告
void WINAPI Handler(DWORD); // 前向宣告
int main() {
SERVICE_TABLE_ENTRY ste[2] = {
{ "MyService", ServiceMain },
{ NULL, NULL } // 結束標記
};
StartServiceCtrlDispatcher(ste); // 阻塞直到服務停止
return 0;
}
2. ServiceMain():服務真正入口,註冊 Handler、報告狀態、執行業務
SERVICE_STATUS_HANDLE g_ssh;
SERVICE_STATUS g_ss;
void WINAPI ServiceMain(DWORD argc, LPTSTR *argv) {
// 1. 註冊控制處理函數
g_ssh = RegisterServiceCtrlHandler("MyService", Handler);
// 2. 初始化狀態:啟動中
g_ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
g_ss.dwCurrentState = SERVICE_START_PENDING;
g_ss.dwControlsAccepted = 0; // 初始化階段不接受控制
g_ss.dwWin32ExitCode = NO_ERROR;
g_ss.dwCheckPoint = 0;
g_ss.dwWaitHint = 3000; // 3 秒超時
SetServiceStatus(g_ssh, &g_ss);
// 3. 執行初始化工作 (資源載入、開啟埠、連線資料庫...)
if (!InitService()) {
g_ss.dwCurrentState = SERVICE_STOPPED;
g_ss.dwWin32ExitCode = GetLastError();
SetServiceStatus(g_ssh, &g_ss);
return;
}
// 4. 進入運行狀態
g_ss.dwCurrentState = SERVICE_RUNNING;
g_ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
g_ss.dwCheckPoint = 0;
g_ss.dwWaitHint = 0;
SetServiceStatus(g_ssh, &g_ss);
// 5. 業務主迴圈 (或啟動工作線程,此處保持存活)
RunServiceLoop(); // 例如: while (g_running) { Sleep(1000); DoWork(); }
// 6. 清理與停止
CleanupService();
g_ss.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus(g_ssh, &g_ss);
}
3. Handler():處理 SCM 發送的控制碼
void WINAPI Handler(DWORD opcode) {
switch (opcode) {
case SERVICE_CONTROL_STOP:
g_ss.dwCurrentState = SERVICE_STOP_PENDING;
g_ss.dwCheckPoint = 1;
g_ss.dwWaitHint = 5000;
SetServiceStatus(g_ssh, &g_ss);
SignalStop(); // 通知主迴圈/工作線程退出
break;
case SERVICE_CONTROL_PAUSE:
g_ss.dwCurrentState = SERVICE_PAUSED;
SetServiceStatus(g_ssh, &g_ss);
PauseWork();
break;
case SERVICE_CONTROL_CONTINUE:
g_ss.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(g_ssh, &g_ss);
ResumeWork();
break;
case SERVICE_CONTROL_INTERROGATE:
SetServiceStatus(g_ssh, &g_ss); // 立即回報當前狀態
break;
case SERVICE_CONTROL_SHUTDOWN:
// 系統關機,同 STOP 但時間更緊
Handler(SERVICE_CONTROL_STOP);
break;
}
}
服務控制程式 (GUI) 實現要點
// 安裝服務
void InstallService() {
SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
SC_HANDLE svc = CreateService(scm, "MyService", "My Service",
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START, // 自動啟動
SERVICE_ERROR_NORMAL,
"C:\\Path\\MyService.exe", // 絕對路徑!
NULL, NULL, NULL, NULL, NULL);
CloseServiceHandle(svc); CloseServiceHandle(scm);
}
// 啟動服務
void StartService() {
SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
SC_HANDLE svc = OpenService(scm, "MyService", SERVICE_START);
StartService(svc, 0, NULL);
CloseServiceHandle(svc); CloseServiceHandle(scm);
}
// 停止服務
void StopService() {
SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
SC_HANDLE svc = OpenService(scm, "MyService", SERVICE_STOP | SERVICE_QUERY_STATUS);
SERVICE_STATUS ss;
ControlService(svc, SERVICE_CONTROL_STOP, &ss);
CloseServiceHandle(svc); CloseServiceHandle(scm);
}
// 移除服務
void RemoveService() {
SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
SC_HANDLE svc = OpenService(scm, "MyService", DELETE);
SERVICE_STATUS ss;
ControlService(svc, SERVICE_CONTROL_STOP, &ss); // 先停止
DeleteService(svc);
CloseServiceHandle(svc); CloseServiceHandle(scm);
}
C++Builder 專屬專案設置
- 專案類型:Console Application (服務主程式) / VCL Forms Application (控制程式)。
- 連結選項:服務主程式需連結
advapi32.lib (C++Builder 自動處理)。
- 進入點:服務主程式保留
main(),非 WinMain。
- 互動式服務:
SERVICE_INTERACTIVE_PROCESS 允許服務顯示 UI (Win2000/XP 有效,**Vista+ Session 0 隔離導致 UI 不顯示**,生產環境**不建議**依賴)。
- 除錯技巧:在
main() 開頭加 if (IsDebuggerPresent()) DebugBreak(); 或命令列參數區分 MyService.exe -debug 以 Console 模式運行業務邏輯,便於除錯。
常見坑與避坑
| 問題 | 原因 | 解決 |
| 服務啟動後立即停止 (1053 錯誤) | ServiceMain 未在 30 秒內回報 RUNNING | 初始化耗時工作放入工作線程,ServiceMain 快速回報 RUNNING |
| 控制程式無法安裝/啟動服務 | 權限不足 (需管理員)、路徑含空格未加引號、服務名衝突 | 以管理員運行、路徑加雙引號、檢查 GetLastError() |
| 服務無法存取網路資源/列印機 | 預設 LocalSystem 帳號無網路憑證 | CreateService 指定 lpServiceStartName 為網域帳號、或服務內用 WNetAddConnection2 |
| Session 0 隔離導致 UI 不顯示 | Vista+ 服務運行於 Session 0,無法與用戶桌面互動 | 改用「服務 + 用戶端 GUI 通訊 (Named Pipe/RPC/TCP)」架構 |
| 服務停止不乾淨,資源洩漏 | Handler 未正確通知工作線程、主迴圈未檢查停止旗標 | 用 Event/原子旗標通知、工作線程定期檢查、CleanupService 釋放所有資源 |
現代化建議:C++17/20 + 獨立庫
- 將業務邏輯封裝為 **無依賴的靜態庫/動態庫**,服務主程式僅作薄適配層。
- 使用
std::thread、std::atomic、std::condition_variable 替代 Win32 同步物件。
- 日誌採用 **spdlog** 等非同步日誌庫,支援滾動、壓縮、遠端傳送。
- 配置檔用 **TOML/JSON** (nlohmann/json),命令列解析用 **CLI11**。
- 單元測試覆蓋業務邏輯,服務主程式僅做整合測試。
總結
- Windows 服務 = **SCM 管理的長期運行進程**,核心三函數:
main → StartServiceCtrlDispatcher、ServiceMain、Handler。
- 服務主程式**必須快速回報狀態**:
START_PENDING → RUNNING,耗時初始化放背景線程。
- 控制程式透過 **SCM API** 完成安裝/啟動/停止/移除全生命週期管理。
- C++Builder 開發服務:**Console 專案 + advapi32.lib**,注意 Session 0 隔離、管理員權限、路徑處理。
- 現代架構:**業務邏輯庫化 + 薄適配層 + 非同步日誌 + 標準庫同步**,提升可測試性與可維護性。