概述

windows操作系统创建服务实例

说明

调用的相关系统API:

  • CreateService
  • StartServiceCtrlDispatcher
  • RegisterServiceCtrlHandlerEx

代码

使用时进需要修改 MyServiceName 即可。

C++cpp
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
 
#define MyServiceName TEXT("MyService")
 
bool                    g_bPausedFlag = false;
HANDLE                  g_hSvcStopEvent = NULL;
SERVICE_STATUS          g_MyServiceStatus;
SERVICE_STATUS_HANDLE   g_hMyServiceStatusHandle;
 
void __stdcall MyServiceMain(DWORD  dwArgc, LPTSTR *lpszArgv);
DWORD __stdcall MyServiceHandlerEx(DWORD  dwControl, DWORD  dwEventType, LPVOID lpEventData, LPVOID lpContext);
void ReportServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint);
DWORD MyServiceInitialization(DWORD  dwArgc, LPTSTR *lpszArgv);
DWORD MyServiceWorker(DWORD dwArgc, LPTSTR *lpszArgv);
 
int _tmain(int argc, TCHAR* argv[])
{
    SERVICE_TABLE_ENTRY dispatchTable[] = { 
        {MyServiceName, (LPSERVICE_MAIN_FUNCTION)MyServiceMain},
        {NULL, NULL} };
 
    if (!StartServiceCtrlDispatcher(dispatchTable))
    {
        // cleaning worker
    }
 
    return 0;
}
 
void __stdcall MyServiceMain(DWORD  dwArgc, LPTSTR *lpszArgv)
{
    g_hSvcStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    g_hMyServiceStatusHandle = RegisterServiceCtrlHandlerEx(MyServiceName, (LPHANDLER_FUNCTION_EX)MyServiceHandlerEx, NULL);
    if (g_hMyServiceStatusHandle == NULL)
        return;
 
    g_MyServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    g_MyServiceStatus.dwServiceSpecificExitCode = 0;
    ReportServiceStatus(SERVICE_START_PENDING, 0, 0);
 
    DWORD dwRet = MyServiceInitialization(dwArgc, lpszArgv);
    if (dwRet != NO_ERROR)
        ReportServiceStatus(SERVICE_STOPPED, dwRet, 0);
    ReportServiceStatus(SERVICE_RUNNING, 0, 0);
 
    MyServiceWorker(dwArgc, lpszArgv);
 
    while (true)
    {
        WaitForSingleObject(g_hSvcStopEvent, INFINITE);
        ReportServiceStatus(SERVICE_STOPPED, 0, 0);
        return;
    }
}
 
DWORD __stdcall MyServiceHandlerEx(DWORD  dwControl, DWORD  dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
    switch (dwControl)
    {
    case SERVICE_CONTROL_SHUTDOWN:
    case SERVICE_CONTROL_STOP:
        ReportServiceStatus(SERVICE_STOP_PENDING, 0, 0);
        SetEvent(g_hSvcStopEvent);
        break;
    case SERVICE_CONTROL_PAUSE:
        ReportServiceStatus(SERVICE_PAUSE_PENDING, 0, 0);
        g_bPausedFlag = true;
        break;
    case SERVICE_CONTROL_CONTINUE:
        ReportServiceStatus(SERVICE_CONTINUE_PENDING, 0, 0);
        g_bPausedFlag = false;
        break;
    case SERVICE_CONTROL_INTERROGATE:
        break;
    default:
        break;
    }
    return 0;
}
 
// 执行一些初始化过程
DWORD MyServiceInitialization(DWORD  dwArgc, LPTSTR *lpszArgv)
{
    return NO_ERROR;
}
 
// 服务工作执行函数
DWORD MyServiceWorker(DWORD dwArgc, LPTSTR *lpszArgv)
{
    return 0;
}
 
