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

Windows 服務程式開發全攻略:從零構建一個可安裝的服務

     編輯:關於C語言

Windows 服務基礎概念

  • 無 GUI、長期運行、開機自啟、可暫停/繼續。
  • 運行於獨立 Session 0(Vista+),隔離用戶交互。
  • 可指定服務帳戶(LocalSystem、NetworkService、LocalService、自定義域帳號)。
  • 狀態機:SERVICE_STOPPED → START_PENDING → RUNNING → STOP_PENDING → STOPPED,以及 PAUSE_PENDING/PAUSED/CONTINUE_PENDING。

五大核心步驟

  1. main():建立 SERVICE_TABLE_ENTRY 分派表,呼叫 StartServiceCtrlDispatcher() 進入 SCM 控制循環。
  2. ServiceMain():服務主執行緒。註冊控制處理器 RegisterServiceCtrlHandler(),回報 SERVICE_START_PENDING → 初始化 → 回報 SERVICE_RUNNING → 進入工作迴圈。
  3. CtrlHandler():接收 SCM 控制碼(STOP, SHUTDOWN, PAUSE, CONTINUE, INTERROGATE),更新狀態、通知工作迴圈退出。
  4. 工作迴圈:定期執行業務邏輯(輪詢、定時器、I/O 完成埠等),檢查全域停止旗標。
  5. 安裝/移除:使用 sc.exe 或 CreateService() API 註冊至 SCM 資料庫。

最小可行程式碼 (Visual Studio 2008+ 驗證)


#include <windows.h>
#include <stdio.h>

#define SLEEP_MS 5000
#define LOG_FILE "C:\\svc_log.txt"

SERVICE_STATUS g_status = {0};
SERVICE_STATUS_HANDLE g_status_handle = NULL;
volatile BOOL g_running = FALSE;

void write_log(const char *msg) {
    FILE *f; fopen_s(&f, LOG_FILE, "a+");
    if (f) { fprintf(f, "%s\n", msg); fclose(f); }
}

VOID WINAPI CtrlHandler(DWORD ctrl) {
    switch (ctrl) {
        case SERVICE_CONTROL_STOP:
        case SERVICE_CONTROL_SHUTDOWN:
            g_running = FALSE;
            g_status.dwCurrentState = SERVICE_STOP_PENDING;
            SetServiceStatus(g_status_handle, &g_status);
            break;
        default: break;
    }
}

VOID WINAPI ServiceMain(DWORD argc, LPSTR *argv) {
    g_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    g_status.dwCurrentState = SERVICE_START_PENDING;
    g_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
    g_status_handle = RegisterServiceCtrlHandler("MyDemoSvc", CtrlHandler);
    if (!g_status_handle) { write_log("RegisterHandler failed"); return; }

    write_log("Service starting...");
    g_status.dwCurrentState = SERVICE_RUNNING;
    SetServiceStatus(g_status_handle, &g_status);

    g_running = TRUE;
    MEMORYSTATUSEX mem = { .dwLength = sizeof(mem) };
    char buf[128];
    while (g_running) {
        GlobalMemoryStatusEx(&mem);
        sprintf_s(buf, sizeof(buf), "Avail RAM: %llu MB", mem.ullAvailPhys / 1024 / 1024);
        write_log(buf);
        Sleep(SLEEP_MS);
    }
    write_log("Service stopped.");
    g_status.dwCurrentState = SERVICE_STOPPED;
    SetServiceStatus(g_status_handle, &g_status);
}

int main() {
    SERVICE_TABLE_ENTRY dispatch[] = {
        { "MyDemoSvc", (LPSERVICE_MAIN_FUNCTION)ServiceMain },
        { NULL, NULL }
    };
    if (!StartServiceCtrlDispatcher(dispatch)) {
        // 若直接雙擊執行,會失敗(錯誤 1063),此時可作為主控台測試邏輯
        return GetLastError();
    }
    return 0;
}

安裝、啟動、查詢、停止、移除


rem 管理員權限 CMD
sc create MyDemoSvc binPath= "C:\full\path\MyDemoSvc.exe" start= auto
sc start MyDemoSvc
sc query MyDemoSvc
sc stop MyDemoSvc
sc delete MyDemoSvc

注意:binPath= 後有空格,路徑含空格需雙引號包裹。

進階主題

  • 事件日誌:RegisterEventSource/ReportEvent 寫入系統日誌,替代文字檔。
  • 服務恢復:sc failure 設定失敗自動重啟、執行指令。
  • 會話 0 隔離:服務若需 UI,需用「互動式服務」或獨立 GUI 進程通訊(Named Pipe/RPC/TCP)。
  • C++ 封裝:CServiceBase 類別封裝狀態機、執行緒池、配置熱載入,參考 Microsoft ATL 服務範本或開源 winsvcpp。

常見坑點

  1. 主控台直接跑報錯:StartServiceCtrlDispatcher 只有在 SCM 啟動時才成功。開發期可加 if (argc>1 && strcmp(argv[1],"-console")==0) run_console_mode();。
  2. 工作迴圈阻塞:不可在 ServiceMain 做耗時同步 I/O,應另開執行緒或用非阻塞/IOCP。
  3. 權限不足:寫入 C:\ 需管理員權限,建議用 %ALLUSERSPROFILE%\MySvc\logs。
  4. 狀態回報延遲:SetServiceStatus 必須及時,否則 SCM 判定服務掛起並強制終止。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved