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

用 C++ 与 Windows 服务交互

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (47投票s)

2016 年 5 月 6 日

CPOL

10分钟阅读

viewsIcon

65837

downloadIcon

2147

使用基于 Windows 服务 API 构建的 C++ 组件,与 Windows 服务进行交互、控制和配置。

引言

本文介绍如何从用 C++ 编写的本机应用程序与 Windows 服务进行交互、控制和配置。本文的目的是介绍一些用于与服务交互的 Windows API,并在此过程中构建可重用的 C++ 包装组件。但是,本文不会全面介绍所有 Windows 服务 API。

有关完整的 Windows API 参考,请参阅 服务函数

一般步骤

为了与现有服务进行交互,您必须遵循以下一般步骤:

  • 调用 OpenSCManager 以建立与指定计算机上的服务控制管理器的连接,并打开指定的服务控制管理器数据库。通过此调用,您必须指定 所需访问权限。要枚举服务,您必须指定 SC_MANAGER_ENUMERATE_SERVICE。要打开服务、查询、更改状态等,您需要 SC_MANAGER_CONNECT。对于其他操作,例如创建服务或锁定服务数据库,您需要指定其他访问权限代码。但是,在这种情况下,进程必须以管理员权限运行,否则调用将失败。
  • 调用 OpenService 以打开现有服务。
  • 调用查询、控制或配置服务的函数(例如,使用 QueryServiceStatusEx 获取服务状态,使用 QueryServiceConfig 获取服务配置数据,使用 ChangeServiceConfig 更改服务配置数据,或使用 EnumDependentServices 枚举依赖服务)。
  • 调用 CloseServiceHandle 关闭服务控制管理器(由 OpenSCManager 返回的句柄)和服务(由 OpenService 返回的句柄)的句柄。

使用 C++ API 的示例

随附的源代码包含用 C++ 编写的类型和函数,这些类型和函数封装了 C Windows API,使其更易于在 C++ 应用程序中使用。这些组件将使我们能够编写如下代码。

枚举本地计算机上所有服务的示例

auto services = ServiceEnumerator::EnumerateServices();
for (auto const & s : services)
{
  std::wcout << "Name:    " << s.ServiceName << std::endl
             << "Display: " << s.DisplayName << std::endl
             << "Status:  " << ServiceStatusToString
                (static_cast<ServiceStatus>(s.Status.dwCurrentState)) << std::endl
             << "--------------------------" << std::endl;
}

打开本地计算机上的服务、读取和更新其配置、读取和更改其状态的示例

// open the service
auto service = ServiceController{ L"LanmanWorkstation" };

auto print_status = [&service]() {
  std::wcout << "Status:              
              " << ServiceStatusToString(service.GetStatus()) << std::endl; 
};

auto print_config = [](ServiceConfig const config) {
  std::wcout << "---------------------" << std::endl;
  std::wcout << "Start name:          " << config.GetStartName() << std::endl;
  std::wcout << "Display name:        " << config.GetDisplayName() << std::endl;
  std::wcout << "Description:         " << config.GetDescription() << std::endl;
  std::wcout << "Type:                
                 " << ServiceTypeToString(config.GetType()) << std::endl;
  std::wcout << "Start type:          
                 " << ServiceStartTypeToString(config.GetStartType()) << std::endl;
  std::wcout << "Error control:       
                 " << ServiceErrorControlToString(config.GetErrorControl()) << std::endl;
  std::wcout << "Binary path:         " << config.GetBinaryPathName() << std::endl;
  std::wcout << "Load ordering group: " << config.GetLoadOrderingGroup() << std::endl;
  std::wcout << "Tag ID:              " << config.GetTagId() << std::endl;
  std::wcout << "Dependencies:        ";
  for (auto const & d : config.GetDependencies()) std::wcout << d << ", ";
  std::wcout << std::endl;
  std::wcout << "---------------------" << std::endl;
};

// read the service configuration, temporary change its description 
// and then restore the old one
{
  auto config = service.GetServiceConfig();

  print_config(config);

  auto oldDescription = config.GetDescription();

  auto newDescription = _T("This is a sample description.");

  config.ChangeDescription(newDescription);

  config.Refresh();

  print_config(config);

  config.ChangeDescription(oldDescription);

  config.Refresh();

  print_config(config);
}