// 向服务控制管理器报告状态信息
void ReportServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint)
{
    static DWORD dwCheckPoint = 1;
 
    g_MyServiceStatus.dwCurrentState = dwCurrentState;
    g_MyServiceStatus.dwWin32ExitCode = dwWin32ExitCode;
    g_MyServiceStatus.dwWaitHint = dwWaitHint;
 
    if (dwCurrentState == SERVICE_START_PENDING)
        g_MyServiceStatus.dwControlsAccepted = 0;
    else 
        g_MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
 
    if ((dwCurrentState == SERVICE_RUNNING) ||
        (dwCurrentState == SERVICE_STOPPED))
        g_MyServiceStatus.dwCheckPoint = 0;
    else 
        g_MyServiceStatus.dwCheckPoint = dwCheckPoint++;
 
    SetServiceStatus(g_hMyServiceStatusHandle, &g_MyServiceStatus);
}

补充 1:创建服务(CreateService 完整示例)

来源

Windows-API-Usage/WinAPI/CreateService/CreateService.cpp

理解本文的前提

上方的模板是服务内部StartServiceCtrlDispatcher 分发入口)的写法;而创建/注册一个服务,需要在外部CreateService 完成。二者是服务生命周期的两个阶段。

阶段API执行者作用
安装/注册OpenSCManager + CreateService外部程序(安装器/管理员)在 SCM 中登记服务
运行StartServiceCtrlDispatcher + RegisterServiceCtrlHandlerEx服务本体连接 SCM,响应控制

关键流程

C++cpp
// 1. 打开服务控制管理器(SCM)
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL,
    SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);
 
// 2. 创建服务
SC_HANDLE hService = ::CreateService(
    hSCM,
    lpszServiceName,      // 服务名(注册表 Services 键名)
    lpszDisplayName,      // 显示名
    SERVICE_ALL_ACCESS,   // 权限
    SERVICE_WIN32_OWN_PROCESS, // 服务类型:独立进程
    SERVICE_AUTO_START,   // 启动类型:自动
    SERVICE_ERROR_NORMAL, // 错误处理
    szFilePath,           // 可执行文件路径
    _T("FSFilter Activity Monitor"), // 服务组(LoadOrderGroup)
    &dwOrder,             // 组内加载顺序标签
    _T("FltMgr"),         // 依赖(Dependencies)
    NULL,                 // ServiceStartName(账户)
    NULL                  // 密码
);

失败处理:服务已存在

别把"已存在"当错误

CreateService 失败时,若 GetLastError() == ERROR_SERVICE_EXISTS,说明服务之前已被创建过(如上次安装未卸载)。此时不应报错,而应视为”已存在”,走更新或直接返回成功的逻辑。

注意点

minifilter 驱动服务

若服务类型为 SERVICE_FILE_SYSTEM_DRIVER / minifilter,还需要在注册表 Services\<name>\Instances 下写入 DefaultInstanceAltitudeFlags 等键,才会被过滤器管理器加载。

ChangeServiceConfig 的坑

CreateService 成功之后再调用 ChangeServiceConfig冗余操作;且若把依赖(FltMgr)误传成空字符串,会覆盖掉 minifilter 的依赖、破坏加载顺序——这正是 CreateService.cpp 中把这段调用注释掉的原因。

要动态修改配置,应在 ERROR_SERVICE_EXISTS 分支里调用 ChangeServiceConfig 才合理。

卸载服务(DeleteService)

C++cpp
// 打开 SCM → 打开服务 → 若在运行则先 STOP → DeleteService
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
SC_HANDLE hSCService = OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS);
QueryServiceStatus(hSCService, &curStatus);
if (curStatus.dwCurrentState != SERVICE_STOPPED)
    ControlService(hSCService, SERVICE_CONTROL_STOP, &ctrlstatus); // 先停止
DeleteService(hSCService); // 再删除

补充 2:服务主进程完整逻辑(SimpleService.cpp)

来源

Windows-API-Usage/WinAPI/ServiceCallDll/SimpleService.cpp

与开头模板的关系

开头那篇是精简骨架,本补充是同一个服务的完整实现SimpleService.cpp)。核心框架一致,但多了:① 日志封装(define.hDebug_Run/Debug_Error);② 在 MyServiceWorker 里挂接实际业务(DLL 注入测试)。

