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

C++Builder 開發 Windows NT 服務程式完整指南:從 SCM 操作到服務主體實作

     編輯:關於C++

Windows NT/2000/XP/2003 等 NT 核心系列作業系統提供強大的服務機制:可開機自動啟動、無需使用者登入即在背景運行、接受 SCM (Service Control Manager) 管理 (啟動/停止/暫停/繼續)。本文詳細說明服務程式架構、核心 API 使用、服務主體與控制程式的完整 C++Builder 實作碼。

服務程式雙元架構

  1. 服務主體:Console 應用程式,包含 main()、ServiceMain()、Handler() 三大核心函式,運行於背景會話 (Session 0)。
  2. 控制程式:GUI 應用程式 (或命令列工具),呼叫 SCM API 進行安裝/移除/啟動/停止/查詢狀態。

核心 SCM API 快速查閱

API用途關鍵參數
OpenSCManager開啟 SCM 資料庫控制代碼機器名、資料庫名、存取權限 (SC_MANAGER_CREATE_SERVICE 等)
CreateService在 SCM 註冊新服務服務名、顯示名、類型 (SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS)、啟動類型 (SERVICE_AUTO_START)、錯誤控制、執行檔路徑
OpenService開啟既有服務控制代碼服務名、存取權限 (SERVICE_START, SERVICE_STOP, SERVICE_QUERY_STATUS)
StartService啟動服務服務控制代碼、啟動參數
ControlService發送控制碼 (停止/暫停/繼續/查詢)服務控制代碼、控制碼 (SERVICE_CONTROL_STOP 等)、狀態結構指標
QueryServiceStatus查詢服務目前狀態服務控制代碼、SERVICE_STATUS 結構指標
DeleteService從 SCM 移除服務服務控制代碼 (需先停止服務)

服務主體完整實作 (Service.cpp - Console App)

#include 
#include 

SERVICE_STATUS_HANDLE g_hStatusHandle = NULL;
SERVICE_STATUS g_Status = {0};

// 前向宣告
VOID WINAPI ServiceMain(DWORD argc, LPTSTR *argv);
VOID WINAPI Handler(DWORD dwOpcode);
void MyServiceCode(); // 你的核心業務邏輯

int main() {
    SERVICE_TABLE_ENTRY dispatchTable[] = {
        { TEXT("MyCppBuilderService"), (LPSERVICE_MAIN_FUNCTION)ServiceMain },
        { NULL, NULL } // 必須以 NULL 結尾
    };

    // 連接 SCM,進入訊息迴圈,不會返回直到服務停止
    if (!StartServiceCtrlDispatcher(dispatchTable)) {
        printf("StartServiceCtrlDispatcher failed: %lu\n", GetLastError());
        return 1;
    }
    return 0;
}

VOID WINAPI ServiceMain(DWORD argc, LPTSTR *argv) {
    // 1. 註冊控制處理函式
    g_hStatusHandle = RegisterServiceCtrlHandler(TEXT("MyCppBuilderService"), Handler);
    if (!g_hStatusHandle) return;

    // 2. 回報「啟動中」狀態
    g_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
    g_Status.dwCurrentState = SERVICE_START_PENDING;
    g_Status.dwControlsAccepted = 0; // 啟動中不接受控制
    g_Status.dwWin32ExitCode = NO_ERROR;
    g_Status.dwCheckPoint = 0;
    g_Status.dwWaitHint = 3000; // 預估 3 秒啟動
    SetServiceStatus(g_hStatusHandle, &g_Status);

    // 3. 執行初始化工作 (資源配置、開啟埠、連接資料庫...)
    // 若初始化耗時 > dwWaitHint,需定期呼叫 SetServiceStatus 更新 dwCheckPoint
    MyServiceCode(); // 這裡放入你的初始化碼

    // 4. 回報「運行中」狀態
    g_Status.dwCurrentState = SERVICE_RUNNING;
    g_Status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
    g_Status.dwCheckPoint = 0;
    g_Status.dwWaitHint = 0;
    SetServiceStatus(g_hStatusHandle, &g_Status);

    // 5. 服務主迴圈 (若為單執行緒服務,這裡可執行主要工作迴圈)
    // 實務上通常另啟工作執行緒,主執行緒僅等待結束事件
    MyServiceCode(); // 主工作迴圈或啟動工作執行緒

    // 6. 服務結束清理 (由 Handler 觸發 SERVICE_STOPPED 後自然結束)
    // 實際上 ServiceMain 在 Handler 設定 STOPPED 狀態後應返回
}