// check the service status
print_status();

// start the service if the service is currently stopped
if (service.GetStatus() == ServiceStatus::Stopped)
{
  service.Start();

  print_status();

  service.WaitForStatus(ServiceStatus::Running);

  print_status();
}

// if the service and running and it supports pause and continue 
// then first pause and then resume the service
if (service.GetStatus() == ServiceStatus::Running && service.CanPauseContinue())
{
  service.Pause();

  print_status();

  service.WaitForStatus(ServiceStatus::Paused);

  print_status();

  service.Continue();

  print_status();

  service.WaitForStatus(ServiceStatus::Running);

  print_status();
}

在本文的其余部分,我将介绍 C++ API,并解释 C Windows API 在这些包装器中是如何使用的。

服务常量

Windows API 使用数值作为类型、状态、状态、配置设置等的参数。这些在头文件 winsvc.hwinnt.h 中定义为宏。然而,在 C++ 代码中,我更倾向于使用常量或枚举。因此,我为所有我需要与服务 API 一起使用的值定义了 enum 类。这些在 ServiceContants.h 头文件中可用。

enum class ServiceStatus 
{
   Unknown     = 0,
   Stopped     = SERVICE_STOPPED,
   Starting    = SERVICE_START_PENDING,
   Stopping    = SERVICE_STOP_PENDING,
   Running     = SERVICE_RUNNING,
   Continuing  = SERVICE_CONTINUE_PENDING,
   Pausing     = SERVICE_PAUSE_PENDING,
   Paused      = SERVICE_PAUSED
};

enum class ServiceControls
{
   Stop                                = SERVICE_ACCEPT_STOP,
   PauseAndContinue                    = SERVICE_ACCEPT_PAUSE_CONTINUE,
   ChangeParams                        = SERVICE_ACCEPT_PARAMCHANGE,
   ChangeBindings                      = SERVICE_ACCEPT_NETBINDCHANGE,
   PreShutdown                         = SERVICE_ACCEPT_PRESHUTDOWN,
   ShutdownNotification                = SERVICE_ACCEPT_SHUTDOWN,
   HardwareProfileChangedNotification  = SERVICE_ACCEPT_HARDWAREPROFILECHANGE,
   PowerChangedNotification            = SERVICE_ACCEPT_POWEREVENT,
   SessionChangedNotification          = SERVICE_ACCEPT_SESSIONCHANGE,
   TriggerEventNotification            = SERVICE_ACCEPT_TRIGGEREVENT,
   TimeChangeNotification              = SERVICE_ACCEPT_TIMECHANGE,
   UserModeNotification                = 0x00000800, //SERVICE_ACCEPT_USERMODEREBOOT
};

enum class ServiceType
{
   KernelDriver       = SERVICE_KERNEL_DRIVER,
   FileSystemDriver   = SERVICE_FILE_SYSTEM_DRIVER,
   Adapter            = SERVICE_ADAPTER,
   RecognizerDriver   = SERVICE_RECOGNIZER_DRIVER,
   Win32OwnProcess    = SERVICE_WIN32_OWN_PROCESS,
   Win32ShareProcess  = SERVICE_WIN32_SHARE_PROCESS,
   InteractiveDriver  = SERVICE_INTERACTIVE_PROCESS,
   Driver             = SERVICE_DRIVER,
   Win32              = SERVICE_WIN32,
   All                = SERVICE_TYPE_ALL
};

enum class ServiceStartType
{
   Boot     = SERVICE_BOOT_START,
   System   = SERVICE_SYSTEM_START,
   Auto     = SERVICE_AUTO_START,
   Demand   = SERVICE_DEMAND_START,
   Disabled = SERVICE_DISABLED,
};

enum class ServiceErrorControl
{
   Ignore   = SERVICE_ERROR_IGNORE,
   Normal   = SERVICE_ERROR_NORMAL,
   Severe   = SERVICE_ERROR_SEVERE,
   Critical = SERVICE_ERROR_CRITICAL,
};

enum class ServiceState
{
   Active   = SERVICE_ACTIVE,
   Inactive = SERVICE_INACTIVE,
   All      = SERVICE_STATE_ALL
};

该头文件还定义了一个名为 ServiceStringstring 类型,它根据用于构建本机项目的字符集,可以是 std::stringstd::wstring。这就是 C++ 服务组件中用于 string 的类型。