服务主进程的生命周期

服务主进程就是 _tmain 里那一套:分发 → 主入口 → 注册控制处理 → 上报状态 → 初始化 → 干活 → 等停止

_tmain
 └─ StartServiceCtrlDispatcher(dispatchTable)   ← 连接 SCM,进入服务分发
     └─ MyServiceMain(被 SCM 调用)
         ├─ CreateEvent(停止事件)
         ├─ RegisterServiceCtrlHandlerEx        ← 注册控制回调
         ├─ ReportServiceStatus(SERVICE_START_PENDING)  ← 启动中
         ├─ MyServiceInitialization             ← 初始化
         ├─ ReportServiceStatus(SERVICE_RUNNING)         ← 运行中
         ├─ MyServiceWorker                     ← 真正干活(本Demo:注入测试)
         └─ while(true) WaitForSingleObject(停止事件)    ← 等待停止
             └─ ReportServiceStatus(SERVICE_STOPPED)     ← 停止

服务主进程活动图

PlantUML diagram

控制回调并行触发

上图中的停止/暂停等状态变化,实际由 MyServiceHandlerEx 控制回调并行触发:SCM 收到 SERVICE_CONTROL_STOP 时回调里 SetEvent(g_hSvcStopEvent),主线程的 WaitForSingleObject 随即返回并上报 SERVICE_STOPPED

各函数职责

函数职责关键点
_tmain程序入口,注册服务分发表SERVICE_TABLE_ENTRY{NULL,NULL} 结尾
MyServiceMain服务主入口(SCM 回调)注册控制处理器、上报状态、调初始化与 Worker
MyServiceHandlerEx处理控制命令SERVICE_CONTROL_STOP/PAUSE/CONTINUE/SHUTDOWN
MyServiceInitialization初始化本 Demo 仅打日志,返回 NO_ERROR
MyServiceWorker业务主循环本 Demo 在此做 DLL 注入测试
ReportServiceStatus向 SCM 上报状态封装 SetServiceStatus

关键代码逐段解读

1) 服务分发(_tmain)

C++cpp
SERVICE_TABLE_ENTRY dispatchTable[] = {
    {(LPWSTR)MyServiceName, (LPSERVICE_MAIN_FUNCTION)MyServiceMain},
    {NULL, NULL} };                       // 必须以 NULL 结尾
 
if (!StartServiceCtrlDispatcher(dispatchTable))
{
    // 失败说明不是被 SCM 启动(如直接在命令行运行),可在此提示
}

StartServiceCtrlDispatcher 的返回值

只有被 SCM 启动时才会正确分发。若直接双击运行该 exe,StartServiceCtrlDispatcher立即失败(返回 FALSE)——因为本进程并非由 SCM 拉起的服务。这也是常见的调试困惑点。

2) 服务主入口(MyServiceMain)

C++cpp
void __stdcall MyServiceMain(DWORD dwArgc, LPTSTR* lpszArgv)
{
    g_hSvcStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);   // 停止事件
    // 注册控制回调(注意是 HandlerEx 版本,带事件参数)
    g_hMyServiceStatusHandle = RegisterServiceCtrlHandlerEx(MyServiceName,
        (LPHANDLER_FUNCTION_EX)MyServiceHandlerEx, NULL);
    if (g_hMyServiceStatusHandle == NULL)
        return;                                              // 注册失败直接退出
 
    g_MyServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    ReportServiceStatus(SERVICE_START_PENDING, 0, 0);        // 上报"启动中"
 
    DWORD dwRet = MyServiceInitialization(dwArgc, lpszArgv); // 初始化
    if (dwRet != NO_ERROR)
        ReportServiceStatus(SERVICE_STOPPED, dwRet, 0);      // 初始化失败 → 停止
    ReportServiceStatus(SERVICE_RUNNING, 0, 0);              // 上报"运行中"
 
    MyServiceWorker(dwArgc, lpszArgv);                       // 干活
 
    while (true)                                             // 等待停止
    {
        WaitForSingleObject(g_hSvcStopEvent, INFINITE);
        ReportServiceStatus(SERVICE_STOPPED, 0, 0);
        return;
    }
}