VOID WINAPI Handler(DWORD dwOpcode) {
    switch (dwOpcode) {
        case SERVICE_CONTROL_STOP:
            // 執行停止前清理:關閉埠、釋放資料、通知工作執行緒退出
            MyServiceCode(); // 放入停止邏輯
            g_Status.dwWin32ExitCode = NO_ERROR;
            g_Status.dwCurrentState = SERVICE_STOPPED;
            g_Status.dwCheckPoint = 0;
            g_Status.dwWaitHint = 0;
            SetServiceStatus(g_hStatusHandle, &g_Status);
            break;

        case SERVICE_CONTROL_PAUSE:
            g_Status.dwCurrentState = SERVICE_PAUSED;
            SetServiceStatus(g_hStatusHandle, &g_Status);
            break;

        case SERVICE_CONTROL_CONTINUE:
            g_Status.dwCurrentState = SERVICE_RUNNING;
            SetServiceStatus(g_hStatusHandle, &g_Status);
            break;

        case SERVICE_CONTROL_INTERROGATE:
            // SCM 要求立即回報狀態
            SetServiceStatus(g_hStatusHandle, &g_Status);
            break;

        case SERVICE_CONTROL_SHUTDOWN:
            // 系統關機通知,同 STOP 但時間更緊迫
            Handler(SERVICE_CONTROL_STOP);
            break;
    }
}

// 你的業務邏輯範例
void MyServiceCode() {
    // 實作你的核心功能:監聽 Socket、定時任務、檔案監控、資料庫輪詢...
    // 注意:服務運行於 Session 0,無桌面互動 (Vista+ 嚴格隔離),UI 操作會失敗。
    // 若需與使用者互動,需建立單獨 GUI 應用程式透過 IPC (Named Pipe, RPC, TCP) 與服務通訊。
}

控制程式實作 (GUI App - 表單按鈕事件)

// 全域變數
SC_HANDLE g_scm = NULL, g_svc = NULL;
SERVICE_STATUS g_svcStatus;

// 共用:開啟 SCM
bool OpenSCM(DWORD access = SC_MANAGER_ALL_ACCESS) {
    g_scm = OpenSCManager(NULL, NULL, access);
    return g_scm != NULL;
}
void CloseSCM() { if (g_scm) { CloseServiceHandle(g_scm); g_scm = NULL; } }

// 1. 安裝服務 (Create Button)
void __fastcall TForm1::BtnInstallClick(TObject *Sender) {
    if (!OpenSCM(SC_MANAGER_CREATE_SERVICE)) return;
    g_svc = CreateService(g_scm,
        TEXT("MyCppBuilderService"),        // 服務名 (內部識別)
        TEXT("My C++Builder Service Demo"), // 顯示名
        SERVICE_ALL_ACCESS,
        SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
        SERVICE_AUTO_START,                 // 自動啟動
        SERVICE_ERROR_NORMAL,
        TEXT("C:\\MyService\\MyService.exe"), // 服務執行檔完整路徑 (必須存在!)
        NULL, NULL, NULL, NULL, NULL);
    if (g_svc) { CloseServiceHandle(g_svc); ShowMessage("服務安裝成功!"); }
    else ShowMessage("安裝失敗: " + IntToStr(GetLastError()));
    CloseSCM();
}

// 2. 移除服務 (Delete Button)
void __fastcall TForm1::BtnRemoveClick(TObject *Sender) {
    if (!OpenSCM(SC_MANAGER_CONNECT)) return;
    g_svc = OpenService(g_scm, TEXT("MyCppBuilderService"), SERVICE_ALL_ACCESS);
    if (g_svc) {
        QueryServiceStatus(g_svc, &g_svcStatus);
        if (g_svcStatus.dwCurrentState == SERVICE_RUNNING)
            ControlService(g_svc, SERVICE_CONTROL_STOP, &g_svcStatus); // 先停止
        if (DeleteService(g_svc)) ShowMessage("服務移除成功!");
        else ShowMessage("移除失敗: " + IntToStr(GetLastError()));
        CloseServiceHandle(g_svc);
    }
    CloseSCM();
}