#ifdef UNICODE
#define ServiceString   std::wstring
#else
#define ServiceString   std::string
#endif

服务句柄

OpenSCManagerOpenService(还有 CreateService)这样的函数会返回一个 SC_HANDLE,它是一个指针值。然而,在需要该句柄的代码执行完毕后,必须关闭该句柄。为了简化在所有情况下正确关闭服务句柄,该句柄将被封装在一个类中,该类在对象销毁时以 RAII 的方式调用 CloseServiceHandle。这种实现可在 ServiceHandle.h 头文件中找到。

class ServiceHandle
{
   SC_HANDLE _handle = nullptr;

   void Close()
   {
      if (_handle != nullptr)
         ::CloseServiceHandle(_handle);
   }
   
public:
   ServiceHandle(SC_HANDLE const handle = nullptr) noexcept :_handle(handle) {}

   ServiceHandle(ServiceHandle&& other) noexcept : _handle(std::move(other._handle)) {}

   ServiceHandle& operator=(SC_HANDLE const handle)
   {
      if (_handle != handle)
      {
         Close();

         _handle = handle;
      }

      return *this;
   }

   ServiceHandle& operator=(ServiceHandle&& other)
   {
      if (this != &other)
      {
         _handle = std::move(other._handle);
         other._handle = nullptr;
      }

      return *this;
   }

   operator SC_HANDLE() const noexcept { return _handle; }

   explicit operator bool() const noexcept { return _handle != nullptr; }
   
   ~ServiceHandle() 
   {
      Close();
   }
};

ServiceController 类

ServiceController 类代表一个 Windows 服务,允许您查询和更改服务状态、查询和更改配置数据等。这是一个最小化的实现,可以根据需要进行扩展。该实现可在 ServiceController.h 头文件中找到。

该类的构造函数接受服务名称以及可选的所需访问权限。如果指定了访问权限,则值为 SERVICE_ALL_ACCESS,表示对所有服务操作的访问权限。构造函数中执行以下调用:

  1. OpenSCManager 以建立与本地计算机上服务控制管理器的连接,并打开默认的服务控制管理器数据库。
  2. OpenService 以打开服务;如果此操作失败,则关闭服务控制管理器句柄。
  3. QueryServiceStatusEx 以检索关于已接受控件的信息,例如服务是否可以被停止、暂停和继续,或者它是否可以接收各种事件的通知。
class ServiceController
{
public:
   ServiceController(ServiceString name, DWORD access = SERVICE_ALL_ACCESS)
   {
      srvName = name;
      scHandle = ::OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT);
      if (scHandle)
      {
         srvHandle = ::OpenService(scHandle, name.c_str(), access);
         if (!srvHandle)
         {
            scHandle = nullptr;
         }
         else
         {
            auto bytesNeeded = DWORD{ 0 };
            auto ssp = SERVICE_STATUS_PROCESS{ 0 };

            auto result = ::QueryServiceStatusEx(
               srvHandle,
               SC_STATUS_PROCESS_INFO,
               reinterpret_cast<LPBYTE>(&ssp),
               sizeof(ssp),
               &bytesNeeded);

            if (result != 0)
            {
               auto setControl = [ssp](std::map<ServiceControls, bool>& controls, 
                                 ServiceControls const control) 
               { 
                  controls[control] = 
                           (ssp.dwControlsAccepted & static_cast<int>(control)) != 0; 
               };

               setControl(acceptedControls, ServiceControls::Stop);
               setControl(acceptedControls, ServiceControls::PauseAndContinue);
               setControl(acceptedControls, ServiceControls::ChangeParams);
               setControl(acceptedControls, ServiceControls::ChangeBindings);
               setControl(acceptedControls, ServiceControls::PreShutdown);
               setControl(acceptedControls, ServiceControls::ShutdownNotification);
               setControl(acceptedControls, 
                          ServiceControls::HardwareProfileChangedNotification);
               setControl(acceptedControls, ServiceControls::PowerChangedNotification);
               setControl(acceptedControls, ServiceControls::SessionChangedNotification);
               setControl(acceptedControls, ServiceControls::TriggerEventNotification);
               setControl(acceptedControls, ServiceControls::TimeChangeNotification);
               setControl(acceptedControls, ServiceControls::UserModeNotification);
            }
         }
      }
   }
   
