65.9K
CodeProject 正在变化。 阅读更多。
Home

简单的 C++ Windows 服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (87投票s)

2012 年 11 月 29 日

CPOL

3分钟阅读

viewsIcon

456434

downloadIcon

28326

这篇文章展示了如何用 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_STOPPEDSERVICE_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 以及 HandlerHandlerEx 函数支持的其他请求,这些函数可以使用 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 日:根据用户评论更新了有关如何安装服务的详细信息。
© . All rights reserved.