Windows NT/2000/XP/2003 等 NT 核心系列作業系統提供強大的服務機制:可開機自動啟動、無需使用者登入即在背景運行、接受 SCM (Service Control Manager) 管理 (啟動/停止/暫停/繼續)。本文詳細說明服務程式架構、核心 API 使用、服務主體與控制程式的完整 C++Builder 實作碼。
main()、ServiceMain()、Handler() 三大核心函式,運行於背景會話 (Session 0)。| 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 移除服務 | 服務控制代碼 (需先停止服務) |
#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) 與服務通訊。
}
// 全域變數
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();
}
main() 的 SERVICE_TABLE_ENTRY、RegisterServiceCtrlHandler、CreateService、OpenService 的服務名稱必須完全一致 (區分大小寫)。CreateService 的 lpBinaryPathName 必須是絕對路徑,且該路徑在服務啟動時必須可存取 (網路磁碟機需映射為本機路徑或使用 UNC 路徑並確保服務帳號權限)。LocalSystem 執行 (高權限、無網路存取權)。需網路資源時,CreateService 指定 lpServiceStartName (如 DOMAIN\User) 與 lpPassword,或用 ChangeServiceConfig2 設定 SERVICE_CONFIG_SERVICE_SID_INFO。SERVICE_INTERACTIVE_PROCESS 允許服務顯示 UI (Vista/Win7+ 預設 Session 0 隔離,UI 不會顯示給使用者)。現代架構強烈建議分離:服務無 UI,另建 GUI 客戶端透過 Named Pipe/TCP/RPC 與服務通訊。ServiceMain 必須在 30 秒內 (預設) 回報 SERVICE_RUNNING,否則 SCM 判定啟動失敗並終止進程。耗時初始化需回報 SERVICE_START_PENDING 並定期更新 dwCheckPoint。GetLastError(),常見錯誤:ERROR_SERVICE_EXISTS (重複安裝)、ERROR_ACCESS_DENIED (權限不足、需以管理員身分執行控制程式)、ERROR_SERVICE_DOES_NOT_EXIST (服務未安裝)。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 架構,本文手工實作僅供理解底層機制或維護舊專案參考。