private:
   ServiceHandle scHandle;
   ServiceHandle srvHandle;
   ServiceString srvName;

   std::map<ServiceControls, bool> acceptedControls = 
   {
      { ServiceControls::Stop, false},
      { ServiceControls::PauseAndContinue, false },
      { ServiceControls::ChangeParams, false },
      { ServiceControls::ChangeBindings, false },
      { ServiceControls::PreShutdown, false },
      { ServiceControls::ShutdownNotification, false },
      { ServiceControls::HardwareProfileChangedNotification, false },
      { ServiceControls::PowerChangedNotification, false },
      { ServiceControls::SessionChangedNotification, false },
      { ServiceControls::TriggerEventNotification, false },
      { ServiceControls::TimeChangeNotification, false },
      { ServiceControls::UserModeNotification, false },
   };   
};

您可以使用 CanAcceptControl() 方法检查服务在其处理函数中接受和处理哪些控件代码。它接受由 ServiceControls 枚举定义的值,并返回一个 bool,指示控件代码是否被接受和处理。使用此方法定义了几个额外的函数。

bool CanAcceptControl(ServiceControls const control) const
{
  auto it = acceptedControls.find(control);
  return it != std::end(acceptedControls) ? it->second : false;
}

bool CanPauseContinue() const          
          { return CanAcceptControl(ServiceControls::PauseAndContinue); }
bool CanShutdown() const               
          { return CanAcceptControl(ServiceControls::ShutdownNotification); }
bool CanStop() const                   
          { return CanAcceptControl(ServiceControls::Stop); }

检查状态

函数 QueryServiceStatusEx 也用于检查服务状态(例如停止、运行、正在启动等)。此函数在 SERVICE_STATUS_PROCESS 结构中返回信息。dwCurrentState 成员表示由 ServiceStatus 枚举定义的当前状态。

ServiceStatus GetStatus()
{
  auto status = ServiceStatus::Unknown;

  if (srvHandle)
  {
     auto bytesNeeded = DWORD{ 0 };
     auto ssp = SERVICE_STATUS_PROCESS {0};

     auto result = ::QueryServiceStatusEx(
        srvHandle,
        SC_STATUS_PROCESS_INFO,
        reinterpret_cast<LPBYTE>(&ssp),
        sizeof(ssp),
        &bytesNeeded);

     if (result != 0)
     {
        status = static_cast<ServiceStatus>(ssp.dwCurrentState)
     }
  }

  return status;
}

更改状态

服务可以启动和停止,有些服务可以暂停和继续。ServiceController 类提供了所有这四种操作的方法。

要启动服务,我们必须调用 StartService。此函数接受服务句柄以及可选的要传递给服务的 ServiceMain 函数的参数。在此实现中,不传递任何参数。

bool Start()
{
  auto success = false;

  if (srvHandle)
  {
     auto result = ::StartService(srvHandle, 0, nullptr);
     success = result != 0;
  }

  return success;
}

Stop()Pause()Continue() 方法是使用 ControlService 函数实现的。此函数接受服务句柄、表示要发送到服务的通知的控件代码以及一个 SERVICE_STATUS 类型的对象,该对象接收最新的服务状态信息。

bool Stop()
{
  auto success = false;

  if (srvHandle)
  {
     success = StopDependentServices();

     if (success)
     {
        auto ssp = SERVICE_STATUS_PROCESS{ 0 };
        success = ChangeServiceStatus(srvHandle, SERVICE_CONTROL_STOP, ssp);
     }
  }

  return success;
}

bool Pause()
{
  auto success = false;

  if (srvHandle)
  {
     auto ssp = SERVICE_STATUS_PROCESS{ 0 };
     success = ChangeServiceStatus(srvHandle, SERVICE_CONTROL_PAUSE, ssp);
  }

  return success;
}

bool Continue()
{
  auto success = false;

  if (srvHandle)
  {
     auto ssp = SERVICE_STATUS_PROCESS{ 0 };
     success = ChangeServiceStatus(srvHandle, SERVICE_CONTROL_CONTINUE, ssp);
  }

  return success;
}

