简单的 C++ Windows 服务






4.92/5 (87投票s)
这篇文章展示了如何用 C++ 创建一个简单的 Windows 服务。
引言
本文展示了如何在 C++ 中创建一个基本的 Windows 服务。服务在许多开发场景中都非常有用,这取决于应用程序的架构。
背景
我发现的 C++ Windows 服务示例并不多。我使用 MSDN 编写了这个非常基本的 Windows 服务。
使用代码
一个服务至少需要以下几项
- 一个主入口点(像任何应用程序一样)
- 一个服务入口点
- 一个服务控制处理程序
您可以使用 Visual Studio 模板项目来帮助您入门。我只是创建了一个“空”的 Win32 控制台应用程序。
在开始主入口点之前,我们需要声明一些将在整个服务中使用的全局变量。为了更面向对象,您可以创建一个代表您的服务的类,并使用类成员而不是全局变量。为了简单起见,我将使用全局变量。
我们需要一个 SERVICE_STATUS
结构,它将用于向 Windows 服务控制管理器 (SCM) 报告服务的状态。
SERVICE_STATUS g_ServiceStatus = {0};
我们还需要一个 SERVICE_STATUS_HANDLE
,它用于在服务向 SCM 注册后引用我们的服务实例。
SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
以下是一些额外的全局变量和函数声明,将在我们进行的过程中使用和解释。
SERVICE_STATUS g_ServiceStatus = {0};
SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
HANDLE g_ServiceStopEvent = INVALID_HANDLE_VALUE;
VOID WINAPI ServiceMain (DWORD argc, LPTSTR *argv);
VOID WINAPI ServiceCtrlHandler (DWORD);
DWORD WINAPI ServiceWorkerThread (LPVOID lpParam);
#define SERVICE_NAME _T("My Sample Service")
主入口点
int _tmain (int argc, TCHAR *argv[])
{
SERVICE_TABLE_ENTRY ServiceTable[] =
{
{SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
{NULL, NULL}
};
if (StartServiceCtrlDispatcher (ServiceTable) == FALSE)
{
return GetLastError ();
}
return 0;
}
在主入口点,您快速调用 StartServiceCtrlDispatcher
,以便 SCM 可以调用您的服务入口点(在上面的示例中为 ServiceMain
)。您希望将任何初始化推迟到您的服务入口点,该入口点定义如下。
服务入口点
VOID WINAPI ServiceMain (DWORD argc, LPTSTR *argv)
{
DWORD Status = E_FAIL;
// Register our service control handler with the SCM
g_StatusHandle = RegisterServiceCtrlHandler (SERVICE_NAME, ServiceCtrlHandler);
if (g_StatusHandle == NULL)
{
goto EXIT;
}
// Tell the service controller we are starting
ZeroMemory (&g_ServiceStatus, sizeof (g_ServiceStatus));
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwServiceSpecificExitCode = 0;
g_ServiceStatus.dwCheckPoint = 0;
if (SetServiceStatus (g_StatusHandle , &g_ServiceStatus) == FALSE)
{
OutputDebugString(_T(
"My Sample Service: ServiceMain: SetServiceStatus returned error"));
}
/*
* Perform tasks necessary to start the service here
*/
// Create a service stop event to wait on later
g_ServiceStopEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
if (g_ServiceStopEvent == NULL)
{
// Error creating event
// Tell service controller we are stopped and exit
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
g_ServiceStatus.dwWin32ExitCode = GetLastError();
g_ServiceStatus.dwCheckPoint = 1;
if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE)
{
OutputDebugString(_T(
"My Sample Service: ServiceMain: SetServiceStatus returned error"));
}
goto EXIT;
}
// Tell the service controller we are started
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwCheckPoint = 0;
if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE)
{
OutputDebugString(_T(
"My Sample Service: ServiceMain: SetServiceStatus returned error"));
}
// Start a thread that will perform the main task of the service
HANDLE hThread = CreateThread (NULL, 0, ServiceWorkerThread, NULL, 0, NULL);
// Wait until our worker thread exits signaling that the service needs to stop
WaitForSingleObject (hThread, INFINITE);
/*
* Perform any cleanup tasks
*/
CloseHandle (g_ServiceStopEvent);
// Tell the service controller we are stopped
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwCheckPoint = 3;
if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE)
{
OutputDebugString(_T(
"My Sample Service: ServiceMain: SetServiceStatus returned error"));
}
EXIT:
return;
}
服务主入口点执行以下任务
- 初始化任何必要项,这些项我们已从主入口点推迟。
- 注册服务控制处理程序,它将处理服务停止、暂停、继续、关闭等控制命令。这些通过
SERVICE_STATUS
结构的dwControlsAccepted
字段作为位掩码注册。 - 设置服务状态为
SERVICE_PENDING
,然后为SERVICE_RUNNING
。在任何错误和退出时,将状态设置为SERVICE_STOPPED
。在将状态设置为SERVICE_STOPPED
或SERVICE_PENDING
时,始终将SERVICE_STATUS.dwControlsAccepted
设置为 0。 - 执行启动任务。比如创建线程/事件/互斥锁/IPC/等等。
服务控制处理程序
VOID WINAPI ServiceCtrlHandler (DWORD CtrlCode)
{
switch (CtrlCode)
{
case SERVICE_CONTROL_STOP :
if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING)
break;
/*
* Perform tasks necessary to stop the service here
*/
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwCheckPoint = 4;
if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE)
{
OutputDebugString(_T(
"My Sample Service: ServiceCtrlHandler: SetServiceStatus returned error"));
}
// This will signal the worker thread to start shutting down
SetEvent (g_ServiceStopEvent);
break;
default:
break;
}
}
服务控制处理程序已在您的服务主入口点中注册。每个服务都必须有一个处理程序来处理来自 SCM 的控制请求。控制处理程序必须在 30 秒内返回,否则 SCM 将返回一个错误,声明该服务无响应。这是因为处理程序将在 SCM 的上下文中被调用,并将一直保持 SCM,直到它从处理程序返回。
我只实现了并支持了 SERVICE_CONTROL_STOP
请求。您可以处理其他请求,如 SERVICE_CONTROL_CONTINUE
, SERVICE_CONTROL_INTERROGATE
, SERVICE_CONTROL_PAUSE
, SERVICE_CONTROL_SHUTDOWN
以及 Handler
或 HandlerEx
函数支持的其他请求,这些函数可以使用 RegisterServiceCtrlHandler(Ex)
函数注册。
服务工作线程
DWORD WINAPI ServiceWorkerThread (LPVOID lpParam)
{
// Periodically check if the service has been requested to stop
while (WaitForSingleObject(g_ServiceStopEvent, 0) != WAIT_OBJECT_0)
{
/*
* Perform main service function here
*/
// Simulate some work by sleeping
Sleep(3000);
}
return ERROR_SUCCESS;
}
此示例服务工作线程只休眠并检查服务是否已收到停止控制。一旦收到停止控制,服务控制处理程序就会设置 g_ServiceStopEvent
事件。服务工作线程中断并退出。这向服务主例程发出信号以返回,并有效地停止服务。
安装服务
您可以通过运行以下命令从命令提示符安装服务
C:\>sc create "My Sample Service" binPath= C:\SampleService.exe
binPath= 和值之间需要一个空格 [?]。另外,使用服务的可执行文件的完整绝对路径。
您现在应该在 Windows 服务控制台中看到该服务。从这里,您可以启动和停止该服务。
卸载服务
您可以通过运行以下命令从命令提示符卸载服务
C:\>sc delete "My Sample Service"
历史
- 2012 年 11 月 28 日:文章和代码的初始版本。
- 2012 年 11 月 29 日:改进了代码并修复了文章示例代码中的一个拼写错误。
- 2015 年 11 月 3 日:根据用户评论更新了有关如何安装服务的详细信息。