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

Windows 服務開發深度解析 (二):ServiceMain 與 StartServiceCtrlDispatcher 協作機制

     編輯:VC 教學

從入口到運行:服務進程的啟動編排

上一篇概述了服務基礎概念,本文深入核心細節:main() 如何初始化服務表、StartServiceCtrlDispatcher 如何與 SCM 協作、每個服務實例如何在獨立線程中運行 ServiceMain。掌握這一流程是編寫穩健多服務進程的關鍵。

關鍵結構:SERVICE_TABLE_ENTRY

定義進程內包含的所有服務名稱與對應的 ServiceMain 入口:

typedef struct _SERVICE_TABLE_ENTRY {
    LPSTR lpServiceName;        // 服務名稱 (註冊表中 ServiceName)
    LPSERVICE_MAIN_FUNCTION lpServiceProc; // ServiceMain 函數指針
} SERVICE_TABLE_ENTRY;

// 範例:單進程承載兩個服務
SERVICE_TABLE_ENTRY dispatchTable[] = {
    { "MyFTPd",   FtpdMain },   // FTP 服務入口
    { "MyHttpd",  HttpdMain },  // HTTP 服務入口
    { NULL, NULL }              // 結束標記 (必須)
};

核心啟動函數:StartServiceCtrlDispatcher

BOOL StartServiceCtrlDispatcher(LPSERVICE_TABLE_ENTRY lpServiceStartTable);

執行流程詳解

  1. 進程啟動:SCM (services.exe) 以 `CreateProcess` 啟動服務進程映像。
  2. 主線程義務:進程**主線程**必須**儘快** (最遲 2 分鐘內,建議 < 1 秒) 調用 StartServiceCtrlDispatcher。超時 SCM 會 `TerminateProcess` 殺死進程。
  3. 建立調度線程:StartServiceCtrlDispatcher 為表中**每個非 NULL 項目**創建一個**新線程**,各線程開始執行對應的 ServiceMain 函數。
  4. 主線程進入調度迴路:調用線程 (原主線程) **不返回**,進入內部迴路:
    • **等待狀態**:掛起等待兩類事件:
while (TRUE) {
    // 1. SCM 發送控制通知給本進程某服務 → 喚醒
    // 2. 某服務狀態變更需回報 (內部機制) → 喚醒
    
    // 喚醒後:
    // - 識別目標服務
    // - 調用該服務的 CtrlHandler 處理控制碼
    // - CtrlHandler 返回 → 循環繼續等待
}

ServiceMain 簽名與職責

VOID WINAPI ServiceMain(DWORD dwArgc, LPSTR* lpszArgv);
// dwArgc: 啟動參數個數 (來自註冊表 ImagePath 或 SCM 啟動參數)
// lpszArgv: 參數字符串陣列 (argv[0] = 服務名)

ServiceMain 必做四件事

  1. 註冊控制處理器:g_statusHandle = RegisterServiceCtrlHandler(lpszArgv[0], CtrlHandler); 失敗即返回。
  2. 初始化狀態結構:設定 dwServiceType, dwControlsAccepted 等。
  3. 回報啟動進度:SERVICE_START_PENDING → 逐步 dwCheckPoint → 最終 SERVICE_RUNNING。
  4. 進入服務主迴路:執行核心業務 (監聽端口、輪詢任務、等待事件等),直到收到停止信號。

多服務進程的線程模型圖解

進程主線程 (main)
    │
    ├─► StartServiceCtrlDispatcher() ──► 進入調度迴路 (永久阻塞)
    │       │
    │       ├─► 服務 A 線程 ──► ServiceMain("MyFTPd") ──► FTP 核心迴路
    │       │       │
    │       │       └─► CtrlHandler_A (處理 FTP 服務控制碼)
    │       │
    │       ├─► 服務 B 線程 ──► ServiceMain("MyHttpd") ──► HTTP 核心迴路
    │       │       │
    │       │       └─► CtrlHandler_B (處理 HTTP 服務控制碼)
    │       │
    │       └─► ... 更多服務線程
    │
    └─► (無其他代碼執行)

常見陷阱與解決方案

問題現象根因解決方案
服務啟動後立即停止,事件日誌記 1053 錯誤 (ERROR_SERVICE_REQUEST_TIMEOUT)主線程未在 2 分鐘內調用 StartServiceCtrlDispatcher,或 ServiceMain 過早返回main() 第一時間調用 Dispatcher;ServiceMain 必須含無限迴路或等待事件,不可直接返回
無法停止服務,SCM 強制終止CtrlHandler 未及時回報 STOP_PENDING / STOPPED,或工作線程未響應停止信號CtrlHandler 立即回報 PENDING;用事件/標誌通知工作線程;工作線程定期檢查退出標誌
多服務進程中,僅第一個服務啟動DispatchTable 結束標記 {NULL,NULL} 缺失或位置錯誤確保陣列以 {NULL,NULL} 結尾,且前面所有項目 Name/Proc 均非 NULL
服務參數 (argv) 為空或錯誤註冊表 ImagePath 格式錯誤,或 SCM 傳遞方式誤解ImagePath 應為 `"C:\Path\Svc.exe" "param1" "param2"`;ServiceMain 從 lpszArgv[1] 開始取業務參數

最小化可運行服務框架代碼 (單服務)

// svc.cpp
#include 
#include 

SERVICE_STATUS_HANDLE g_hStatus = NULL;
SERVICE_STATUS g_status = {0};
HANDLE g_hStopEvent = NULL;

VOID WINAPI CtrlHandler(DWORD dwCtrl) {
    if (dwCtrl == SERVICE_CONTROL_STOP) {
        g_status.dwCurrentState = SERVICE_STOP_PENDING;
        g_status.dwCheckPoint = 1;
        g_status.dwWaitHint = 5000;
        SetServiceStatus(g_hStatus, &g_status);
        SetEvent(g_hStopEvent); // 通知工作線程
    }
}

VOID WINAPI ServiceMain(DWORD argc, LPSTR* argv) {
    g_hStatus = RegisterServiceCtrlHandler("MyDemoSvc", CtrlHandler);
    if (!g_hStatus) return;
    
    g_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    g_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
    g_status.dwCurrentState = SERVICE_START_PENDING;
    g_status.dwCheckPoint = 1;
    SetServiceStatus(g_hStatus, &g_status);
    
    g_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    // ... 初始化資源 (Socket, DB, 執行緒池) ...
    
    g_status.dwCurrentState = SERVICE_RUNNING;
    g_status.dwCheckPoint = 0;
    SetServiceStatus(g_hStatus, &g_status);
    
    WaitForSingleObject(g_hStopEvent, INFINITE); // 核心等待
    
    // ... 清理資源 ...
    g_status.dwCurrentState = SERVICE_STOPPED;
    SetServiceStatus(g_hStatus, &g_status);
}

int main() {
    SERVICE_TABLE_ENTRY table[] = {
        { "MyDemoSvc", ServiceMain },
        { NULL, NULL }
    };
    StartServiceCtrlDispatcher(table); // 永不返回
    return 0;
}

總結

StartServiceCtrlDispatcher 是服務進程的「心臟起搏器」:它將單一主線程進程轉化為**多線程服務容器**,每個服務擁有獨立線程運行 ServiceMain,主線程轉型為**控制調度器**負責分發 SCM 指令至對應 CtrlHandler。理解這一「一主多從、調度分發」模型,是診斷服務啟動失敗、停止超時、多服務干擾等疑難雜症的基石。

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