static bool ChangeServiceStatus
    (SC_HANDLE const handle, DWORD const controlCode, SERVICE_STATUS_PROCESS& ssp)
{
  auto success = false;

  if (handle)
  {
     auto result = ::ControlService(
        handle,
        controlCode,
        reinterpret_cast<LPSERVICE_STATUS>(&ssp));

     success = result != 0;
  }

  return success;
}

Stop() 方法的实现与 Pause()Continue() 的实现略有不同,因为服务可能具有依赖服务,并且在停止服务时也需要停止它们。停止依赖服务是在 StopDependentServices() private 函数中实现的。该函数执行以下操作:

  • 通过调用 EnumDependentServices 来枚举依赖服务。与所有其他 API 一样,它首先使用 null 输出缓冲区调用以获取缓冲区大小,然后再次使用大小正确的缓冲区调用。此函数返回 ENUM_SERVICE_STATUS 数组和元素数量。
  • 对于每个依赖服务,我们需要打开服务、停止服务并等待服务实际停止。

如果任何依赖服务未能正确停止,其余例程将不会执行,主服务也不会被停止。请注意,在等待服务时,我们默认设置了 30 秒的超时时间。如果服务在此间隔内未停止,则例程失败。

bool StopDependentServices()
{
  auto ess = ENUM_SERVICE_STATUS{ 0 };
  auto bytesNeeded = DWORD{ 0 };
  auto count = DWORD{ 0 };

  if (!::EnumDependentServices(
     srvHandle,
     SERVICE_ACTIVE,
     nullptr,
     0,
     &bytesNeeded,
     &count))
  {
     if (GetLastError() != ERROR_MORE_DATA)
        return false;

     std::vector<unsigned char> buffer(bytesNeeded, 0);

     if (!::EnumDependentServices(
        srvHandle,
        SERVICE_ACTIVE,
        reinterpret_cast<LPENUM_SERVICE_STATUS>(buffer.data()),
        bytesNeeded,
        &bytesNeeded,
        &count))
     {
        return false;
     }

     for (auto i = DWORD{ 0 }; i < count; ++i)
     {
        auto ess = static_cast<ENUM_SERVICE_STATUS>
                   (*(reinterpret_cast<LPENUM_SERVICE_STATUS>(buffer.data() + i)));

        ServiceHandle handle = ::OpenService(
           scHandle,
           ess.lpServiceName,
           SERVICE_STOP | SERVICE_QUERY_STATUS);

        if (!handle)
           return false;

        auto ssp = SERVICE_STATUS_PROCESS{ 0 };

        if (!ChangeServiceStatus(handle, SERVICE_CONTROL_STOP, ssp))
           return false;

        if (!WaitForStatus(handle, ssp, ServiceStatus::Stopped))
           return false;
     }
  }

  return true;
}

等待状态更改

当您启动、停止、暂停或继续服务时,状态更改可能不会立即发生。例如,启动服务可能需要几秒钟。Start()Stop()Pause()Continue() 方法会立即返回,仅表示更改请求已成功完成,而没有表示状态确实已更改。(请注意,Stop() 在请求停止服务后立即返回,但会等待所有依赖服务先停止。)

要检查状态是否已更改,您需要定期查询服务状态,直到其更改为所需状态。ServiceController 类提供了一个执行此操作的方法,名为 WaitForStatus()。该方法以所需状态和超时(默认为 30 秒)作为参数。它首先查询服务状态,如果服务未处于所需状态,则使当前线程休眠一段时间,然后重复操作。循环要么在状态已更改为所需状态时结束,要么在超时到期时结束。请注意,睡眠间隔是服务等待提示的 1/10,但不得小于 1 秒,不得大于 10 秒。

bool WaitForStatus(ServiceStatus desiredStatus, 
                   std::chrono::milliseconds const timeout = 30000ms)
{
  auto success = false;

  if (srvHandle)
  {
     auto ssp = SERVICE_STATUS_PROCESS{ 0 };

     auto bytesNeeded = DWORD{ 0 };

     if (::QueryServiceStatusEx(
        srvHandle,
        SC_STATUS_PROCESS_INFO,
        reinterpret_cast<LPBYTE>(&ssp),
        sizeof(ssp),
        &bytesNeeded))
     {
        success = WaitForStatus(srvHandle, ssp, desiredStatus, timeout);
     }
  }

  return success;
}

