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

VC++ 開發 Windows 服務應用程式完整教程:從 SCM 操作到服務主迴圈實作

     編輯:關於VC++

前言:為什麼需要 Windows 服務?

Windows 服務(Service)在系統啟動階段即自動運行,無需使用者登入,擁有獨立的會話 0 隔離環境,適合:

  • 後台監控/資料收集/定時任務
  • 網路伺服器/代理/通訊協定處理
  • 硬體裝置監控/驅動程式輔助
  • 企業級無人值守自動化流程

本文完整示範使用 Win32 API(OpenSCManager、CreateService、RegisterServiceCtrlHandler、SetServiceStatus、StartServiceCtrlDispatcher 等)建立、安裝、啟動、控制、移除 Windows 服務的標準流程。

核心 API 架構圖

SCM (Service Control Manager)
    │
    ├── OpenSCManager() → 取得 SCM 控制碼
    ├── CreateService() → 在 SCM 資料庫註冊服務
    ├── OpenService() → 取得既有服務控制碼
    ├── ControlService() → 發送控制碼 (START/STOP/PAUSE...)
    ├── DeleteService() → 移除服務註冊
    │
    服務程式內部:
    ├── StartServiceCtrlDispatcher() → 進入服務主迴圈,接收 SCM 指令
    ├── RegisterServiceCtrlHandler() → 註冊控制處理函數 (ServiceCtrlHandler)
    ├── SetServiceStatus() → 回報服務狀態 (RUNNING/STOPPED/PENDING...)
    └── ServiceMain() → 服務實際工作執行緒

完整實作步驟與代碼

1. 專案建立:Win32 Application(無 UI)

Visual C++ 6.0:File → New → Projects → Win32 Application → 名稱 ServiceTest → 選擇「A simple application」或空白專案。

2. 全域變數與初始化

TCHAR szServiceName[] = _T("ServiceTest");
BOOL bInstall;
SERVICE_STATUS_HANDLE hServiceStatus;
SERVICE_STATUS status;
DWORD dwThreadID;

BOOL IsInstalled(); BOOL Install(); BOOL Uninstall();
void LogEvent(LPCTSTR pszFormat, ...);
void WINAPI ServiceMain();
void WINAPI ServiceCtrl(DWORD dwOpcode);

void Init() {
    hServiceStatus = NULL;
    status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    status.dwCurrentState = SERVICE_STOPPED;
    status.dwControlsAccepted = SERVICE_ACCEPT_STOP;  // 關鍵:宣告接受 STOP 控制
    status.dwWin32ExitCode = 0;
    status.dwServiceSpecificExitCode = 0;
    status.dwCheckPoint = 0;
    status.dwWaitHint = 0;
}

3. 安裝與移除服務

BOOL Install() {
    SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (!hSCM) return FALSE;

    TCHAR szFilePath[MAX_PATH];
    GetModuleFileName(NULL, szFilePath, MAX_PATH);

    SC_HANDLE hService = CreateService(
        hSCM, szServiceName, szServiceName,
        SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
        SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
        szFilePath, NULL, NULL, _T(""), NULL, NULL);

    CloseServiceHandle(hService);
    CloseServiceHandle(hSCM);
    return TRUE;
}

BOOL Uninstall() {
    SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (!hSCM) return FALSE;

    SC_HANDLE hService = OpenService(hSCM, szServiceName, SERVICE_STOP | DELETE);
    if (hService) {
        ControlService(hService, SERVICE_CONTROL_STOP, &status); // 先停止
        DeleteService(hService);                                 // 再刪除
        CloseServiceHandle(hService);
    }
    CloseServiceHandle(hSCM);
    return TRUE;
}

4. 服務主函數與控制處理器

void WINAPI ServiceMain() {
    // 註冊控制處理器
    status.dwCurrentState = SERVICE_START_PENDING;
    status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
    hServiceStatus = RegisterServiceCtrlHandler(szServiceName, ServiceCtrl);
    if (!hServiceStatus) {
        LogEvent(_T("Handler not installed"));
        return;
    }

    // 回報 RUNNING 狀態
    status.dwWin32ExitCode = 0;
    status.dwCheckPoint = 0;
    status.dwWaitHint = 0;
    status.dwCurrentState = SERVICE_RUNNING;
    SetServiceStatus(hServiceStatus, &status);

    // ===== 服務實際工作區 =====
    // 應用時將主要任務放在此處:監控、伺服器迴圈、定時器等
    int i = 0;
    while (i < 10) {  // 示範:運行 10 秒後自動停止
        Sleep(1000);
        i++;
    }
    // ===========================

    // 正常停止
    status.dwCurrentState = SERVICE_STOPPED;
    SetServiceStatus(hServiceStatus, &status);
    LogEvent(_T("Service stopped"));
}

void WINAPI ServiceCtrl(DWORD dwOpcode) {
    switch (dwOpcode) {
        case SERVICE_CONTROL_STOP:
            status.dwCurrentState = SERVICE_STOP_PENDING;
            SetServiceStatus(hServiceStatus, &status);
            // 這裡可發送事件通知 ServiceMain 迴圈退出
            break;
        case SERVICE_CONTROL_INTERROGATE:
            SetServiceStatus(hServiceStatus, &status);
            break;
        default:
            break;
    }
}

5. 進入點 WinMain:命令列分派

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, int nCmdShow) {
    Init();
    dwThreadID = GetCurrentThreadId();

    SERVICE_TABLE_ENTRY st[] = {
        { szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain },
        { NULL, NULL }
    };

    if (stricmp(lpCmdLine, "/install") == 0) {
        Install();
    } else if (stricmp(lpCmdLine, "/uninstall") == 0) {
        Uninstall();
    } else {
        // 作為服務啟動:進入 SCM 分派迴圈
        if (!StartServiceCtrlDispatcher(st)) {
            LogEvent(_T("Register Service Main Function Error!"));
        }
    }
    return 0;
}

安裝、啟動、除錯實戰指令

操作指令說明
安裝服務ServiceTest.exe /install向 SCM 註冊,啟動類型為「手動」
移除服務ServiceTest.exe /uninstall停止並刪除註冊
啟動服務net start ServiceTest 或 sc start ServiceTest透過 SCM 啟動,進入 StartServiceCtrlDispatcher
停止服務net stop ServiceTest發送 STOP 控制碼,觸發 ServiceCtrl
除錯技巧在 ServiceMain 開頭加 DebugBreak() 或寫入日誌檔服務無 UI,無法直接斷點,需靠日誌或附加偵錯器

常見坑與最佳實踐

  1. dwControlsAccepted 必須設定:未設 SERVICE_ACCEPT_STOP 會導致 SCM 無法停止服務,顯示「無法控制」。
  2. ServiceMain 不能阻塞太久:啟動階段應快速回報 SERVICE_RUNNING,耗時初始化放背景執行緒。
  3. 日誌輸出:服務無主控台,使用 ReportEvent 寫入 Windows 事件檢視器,或自行寫入旋轉日誌檔。
  4. 權限隔離:服務預設以 LocalSystem 執行,權限極高;生產環境建議建立專用服務帳號,授予最小權限。
  5. 會話 0 隔離:Vista+ 服務運行於會話 0,無法直接顯示 UI 或互動桌面;需用「互動式服務」機制或獨立 GUI 程式透過 IPC 通訊。

總結

Windows 服務開發核心在於:SCM 註冊 ↔ 狀態機回報 ↔ 控制碼處理 ↔ 工作迴圈 四位一體。掌握此流程,即可將任何長時間運行邏輯封裝為系統級服務,實現開機自啟、無人值守、高可靠性的後台運算能力。

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