用 C++ 与 Windows 服务交互






4.97/5 (47投票s)
使用基于 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.h 和 winnt.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
};
该头文件还定义了一个名为 ServiceString
的 string
类型,它根据用于构建本机项目的字符集,可以是 std::string
或 std::wstring
。这就是 C++ 服务组件中用于 string
的类型。
#ifdef UNICODE
#define ServiceString std::wstring
#else
#define ServiceString std::string
#endif
服务句柄
像 OpenSCManager 和 OpenService(还有 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
,表示对所有服务操作的访问权限。构造函数中执行以下调用:
- OpenSCManager 以建立与本地计算机上服务控制管理器的连接,并打开默认的服务控制管理器数据库。
- OpenService 以打开服务;如果此操作失败,则关闭服务控制管理器句柄。
- 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日:初始版本