static std::chrono::milliseconds GetWaitTime(DWORD const waitHint)
{
  auto waitTime = waitHint / 10;

  if (waitTime < 1000)
     waitTime = 1000;
  else if (waitTime > 10000)
     waitTime = 10000;

  return std::chrono::milliseconds(waitTime);
}

static bool WaitForStatus(SC_HANDLE const handle, 
                          SERVICE_STATUS_PROCESS& ssp, 
                          ServiceStatus const desireStatus, 
                          std::chrono::milliseconds const timeout = 30000ms)
{
  auto success = ssp.dwCurrentState == static_cast<DWORD>(desireStatus);

  if (!success && handle)
  {
     auto start = std::chrono::high_resolution_clock::now();
     auto waitTime = GetWaitTime(ssp.dwWaitHint);

     while (ssp.dwCurrentState != static_cast<DWORD>(desireStatus))
     {
        std::this_thread::sleep_for(waitTime);

        auto bytesNeeded = DWORD{ 0 };

        if (!::QueryServiceStatusEx(
           handle,
           SC_STATUS_PROCESS_INFO,
           reinterpret_cast<LPBYTE>(&ssp),
           sizeof(ssp),
           &bytesNeeded))
           break;

        if (ssp.dwCurrentState == static_cast<DWORD>(desireStatus))
        {
           success = true;
           break;
        }

        if (std::chrono::high_resolution_clock::now() - start > timeout)
           break;
     }
  }

  return success;
}

删除服务

要删除服务,我们必须调用 DeleteService 函数。然而,此函数仅将服务标记为从服务控制管理器数据库中删除。只有在所有打开的服务句柄都已关闭且服务未运行时,服务才会被删除。如果在调用时服务正在运行,则在系统重启时会删除数据库条目。

bool Delete()
{
  auto success = false;

  if (srvHandle)
  {
     success = ::DeleteService(srvHandle) != 0;

     if (success)
        srvHandle = nullptr;
  }

  return success;
}

ServiceConfig 类

ServiceConfig 类代表服务的配置参数。它允许您读取和更新其中一些配置参数,并且可以根据需要进行扩展以包含其他参数。它可在 ServiceConfig.h 头文件中找到。

自动读取的参数包括:服务类型、启动类型、启动失败时的错误控制、路径名、加载顺序组名、标记 ID、依赖服务、启动名称、显示名称和描述。

在当前实现中可以修改的参数是启动类型、错误控制和描述。对于其他参数,必须通过适当的方法扩展该类。

可以使用 static 方法 ServiceConfig::Create 创建该类的一个实例,该方法接受现有服务的句柄。创建对象后,此函数会调用其 Refresh() 方法来读取配置参数。

Refresh() 方法执行以下操作:

  • 调用 QueryServiceConfig 以检索有关所引用服务​​的配置参数。
  • 调用 QueryServiceConfig2 以检索有关所引用服务​​的可选配置参数,但在本实现中,仅为服务的描述。

要修改配置参数,有几种可用方法:

  • ChangeStartType() 更改服务启动类型(例如自动、按需、禁用等),ChangeStartErrorControl() 更改服务启动失败时的错误控制。这两个方法都使用 ChangeServiceConfig 来更改配置参数。请注意,调用此函数时,如果这些参数的当前值不应被更改,您必须为服务类型、启动类型和错误控制指定 SERVICE_NO_CHANGE
  • ChangeDescription() 更改服务的描述。此方法调用 ChangeServiceConfig2 来更改可选配置参数,在此情况下为描述。
class ServiceConfig
{
   SC_HANDLE srvHandle;
   ServiceType type;
   ServiceStartType startType;
   ServiceErrorControl errorControl;
   ServiceString pathName;
   ServiceString loadOrderGroup;
   DWORD tagId;
   std::vector<ServiceString> dependencies;
   ServiceString startName;
   ServiceString displayName;
   ServiceString description;

public:
   ServiceType                      GetType() const               { return type; }
   ServiceStartType                 GetStartType() const          { return startType; }
   ServiceErrorControl              GetErrorControl() const       { return errorControl; }
   ServiceString                    GetBinaryPathName() const     { return pathName; }
   ServiceString                    GetLoadOrderingGroup() const  { return loadOrderGroup; }
   DWORD                            GetTagId() const              { return tagId; }
   std::vector<ServiceString> const GetDependencies() const       { return dependencies; }
   ServiceString                    GetStartName() const          { return startName; }
   ServiceString                    GetDisplayName() const        { return displayName; }
   ServiceString                    GetDescription() const        { return description; }

public:
   static ServiceConfig Create(SC_HANDLE const handle)
   {
      auto config = ServiceConfig{};
      config.srvHandle = handle;

      config.Refresh();

      return config;
   }