// 3. 啟動服務 (Start Button)
void __fastcall TForm1::BtnStartClick(TObject *Sender) {
    if (!OpenSCM(SC_MANAGER_CONNECT)) return;
    g_svc = OpenService(g_scm, TEXT("MyCppBuilderService"), SERVICE_START);
    if (g_svc) {
        if (StartService(g_svc, 0, NULL)) ShowMessage("服務啟動成功!");
        else ShowMessage("啟動失敗: " + IntToStr(GetLastError()));
        CloseServiceHandle(g_svc);
    }
    CloseSCM();
}

// 4. 停止服務 (Stop Button)
void __fastcall TForm1::BtnStopClick(TObject *Sender) {
    if (!OpenSCM(SC_MANAGER_ALL_ACCESS)) return;
    g_svc = OpenService(g_scm, TEXT("MyCppBuilderService"), SERVICE_STOP | SERVICE_QUERY_STATUS);
    if (g_svc) {
        QueryServiceStatus(g_svc, &g_svcStatus);
        if (g_svcStatus.dwCurrentState == SERVICE_RUNNING) {
            if (ControlService(g_svc, SERVICE_CONTROL_STOP, &g_svcStatus))
                ShowMessage("服務停止成功!");
            else ShowMessage("停止失敗: " + IntToStr(GetLastError()));
        } else ShowMessage("服務未在運行中。");
        CloseServiceHandle(g_svc);
    }
    CloseSCM();
}

關鍵實作細節與避坑指南

  1. 服務名稱一致性:main() 的 SERVICE_TABLE_ENTRY、RegisterServiceCtrlHandler、CreateService、OpenService 的服務名稱必須完全一致 (區分大小寫)。
  2. 執行檔路徑:CreateService 的 lpBinaryPathName 必須是絕對路徑,且該路徑在服務啟動時必須可存取 (網路磁碟機需映射為本機路徑或使用 UNC 路徑並確保服務帳號權限)。
  3. 服務帳號:預設以 LocalSystem 執行 (高權限、無網路存取權)。需網路資源時,CreateService 指定 lpServiceStartName (如 DOMAIN\User) 與 lpPassword,或用 ChangeServiceConfig2 設定 SERVICE_CONFIG_SERVICE_SID_INFO。
  4. 互動式服務:SERVICE_INTERACTIVE_PROCESS 允許服務顯示 UI (Vista/Win7+ 預設 Session 0 隔離,UI 不會顯示給使用者)。現代架構強烈建議分離:服務無 UI,另建 GUI 客戶端透過 Named Pipe/TCP/RPC 與服務通訊。
  5. 啟動逾時:ServiceMain 必須在 30 秒內 (預設) 回報 SERVICE_RUNNING,否則 SCM 判定啟動失敗並終止進程。耗時初始化需回報 SERVICE_START_PENDING 並定期更新 dwCheckPoint。
  6. 錯誤處理:所有 API 失敗必須檢查 GetLastError(),常見錯誤:ERROR_SERVICE_EXISTS (重複安裝)、ERROR_ACCESS_DENIED (權限不足、需以管理員身分執行控制程式)、ERROR_SERVICE_DOES_NOT_EXIST (服務未安裝)。
  7. 除錯技巧:在 ServiceMain 開頭加入 DebugBreak() 或 Sleep(10000) 附上偵錯器;或寫入日誌檔 (C:\Temp\ServiceLog.txt) 追蹤流程。

現代替代方案

C++Builder 10.4+ 提供 TService 元件 (File → New → Other → Service Application) 封裝上述所有邏輯,僅需覆寫 OnExecute、OnStart、OnStop、OnPause、OnContinue 事件,自動處理 SCM 通訊、狀態回報、安裝/移除命令列參數 (/install, /uninstall)。新專案強烈建議使用 TService 架構,本文手工實作僅供理解底層機制或維護舊專案參考。

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