后台服务 程序是在后台悄悄运行的。我们通过将自己的程序登记为服务,可以使自己的程序不出现在任务管理器中,并且随系统启动而最先运行,随系统关闭而最后停止。
服务程序通常编写成控制台类型的应用程序,总的来说,一个遵守服务控制管理程序接口要求的程序包含下面三个函数:
#include
#include "Windows.h"
#define SERVICE_NAME "srv_demo"
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hServiceStatusHandle;
int main (int argc, const char *argv[])
{
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceName = SERVICE_NAME;
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)service_main;
ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;
// 启动服务的控制分派机线程
StartServiceCtrlDispatcher(ServiceTable);
return 0;
}
首先声明几个全局变量,以便在程序的多个函数之间共享它们值。之后在主函数中创建一个分派表。分派表是SERVICE_TABLE_ENTRY 类型结构,它有两个域:
lpServiceProc: 指向服务主函数的指针(服务入口点)
一个程序可能包含若干个服务。每一个服务都必须列于分派表中,分派表的最后一项的两个域都必须为 NULL 指针。并且在只有一个服务的情况下,服务名是可选的。
StartServiceCtrlDispatcher函数启动服务的控制分派机线程,当分派表中所有的服务执行完之后(服务为停止状态),或发生运行时错误,该函数调用返回,进程终止。
void WINAPI service_main(int argc, char** argv)
{
ServiceStatus.dwServiceType = SERVICE_WIN32;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
hServiceStatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, ServiceHandler);
if (hServiceStatusHandle==0)
{
DWORD nError = GetLastError();
}
//add your init code here
//add your service thread here
// Initialization complete - report running status
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 9000;
if(!SetServiceStatus(hServiceStatusHandle, &ServiceStatus))
{
DWORD nError = GetLastError();
}
}
上面给出的是是服务的入口点函数示例代码。它运行在一个单独的线程当中,这个线程由控制分派器创建。该函数应尽快调用 RegisterServiceCtrlHadler 函数为服务注册控制处理器,注册完控制处理器之后,获得状态句柄(hServiceStatusHandle)。
ServiceStatus 结构的每个域的用途如下:
dwCheckPoint 和 dwWaitHint :这两个域表示初始化某个服务进程时要30 秒以上。本文例子服务的初始化过程很短,所以这两个域的值都为 0
SetServiceStatus 函数用于向 SCM 报告服务的状态。需要提供 hStatus 句柄和 ServiceStatus 结构。
void WINAPI ServiceHandler(DWORD fdwControl)
{
switch(fdwControl)
{
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
//add you quit code here
break;
default:
return;
};
if (!SetServiceStatus(hServiceStatusHandle, &ServiceStatus))
{
DWORD nError = GetLastError();
}
}
在第二步中,我们用 RegisterServiceCtrlHadler函数注册了控制处理器函数。控制处理器与处理各种 Windows 消息的窗口回调函数非常类似。它检查 SCM 发送了什么请求并采取相应行动。
STOP 请求是 SCM 终止服务的时候发送的。例如,如果用户在“ 服务” 控制面板中手动终止服务。SHUTDOWN 请求是关闭机器时,由 SCM 发送给所有运行中服务的请求。两种情况的处理方式相同。
控制处理器函数必须报告服务状态,即便 SCM 每次发送控制请求的时候状态保持相同。因此,不管响应什么请求,都要调用 SetServiceStatus 。
上面代码编译完成后,可以通过下面指令将其添加为一个服务。注意:该指令运行时需要管理员权限,且“=”后面必须空一格。
之后我们可以在计算机中看到我们新添加的服务。
可以通过windows提供的控制界面来启动服务,另外还可以使用下面的指令启动服务。
若服务启动时出现如下错误信息,这是因为运行作为服务的应用程序不是按服务的流程写的。所以运行提示“服务没有及时响应启动或控制请求”
我们看到main函数及service_main函数中均有argc、argv参数,也就是说我们是可以向这两个函数中传递参数的,传递参数方法如下。
sc create atest binPath= "D:\Project Files\ImosServer\x64\Release -port=1024"
sc start atest -port=1024
测试参数传递是否成功,可以在相应位置加入如下代码段。
FILE* log = fopen("D:\\log.txt", "a");
for (int i = 0; i < argc; ++i)
{
fprintf(log, "main: %s\n", argv[i]);
}
fclose(log);