   bool ChangeStartType(ServiceStartType const type)
   {
      auto result = false;

      if (ChangeServiceConfig(
         srvHandle,
         SERVICE_NO_CHANGE,
         static_cast<DWORD>(type),
         SERVICE_NO_CHANGE,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr))
      {
         startType = type;
         result = true;
      }

      return result;
   }

   bool ChangeStartErrorControl(ServiceErrorControl const control)
   {
      auto result = false;

      if (ChangeServiceConfig(
         srvHandle,
         SERVICE_NO_CHANGE,
         SERVICE_NO_CHANGE,
         static_cast<DWORD>(control),         
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr))
      {
         errorControl = control;
         result = true;
      }

      return result;
   }

   bool ChangeDescription(ServiceString const newDescription)
   {
      auto result = false;

      auto sd = SERVICE_DESCRIPTION {};
      sd.lpDescription = const_cast<LPTSTR>(newDescription.c_str());

      if (ChangeServiceConfig2(srvHandle, SERVICE_CONFIG_DESCRIPTION, &sd))
      {
         description = newDescription;
         result = true;
      }

      return result;
   }

   void Refresh()
   {
      auto bytesNeeded = DWORD{ 0 };

      if (!QueryServiceConfig(
         srvHandle,
         nullptr,
         0,
         &bytesNeeded))
      {
         if (ERROR_INSUFFICIENT_BUFFER == ::GetLastError())
         {
            std::vector<unsigned char> buffer(bytesNeeded, 0);

            auto lpsc = reinterpret_cast<LPQUERY_SERVICE_CONFIG>(buffer.data());
            if (QueryServiceConfig(
               srvHandle,
               lpsc,
               bytesNeeded,
               &bytesNeeded))
            {
               type = (ServiceType)lpsc->dwServiceType;
               startType = (ServiceStartType)lpsc->dwStartType;
               errorControl = (ServiceErrorControl)lpsc->dwErrorControl;
               pathName = lpsc->lpBinaryPathName;
               loadOrderGroup = lpsc->lpLoadOrderGroup;
               tagId = lpsc->dwTagId;
               dependencies = SplitDoubleNullTerminatedString(lpsc->lpDependencies);
               startName = lpsc->lpServiceStartName;
               displayName = lpsc->lpDisplayName;
            }
         }
      }

      bytesNeeded = 0;
      if (!QueryServiceConfig2(
         srvHandle,
         SERVICE_CONFIG_DESCRIPTION,
         nullptr,
         0,
         &bytesNeeded))
      {
         if (ERROR_INSUFFICIENT_BUFFER == ::GetLastError())
         {
            std::vector<unsigned char> buffer(bytesNeeded, 0);

            if (QueryServiceConfig2(
               srvHandle,
               SERVICE_CONFIG_DESCRIPTION,
               reinterpret_cast<LPBYTE>(buffer.data()),
               bytesNeeded,
               &bytesNeeded))
            {
               auto lpsd = reinterpret_cast<LPSERVICE_DESCRIPTION>(buffer.data());

               description = lpsd->lpDescription;
            }
         }
      }
   }

private:
   static std::vector<ServiceString> SplitDoubleNullTerminatedString(LPCTSTR text)
   {
      std::vector<ServiceString> texts;
      LPCTSTR ptr = text;
      do 
      {
         texts.push_back(ptr);
         ptr += texts.back().size() + 1;
      } while (*ptr != _T('\0'));

      return texts;
   }
};

要检索服务的配置参数,您应该使用 ServiceController 类的 GetServiceConfig() 方法。它返回一个 ServiceConfig 类的实例。

ServiceConfig GetServiceConfig()
{
  return ServiceConfig::Create(srvHandle);
}

注意:如果您显式调用 ServiceConfig::Create() 方法,则必须确保在不再需要与服务交互时正确关闭 SC_HANDLE

ServiceEnumerator 类

