概述
windows操作系统创建服务实例
说明
调用的相关系统API:
- CreateService
- StartServiceCtrlDispatcher
- RegisterServiceCtrlHandlerEx
代码
使用时进需要修改 MyServiceName 即可。
#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,响应控制 |
关键流程
// 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下写入DefaultInstance、Altitude、Flags等键,才会被过滤器管理器加载。
ChangeServiceConfig的坑在
CreateService成功之后再调用ChangeServiceConfig是冗余操作;且若把依赖(FltMgr)误传成空字符串,会覆盖掉 minifilter 的依赖、破坏加载顺序——这正是CreateService.cpp中把这段调用注释掉的原因。要动态修改配置,应在
ERROR_SERVICE_EXISTS分支里调用ChangeServiceConfig才合理。
卸载服务(DeleteService)
// 打开 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.h的Debug_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) ← 停止
服务主进程活动图
控制回调并行触发
上图中的停止/暂停等状态变化,实际由
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)
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)
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)
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)
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 里挂的是注入逻辑——这只是用来验证”服务启动后能做点实际业务”的附加测试,并非服务核心:
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 内容等,属于测试业务的延伸。