完整的Windows服务开发主要分为三个模块:服务创建模块,服务管理模块,业务模块。
首先来说服务创建模块,上代码:
//打开服务控制管理器
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM == NULL)
{
return FALSE;
}
// Get the executable file path
TCHAR szFilePath[MAX_PATH];
::GetModuleFileName(NULL, szFilePath, MAX_PATH);
//创建服务
SC_HANDLE hService = ::CreateService(
hSCM, g_szServiceName, g_szServiceName,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
szFilePath, NULL, NULL, _T(""), NULL, NULL);
if (hService == NULL)
{
::CloseServiceHandle(hSCM);
return FALSE;
}
printf("服务安装成功\n");
BOOL bResult = StartService(hService, 0, NULL);
if (bResult == FALSE)
{
DWORD dwRet = GetLastError();
printf("服务启动失败:GetLastError()=%ld\n", dwRet);
}
else
{
Sleep(1000);
SERVICE_STATUS ssSvcStatus = {};
while (QueryServiceStatus(hService, &ssSvcStatus))
{
if (ssSvcStatus.dwCurrentState == SERVICE_START_PENDING)
{
Sleep(1000);
}
else
break;
}
if (ssSvcStatus.dwCurrentState == SERVICE_RUNNING)
{
printf("服务启动成功\n");
}
}
::CloseServiceHandle(hService);
::CloseServiceHandle(hSCM);
通过上面代码可以看出,创建服务的第一步是使用Windows的API函数OpenSCManager来打开服务控制管理器,然后是通过CreateService来创建服务。之后可以调用StartService接口选择立即启动所创建的服务,也可以根据需要通过服务名称从另外一个进程进行启动所创建的服务。通过QueryServiceStatus可以查询服务当前的状态。当然通过命令行输入“sc query 服务名称”也是可以查询服务的当前状态的。
服务的创建、启动和查询都有了。那么在启动服务时调用StartService之后发生了什么事情呢?这时服务控制管理器(SCM)就登场了。
服务控制管理器 (SCM:Services Control Manager) 是一个管理系统所有服务的进程。当 SCM 启动某个服务时,它等待某个进程的主线程来调用 StartServiceCtrlDispatcher 函数,将分派表传递给 StartServiceCtrlDispatcher。这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中每个服务的 ServiceMain 函数分派器还监视程序中所有服务的执行情况。然后分派器将控制请求从 SCM 传给服务。分派表中所有的服务执行完之后,或者发生错误时。StartServiceCtrlDispatcher 调用返回。然后主进程终止。
SCM 启动一个服务程序时,总是等待这个服务程序去调用StartServiceCtrlDispatcher()。而当服务开始运行时,main() 将会调用 ServiceMain(), 直到 ServiceMain() 执行完毕或发生错误而退出,StartServiceCtrlDispatcher() 返回,主线程将会终止。
上代码:
SERVICE_TABLE_ENTRY st[] =
{
{ _T(SERVER_NAME), (LPSERVICE_MAIN_FUNCTION)ServiceMain },
{ NULL, NULL }
};
if (!::StartServiceCtrlDispatcher(st))
{
LogEvent(_T("Register Service Main Function Error!"));
return 0;
}
下面是ServiceMain函数的实例,包含业务模块和服务控制模块:
void WINAPI ServiceMain()
{
LogEvent(_T("ServiceMain"));
//注册服务控制
g_hServiceStatus = RegisterServiceCtrlHandler(g_szServiceName, ServiceStrl);
if (g_hServiceStatus == NULL)
{
LogEvent(_T("注册服务控制失败,程序退出!"));
// 注册服务失败 退出
return;
}
// Register the control request handler
g_status.dwWin32ExitCode = S_OK;
g_status.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(g_hServiceStatus, &g_status);
// 业务代码
int nRet = StartTcpServer();
if (nRet < 0)
{
LogEvent(_T("StartTcpServer.failed , exit!!!"));
exit(-1);
}
while (1)
{
if (g_status.dwCurrentState == SERVICE_STOPPED) //已停止
{
LogEvent(_T("%s"),_T("服务已经停止"));
return ;
}
else if (g_status.dwCurrentState == SERVICE_START_PENDING) //启动(正在启动)
{
//初始化一些东西
//if (ServiceInit() < 0)
//{
// // 初始化失败,终止服务
// g_status.dwCurrentState = SERVICE_STOPPED;
// g_status.dwWin32ExitCode = -1;
// SetServiceStatus(g_hServiceStatus, &g_status); //向 SCM 报告服务的状态
// LogEvent(_T("%s"),_T("初始化失败,服务已经停止"));
// return; // 退出 ServiceMain
//}
//else
{
g_status.dwWin32ExitCode = S_OK;
g_status.dwCheckPoint = 0;
g_status.dwWaitHint = 0;
g_status.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(g_hServiceStatus, &g_status);
}
}
else if (g_status.dwCurrentState == SERVICE_STOP_PENDING) //停止(正在停止)
{
g_status.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus(g_hServiceStatus, &g_status);
return; // 退出 ServiceMain
}
else if (g_status.dwCurrentState == SERVICE_RUNNING) //已启动
{
//LogEvent(L"GrServer start success!");
}
else if (g_status.dwCurrentState == SERVICE_CONTINUE_PENDING) //恢复(正在恢复-由已暂停至已启动)
{
g_status.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(g_hServiceStatus, &g_status);
}
else if (g_status.dwCurrentState == SERVICE_PAUSE_PENDING) //暂停(正在暂停)
{
g_status.dwCurrentState = SERVICE_PAUSED;
SetServiceStatus(g_hServiceStatus, &g_status);
}
else if (g_status.dwCurrentState == SERVICE_PAUSED) //已暂停
{
OutputDebugStringA("服务已经暂停\n");
}
Sleep(500);
}
//status.dwCurrentState = SERVICE_STOPPED;
//SetServiceStatus(hServiceStatus, &status);
//LogEvent(_T("Service stopped"));
}
从上面代码可以看出,首先需要通过RegisterServiceCtrlHandler来注册服务控制,然后设置服务状态,接下来就是自己所需要的业务代码了。while循环来对服务状态进行监控,并对不同的状态来进行不同的处理。
接下来说说服务的停止,上代码:
//打开服务控制管理器
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM != NULL)
{
//打开服务
SC_HANDLE hService = ::OpenService(hSCM, g_szServiceName, SERVICE_STOP);
if (hService != NULL)
{
// 停止服务
SERVICE_STATUS service_status;
bResult = ::ControlService(hService, SERVICE_CONTROL_STOP, &service_status);
if (bResult == FALSE)
{
printf("stop SelfServers error, GetLastError()=%ld\n", GetLastError());
}
::CloseServiceHandle(hService);
}
::CloseServiceHandle(hSCM);
}
从上面代码可以看出,停止服务首先要通过OpenSCManager打开服务控制管理器,然后通过OpenService打开服务。需要注意的是OpenService的最后一个参数需要包含SERVICE_STOP,当然也可以有其他参数,写成SERVICE_STOP|SERVICE_QUERY_STATUS的形式,这里只做服务停止不做服务查询就用不到SERVICE_QUERY_STATUS参数了。打开服务之后通过ControlService接口来对服务管理器进行控制,通过SERVICE_CONTROL_STOP参数告诉服务管理器要停止这个服务就可以了。最后要记得释放句柄。
服务的停止我们知道了,那么服务该怎么来卸载呢?上代码:
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM == NULL)
{
return FALSE;
}
SC_HANDLE hService = ::OpenService(hSCM, g_szServiceName, SERVICE_STOP | DELETE);
if (hService == NULL)
{
::CloseServiceHandle(hSCM);
return FALSE;
}
//SERVICE_STATUS status;
//::ControlService(hService, SERVICE_CONTROL_STOP, &status);
//CSysShellHelp theSysHelp;
//theSysHelp.KillProcessByName(_T(SERVER_EXE_NAME));
//DWORD dwPid = 0;
//if (theSysHelp.GetProcessIDByName(L"SelfServer.exe", &dwPid))
//{
// return FALSE;
//}
//else
//{
// return TRUE;
//}
//删除服务
BOOL bDelete = ::DeleteService(hService);
::CloseServiceHandle(hService);
::CloseServiceHandle(hSCM);
和停止服务模块很相似,只不过在停止服务之后增加了DeleteService接口来对服务进行删除。同样的,最后记得关闭打开的句柄。