ServiceEnumerator 类基本上是一个用于枚举现有服务的实用类。它包含一个名为 EnumerateServices 的单个 static 方法,该方法接受服务类型和状态、计算机和服务控制管理器名称以及加载顺序组名称的几个参数。使用这些参数的默认值,该函数将在本地计算机上枚举所有服务,无论其类型和状态如何。

为了枚举服务,此函数:

  • 调用 OpenSCManager 并将 SC_MANAGER_ENUMERATE_SERVICE 指定为所需访问权限。
  • 调用 EnumServicesStatusEx 并将 SC_ENUM_PROCESS_INFO 指定为信息级别,以检索服务名称和服务状态信息。该函数被调用两次,第一次使用 null 缓冲区用于输出数据以获取所需的实际大小,第二次使用大小正确的缓冲区。但是,此函数对输出缓冲区大小的限制为 256 KB,这意味着它可能一次无法返回所有服务。因此,该函数必须实际在循环中调用,直到不再返回数据为止。为了跟踪新调用应该从哪里开始,该函数有一个称为 lpResumeHandle 的特殊参数。这是一个输入输出参数。输入时,它指定枚举的起点,第一次调用时必须设置为 0;输出时,如果函数成功,则设置为 0,或者如果函数因 ERROR_MORE_DATA 失败,则设置为下一个服务条目的索引。
struct ServiceStatusProcess
{
   ServiceString           ServiceName;
   ServiceString           DisplayName;
   SERVICE_STATUS_PROCESS  Status;
};

class ServiceEnumerator
{
public:
   static std::vector<ServiceStatusProcess> EnumerateServices(
      ServiceType const type = ServiceType::All, 
      ServiceState const state = ServiceState::All, 
      ServiceString const * machine = nullptr,
      ServiceString const * dbname = nullptr,
      ServiceString const * groupName = nullptr)
   {
      std::vector<ServiceStatusProcess> ssps;

      auto scHandle = ServiceHandle
      { 
         ::OpenSCManager(
            machine == nullptr ? nullptr : machine->c_str(), 
            dbname == nullptr ? nullptr : dbname->c_str(), 
            SC_MANAGER_ENUMERATE_SERVICE) 
      };
      auto bytesNeeded = DWORD{ 0 };
      auto servicesReturnedCount = DWORD{ 0 };
      auto resumeHandle = DWORD{ 0 };

      do
      {
         if (!EnumServicesStatusEx(
            scHandle,
            SC_ENUM_PROCESS_INFO,
            static_cast<DWORD>(type),
            static_cast<DWORD>(state),
            nullptr,
            0,
            &bytesNeeded,
            &servicesReturnedCount,
            &resumeHandle,
            groupName == nullptr ? nullptr : groupName->c_str()))
         {
            if (ERROR_MORE_DATA == ::GetLastError())
            {
               std::vector<unsigned char> buffer(bytesNeeded, 0);

               if (EnumServicesStatusEx(
                  scHandle,
                  SC_ENUM_PROCESS_INFO,
                  static_cast<DWORD>(type),
                  static_cast<DWORD>(state),
                  reinterpret_cast<LPBYTE>(buffer.data()),
                  bytesNeeded,
                  &bytesNeeded,
                  &servicesReturnedCount,
                  nullptr,
                  groupName == nullptr ? nullptr : groupName->c_str()))
               {
                  auto essp = reinterpret_cast<LPENUM_SERVICE_STATUS_PROCESS>(buffer.data());

                  for (auto i = DWORD{ 0 }; i < servicesReturnedCount; ++i)
                  {
                     auto ssp = ServiceStatusProcess{};
                     ssp.ServiceName = essp[i].lpServiceName;
                     ssp.DisplayName = essp[i].lpDisplayName;
                     ssp.Status = essp[i].ServiceStatusProcess;

                     ssps.push_back(ssp);
                  }
               }
               else break;
            }
            else break;
         }
      } while (resumeHandle != 0);

      return ssps;
   }
};

结论

在本文中,我们构建了几个用于管理 Windows 服务的 C++ 组件,以简化 C++ 应用程序中 Windows 服务 API 的使用。在此过程中,我们还了解了 Windows 服务 API 的内容以及它们应该如何使用。有关 API 的完整参考,请参阅 MSDN 中的 服务函数

历史

  • 2016年5月6日:初始版本
© . All rights reserved.