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

五步構建Windows服務程序:C語言實現後台內存監控實戰

     編輯:關於C語言

前言:為什麼用C而非C++寫入門級Windows服務?

Windows服務(NT Service)專為後台長期運行、無用戶交互任務設計。雖然C++封裝類開發效率高,但面向對象封裝掩蓋了底層Win32 API調用細節,不利於初學者理解「服務控制管理器(SCM)如何調度服務、何時調用什麼函數」。C語言直擊API本質,是學習服務機制的最佳語言。本文以「定期查詢可用物理內存並寫入日誌」為例,完整演示服務的創建、安裝、配置、測試全生命周期。

核心架構:服務程序運行流程圖

main() → StartServiceCtrlDispatcher(分派表)
    ↓
SCM創建控制分派器線程 → 為每個服務啟動ServiceMain線程
    ↓
ServiceMain → RegisterServiceCtrlHandler(註冊控制處理器)
    ↓
初始化 → SetServiceStatus(SERVICE_RUNNING)
    ↓
工作循環(查詢內存、寫日誌、Sleep) ← ControlHandler處理STOP/SHUTDOWN請求修改狀態
    ↓
服務停止 → 清理 → 退出

第一步:主函數與全局定義——服務的「入口與骨架」

1. 頭文件與常量

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

#define SLEEP_TIME 5000          // 查詢間隔5秒
#define LOGFILE "C:\\MyServices\\memstatus.txt"  // 日誌路徑

2. 日誌寫入函數

int WriteToLog(char* str) {
    FILE* log = fopen(LOGFILE, "a+");
    if (!log) return -1;
    fprintf(log, "%s\n", str);
    fclose(log);
    return 0;
}

3. 全局狀態變量與前向聲明

SERVICE_STATUS ServiceStatus;        // 服務狀態結構
SERVICE_STATUS_HANDLE hStatus;       // 狀態句柄
void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
int InitService();

4. 極簡 main():只負責啟動分派器

void main() {
    SERVICE_TABLE_ENTRY ServiceTable[2];
    ServiceTable[0].lpServiceName = "MemoryStatus";
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
    ServiceTable[1].lpServiceName = NULL;
    ServiceTable[1].lpServiceProc = NULL;
    StartServiceCtrlDispatcher(ServiceTable);  // 進入分派器,不再返回
}

關鍵結構 SERVICE_TABLE_ENTRY:lpServiceName(服務名)、lpServiceProc(入口函數指針)。數組以雙NULL結尾。StartServiceCtrlDispatcher 將主線程轉為控制分派器,必須在30秒內調用否則報錯。

第二步:ServiceMain——服務的真正主函數

1. 註冊控制處理器並獲取狀態句柄

void ServiceMain(int argc, char** argv) {
    hStatus = RegisterServiceCtrlHandler("MemoryStatus", (LPHANDLER_FUNCTION)ControlHandler);
    // 初始化 ServiceStatus 結構...
}

2. 初始化 ServiceStatus 關鍵字段

字段初始值含義
dwServiceTypeSERVICE_WIN32Win32自有進程服務
dwCurrentStateSERVICE_START_PENDING正在啟動中
dwControlsAcceptedSERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN接受停止/關機請求
dwWin32ExitCode / dwServiceSpecificExitCode0退出碼(運行時更新)
dwCheckPoint / dwWaitHint0啟動超過30秒時用於心跳上報

調用 SetServiceStatus(hStatus, &ServiceStatus) 上報SCM。

3. 業務初始化與錯誤處理

int InitService() { return WriteToLog("Monitoring started."); }

// 在ServiceMain中:
int error = InitService();
if (error) {
    ServiceStatus.dwCurrentState = SERVICE_STOPPED;
    ServiceStatus.dwWin32ExitCode = -1;
    SetServiceStatus(hStatus, &ServiceStatus);
    return;  // 退出線程,服務終止
}

4. 進入運行狀態與工作循環

ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(hStatus, &ServiceStatus);

while (ServiceStatus.dwCurrentState == SERVICE_RUNNING) {
    MEMORYSTATUS ms; ms.dwLength = sizeof(ms);
    GlobalMemoryStatus(&ms);
    char buf[32]; sprintf(buf, "%lu", ms.dwAvailPhys);
    if (WriteToLog(buf)) break;  // 寫失敗則退出循環
    Sleep(SLEEP_TIME);
}

// 循環結束前上報停止狀態
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus(hStatus, &ServiceStatus);

第三步:ControlHandler——響應SCM控制請求

類似窗口消息循環,處理 SERVICE_CONTROL_STOP、SERVICE_CONTROL_SHUTDOWN:

void ControlHandler(DWORD request) {
    switch (request) {
        case SERVICE_CONTROL_STOP:
        case SERVICE_CONTROL_SHUTDOWN:
            WriteToLog("Monitoring stopped.");
            ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            SetServiceStatus(hStatus, &ServiceStatus);  // 必須上報
            break;
        default: break;
    }
    // PAUSE/CONTINUE 本例未處理
}

核心機制:工作循環檢查 ServiceStatus.dwCurrentState,ControlHandler修改該全局變量實現線程間通信。

第四步:安裝與配置——讓服務跑起來

  1. 編譯生成 MemoryStatus.exe,拷貝至 C:\MyServices\
  2. 使用 sc.exe (Win32 Platform SDK工具) 安裝:
    sc create MemoryStatus binpath= c:\MyServices\MemoryStatus.exe
    // 注意 binpath= 後有空格!
  3. 打開「服務」控制面板 (services.msc) 可見 MemoryStatus,啟動類型默認為「手動」。
  4. 右鍵「屬性」可改為「自動」,或在「常規」頁啟動/停止。
  5. 卸載命令:sc delete MemoryStatus(標記刪除,重啟後生效)。

第五步:測試驗證——看到真實的內存數據

// 啟動服務後,memstatus.txt 內容示例:
Monitoring started.
273469440
273379328
273133568
273084416
Monitoring stopped.

故障注入測試:將日誌文件設為「只讀」→ 啟動服務失敗(初始化寫入失敗)→ 服務狀態顯示「已停止」;運行中設為只讀 → 服務自動停止(工作循環寫入失敗跳出)。

進階方向:從C邁向C++封裝與多線程服務

  • 理解底層後,編寫C++包裝類隱藏Win32 API細節,提供舒適接口。
  • 複雜任務可在 ServiceMain 中創建多個工作線程,主線程負責監控與狀態上報。
  • 引入日誌庫、配置文件解析、IPC機制(命名管道/TCP)實現服務間通信。

掌握這五步,你已擁有了編寫任意Windows後台服務的核心能力。動手改造 MemoryStatus,實現你的專屬服務吧!

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