服务的"主线程"不能返回

服务主线程进入 WaitForSingleObject(停止事件, INFINITE) 后就不能返回,否则 SCM 认为服务已退出。停止由控制回调通过 SetEvent(g_hSvcStopEvent) 触发。

3) 控制回调(MyServiceHandlerEx)

C++cpp
DWORD __stdcall MyServiceHandlerEx(DWORD dwControl, ...)
{
    switch (dwControl)
    {
    case SERVICE_CONTROL_SHUTDOWN:   // 系统关机
    case SERVICE_CONTROL_STOP:       // 停止服务
        ReportServiceStatus(SERVICE_STOP_PENDING, 0, 0);
        SetEvent(g_hSvcStopEvent);   // 触发主线程停止
        break;
    case SERVICE_CONTROL_PAUSE:      // 暂停
        ReportServiceStatus(SERVICE_PAUSE_PENDING, 0, 0);
        g_bPausedFlag = true;
        break;
    case SERVICE_CONTROL_CONTINUE:   // 继续
        ReportServiceStatus(SERVICE_CONTINUE_PENDING, 0, 0);
        g_bPausedFlag = false;
        break;
    case SERVICE_CONTROL_INTERROGATE: // 查询状态,无需动作
        break;
    default:
        break;
    }
    return 0;
}

4) 上报状态(ReportServiceStatus)

C++cpp
void ReportServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint)
{
    static DWORD dwCheckPoint = 1;
    g_MyServiceStatus.dwCurrentState = dwCurrentState;
    g_MyServiceStatus.dwWin32ExitCode = dwWin32ExitCode;
    g_MyServiceStatus.dwWaitHint = dwWaitHint;
 
    // 启动中不允许接收控制命令;运行/停止后可接收 停止+暂停/继续
    if (dwCurrentState == SERVICE_START_PENDING)
        g_MyServiceStatus.dwControlsAccepted = 0;
    else
        g_MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
 
    // 运行/停止时 checkpoint 归零;中间态自增(供 SCM 判断是否卡死)
    if ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED))
        g_MyServiceStatus.dwCheckPoint = 0;
    else
        g_MyServiceStatus.dwCheckPoint = dwCheckPoint++;
 
    SetServiceStatus(g_hMyServiceStatusHandle, &g_MyServiceStatus);
}

CheckPoint 的意义

启动/停止等过渡态*_PENDING)需要 dwCheckPoint 自增并配合 dwWaitHint 提示 SCM “我还要多久”。若长时间无进展,SCM 会判定服务卡死。运行/停止的稳态则归零。

附加功能:Worker 中的 DLL 注入测试

MyServiceWorker 里挂的是注入逻辑——这只是用来验证”服务启动后能做点实际业务”的附加测试,并非服务核心:

C++cpp
DWORD MyServiceWorker(DWORD dwArgc, LPTSTR* lpszArgv)
{
    Debug_Run(L"[%s]-------Work", __FUNCTIONW__);
 
    // 附加测试:把 SimpleDll.dll 注入到 mysqld.exe (PID 5552)
    InjectDll idl(5552, "C:\MYSQLPLUGIN64.dll");
    idl.SetCheckEnvValue("MYSQLD_WINDOWS_SERVICE");
    idl.Inject(InjectDll::INJECT_NT_CREATETH);   // 注入方式之一
 
    return 0;
}

注入逻辑只是附加功能

本目录的 InjectDll / ProcessHelper / SimpleDll 均服务于这个测试性注入功能,用来证明”服务主进程在 MyServiceWorker 里能驱动真实业务”。

若只关注服务本身的框架,看本文 _tmain → MyServiceMain → HandlerEx → ReportServiceStatus 这条主线即可;注入相关的三种方式、DLL 内容等,属于测试业务的延伸。