|
|
冷静的柳树 · 前端基本知识(四):JS的异步模式:1、回调 ...· 1 年前 · |
|
|
善良的伏特加 · SSH配置Linux免密登录_51CTO博客 ...· 2 年前 · |
|
|
腹黑的开心果 · HttpURLConnection ...· 2 年前 · |
|
|
个性的韭菜 · 使用curl命令获取网站的header头及状 ...· 2 年前 · |
| const dword |
| https://www.codeproject.com/Articles/1098263/Interact-with-Windows-Services-in-Cplusplus |
|
|
才高八斗的剪刀
2 年前 |
This article shows how to interact, control and configure Windows services from a native application written in C++. The purpose of the article is to present some of the Windows API for interacting with services and build reusable wrapping components in C++ along the way. However, this will not be a comprehensive walk through all the Windows service APIs.
For a complete Windows API reference, see Service Functions .
In order to interact with an existing service, you must follow these general steps:
SC_MANAGER_ENUMERATE_SERVICE
. To open services, query, change statuses, etc., you need
SC_MANAGER_CONNECT
. For additional operations such as creating services or locking the service database, you need to specify other access right codes. However, in this case, the process must run elevated with administrator privileges, otherwise the call will fail.
The attached source code contains types and functions written in C++ that wrap the C Windows API, making it easier to use in a C++ application. These components will enable us to write code like the following.
Example for enumerating all services on the local computer:
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 ;Example for opening a service on the local computer, reading and updating its configuration, reading and changing its status:
// 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();Throughout the rest of the article, I will present the C++ API and explain how the C Windows API is used in these wrappers.
Windows APIs use numerical values as parameters for types, statuses, states, configuration settings and others. These are defined as macros in headers
winsvc.h
and
winnt.h
. In the C++ code however, I prefer using constants or enumerations. Therefore, I have defined
enum
classes for all these values that I need to use with the service APIs. These are available in the
ServiceContants.h
header.
The header also defines a
string
type called
ServiceString
that is either
std::string
or
std::wstring
, depending on the character set used to build the native project. This is the type used for
string
in the C++ service components.
Functions such as
OpenSCManager
and
OpenService
(but also
CreateService
) return a
SC_HANDLE
that is a pointer value. However, the handle must be closed after the code that requires the handle finishes execution. To simplify the correct closing of service handles in all cases, the handle will be wrapped in a class that calls
CloseServiceHandle
when the object is destroyed, in a RAII fashion. Such an implementation is available in the
ServiceHandle.h
header.
The
ServiceController
class represents a Windows service and allows you to query and change the service status, query and change configuration data and others. This is a minimal implementation and can be extended with additional functionality as needed. The implementation is available in the header
ServiceController.h
.
The constructor of the class takes the service name and optionally desired access. If the access is specified, the value is
SERVICE_ALL_ACCESS
that means access rights for all service operations. The following calls are performed in the constructor:
You can check what control codes the service accepts and processes in its handler function by using the
CanAcceptControl()
method. It takes a value defined by the
ServiceControls
enumeration and returns a
bool
indicating whether the control code is accepted and processed or not. Several additional methods are defined using this method.
Function
QueryServiceStatusEx
is also used to check the status of the service (such as stopped, running, starting, etc.). This function returns information in a
SERVICE_STATUS_PROCESS
structure. The
dwCurrentState
member represents the current status as defined by the
ServiceStatus
enumeration.
Services can be started and stopped and some services can be paused and continued. The
ServiceController
class provides methods for all these four operations.
To start the service, we must call
StartService
. This function takes the service handle and optionally arguments to be passed to the
ServiceMain
function of the service. In this implementation, no parameters are passed.
The
Stop()
,
Pause()
and
Continue()
methods are implementing using the
ControlService
function. This function takes the service handle, a control code that represents a notification to be sent to the service and an object of type
SERVICE_STATUS
that receives the latest service status information.
The implementation of the
Stop()
method is a little bit different though than of
Pause()
and
Continue()
because the service might have dependent services and those also need to be stopped when the service is stopped. Stopping the dependent services is implemented in the
StopDependentServices()
private
function. This function does the following:
null
output buffer to get the size of the buffer and then called again with a properly sized buffer. This function returns an array of
ENUM_SERVICE_STATUS
and the number of elements.
Should any of the dependent services fail to properly stop, the rest of the routine will not execute and the main service will not be stopped. Notice that when waiting for the service, we have a timeout set for 30 seconds by default. If the service does not stop within this interval, the routine fails.
bool StopDependentServices() auto ess = ENUM_SERVICE_STATUS{ 0 }; auto bytesNeeded = DWORD{ 0 }; auto count = DWORD{ 0 }; if (!::EnumDependentServices( srvHandle, SERVICE_ACTIVE, nullptr , &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 ;
When you start, stop, pause or continue a service, the change of status might not happen instantly. For instance, starting a service could take several seconds. The
Start()
,
Stop()
,
Pause()
and
Continue()
methods return immediately, indicating only that the change request has been completed successfully, not that the status was indeed changed. (Notice that
Stop()
returns immediately after requesting the stopping of the service, but does wait for all the dependent services to stop first.)
To check the status has changed, you need to periodically query the service status until it changes to the desired state. The
ServiceController
class provides a method that does that called
WaitForStatus()
. The method takes as parameters the desired status and a timeout (set by default to 30 seconds). It first queries the service status and if the service is not in the desired state, then it puts the current thread to sleep for a while and then repeats the operation. The loop ends either when the state has been changed to the desired state or when the timeout elapses. Notice that the sleep interval is 1/10
th
of the service's wait hint, but not smaller than 1 second and not larger than 10 seconds.
To delete a service, we must call the DeleteService function. This function however only marks the service for deletion from the service control manager database. The service is deleted only when all open handles to the service have been closed and the service is not running. If the service is running at the time of the call, the database entry is removed when the system is restarted.
bool Delete() auto success = false ; if (srvHandle) success = ::DeleteService(srvHandle) != 0 ; if (success) srvHandle = nullptr ; return success;
The
ServiceConfig
class represents the configuration parameters of a service. It enables you to read and update some of these configuration parameters and it can be extended if needed to include additional parameters. It is available in the header
ServiceConfig.h
.
The parameters that are automatically read are: the service type, the startup type, the error control for startup failure, the path name, the load ordering group name, the tag ID, the dependent services, the start name, the display name and the description.
The parameters that can be modified in the current implementation are the start type, the error control and the description. For other parameters, the class must be extended with appropriate methods.
An instance of the class can be created using the
static
method
ServiceConfig::Create
that takes a handle to an existing service. After creating an object, this function called its
Refresh()
method to read the configuration parameters.
The
Refresh()
method does the following:
To modify the configuration parameters, several methods are available:
ChangeStartType()
changes the service start type (such as auto, demand, disabled, etc.) and
ChangeStartErrorControl()
changes the service error control on startup failure. These methods both use
ChangeServiceConfig
to change the configuration parameters. Notice that when calling this function you must specify
SERVICE_NO_CHANGE
for service type, start type, and error control if the current values of these parameters are not supposed to be changed.
ChangeDescription()
changes the description of the service. This method calls
ChangeServiceConfig2
for changing the optional configuration parameters, in this case the description.
To retrieve the configuration parameters of a service, you should use the
GetServiceConfig()
method of the
ServiceController
class. It returns an instance of the
ServiceConfig
class.
Notice
: if you explicitly call the
ServiceConfig::Create()
method, you must make sure the
SC_HANDLE
is properly closed when you no longer need to interact with the service.
The
ServiceEnumerator
class is basically a small utility class for enumerating existing services. It contains a single
static
method called
EnumerateServices
that takes several parameters for the service type and state, the machine and service control database name and the load-order group name. With the default values for these parameters, the function enumerates all services, regardless of their type and state, on the local machine.
To enumerate the services, this function:
SC_MANAGER_ENUMERATE_SERVICE
for desired access.
SC_ENUM_PROCESS_INFO
for info level to retrieve the name and service status information for the services. The function is called twice, first with a
null
buffer for the output data to retrieve the actual size required for it and a second time with a properly sized buffer. However, this function has a limit of 256 KB on the output buffer size, which means it may not return all the services in a single call. As a result, the function must be actually called in a loop until no more data is returned. To keep track of where a new call has to start from, the function has a special parameter called
lpResumeHandle
. This is an in-out parameter. On input, it specifies the starting point of the enumeration and must be set to 0 on the first call, on output it is set to 0 if the function succeeded, or the index of the next service entry if the function failed with
ERROR_MORE_DATA
.
In this article, we have built several C++ components for managing Windows services to simplify the use of the Windows service APIs in a C++ application. Along the way, we have also seen what are the Windows service APIs and how they should be used. For a complete reference on the API, see Service Functions in MSDN.
Those handle moves are in the move constructor and move assignment operator. Leaving the original instance with no handle to the service is these members' purpose. They are intended to take ownership.
On further examination, it appears that the move constructor isn't quite right. It should be