在windows环境使用C++开发一个提示保护眼睛的服务(一)

长时间使用电脑,导致眼睛胀痛,去医院检查,眼压升高,严重可能导致青光眼,进而导致失明。

所以准备开发一个程序来提醒自己保护眼睛。

程序在后台运行,所以准备开发为服务,开机自启动。

我对C++更熟,对C#不熟,所以准备使用C++。

服务检测开机与解锁,检测到之后开始计时,比如40分钟,这个可以自己定,之后提示已用时40分钟,需要休息眼睛。

windows服务开发文档

Services (Services) - Win32 apps | Microsoft Learn

服务

一个服务应用程序符合服务控制管理器(SCM)的接口规则。它可以在系统启动时自动启动,通过服务控制面板小程序由用户启动,或者由使用服务函数的应用程序启动。即使没有用户登录到系统,服务也可以执行。

驱动程序服务符合设备驱动程序协议。它类似于服务应用程序,但不与SCM交互。为简单起见,在这个概述中,术语"服务"指的是服务应用程序。

触发器现在可以用来控制服务启动。有关更多信息,请参阅服务配置。

关于服务

服务控制管理器(SCM)维护一个安装的服务和驱动程序服务的数据库,并提供了一种统一且安全的方法来控制它们。数据库包含每个服务或驱动程序服务应如何启动的信息。它还使系统管理员能够自定义每个服务的安全要求,从而控制对该服务的访问。

下面是使用SCM提供的功能的程序类型:

类型            描述

服务程序:提供一个或多个服务所需的可执行代码的程序。服务程序使用与SCM连接并向SCM发送状态信息的函数。

服务配置程序:查询或修改服务数据库的程序。服务配置程序使用打开数据库的函数,在数据库中安装或删除服务,并查询或修改已安装服务的配置和安全参数。服务配置程序管理服务和驱动程序服务。

服务控制程序:启动和控制服务和驱动程序服务的程序。服务控制程序使用向SCM发送请求的函数,而SCM执行这些请求。

服务控制管理器

.服务控制管理器

服务控制管理器(SCM)在系统启动时启动。它是一个远程过程调用(RPC)服务器,因此服务配置和服务控制程序可以操作远程机器上的服务。

服务函数提供了SCM执行以下任务的接口:

维护安装的服务数据库。 在系统启动或需求时启动服务和驱动程序服务。 枚举安装的服务和驱动程序服务。 维护正在运行的服务和驱动程序服务的状态信息。 向正在运行的服务发送控制请求。 锁定和解锁服务数据库。

.已安装服务的数据库

SCM在注册表中维护着一个已安装服务的数据库。该数据库由SCM和添加、修改或配置服务的程序使用。以下是该数据库的注册表键:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services。

该键包含每个已安装服务和驱动程序服务的子键。子键的名称是服务的名称,由服务配置程序在安装服务时通过CreateService函数指定。

在系统安装时创建了数据库的初始副本。数据库包含系统启动期间所需的设备驱动程序的条目。数据库包括每个已安装服务和驱动程序服务的以下信息:

服务类型。这指示服务是在自己的进程中执行还是与其他服务共享进程。对于驱动程序服务,这指示服务是内核驱动程序还是文件系统驱动程序。 启动类型。这指示服务或驱动程序服务是否在系统启动时自动启动(自动启动服务),或者在服务控制程序请求时由SCM启动它(按需启动服务)。启动类型还可以指示服务或驱动程序服务被禁用,此时无法启动它。 错误控制级别。如果服务或驱动程序服务在系统启动期间无法启动,这指定错误的严重性,并确定启动程序将采取的操作。 可执行文件的完整路径。服务的文件扩展名为.EXE,驱动程序服务的文件扩展名为.SYS。 用于确定启动服务或驱动程序服务的正确顺序的可选依赖信息。对于服务,此信息可以包括SCM必须在启动指定服务之前启动的服务列表、服务所属的加载顺序组的名称,以及指示服务在其加载顺序组中的启动顺序的标识符。对于驱动程序服务,此信息包括必须在指定驱动程序之前启动的驱动程序的列表。 对于服务,还有一个可选的账户名和密码。服务程序在该账户的上下文中运行。如果未指定账户,则服务在LocalSystem账户的上下文中执行。 对于驱动程序服务,还有一个可选的驱动程序对象名称(例如,\FileSystem\Rdr或\Driver\Xns),由I/O系统用于加载设备驱动程序。如果未指定名称,则I/O系统将根据驱动程序服务名称创建一个默认名称。

.自动启动服务

在系统启动过程中,SCM会启动所有自动启动的服务以及它们所依赖的服务。例如,如果一个自动启动的服务依赖于一个按需启动的服务,那么按需启动的服务也会被自动启动。

加载顺序由以下因素决定:

加载顺序组列表中组的顺序。这些信息存储在以下注册表键中的ServiceGroupOrder值中:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control

要为一个服务指定加载顺序组,可以使用CreateService或ChangeServiceConfig函数的lpLoadOrderGroup参数。

标签顺序向量中每个组内服务的顺序。这些信息存储在以下注册表键中的GroupOrderList值中:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control

每个服务所列出的依赖关系。

在引导完成后,系统执行由以下注册表键的BootVerificationProgram值指定的引导验证程序:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control

默认情况下,此值没有设置。在第一个用户登录后,系统只是报告引导成功。您可以提供一个引导验证程序,使用NotifyBootConfigStatus函数检查系统是否存在问题,并向SCM报告引导状态。

成功启动后,系统会将数据库的副本保存在最后已知的良好(LKG)配置中。如果对活动数据库进行的更改导致系统重新启动失败,系统可以恢复此数据库副本。以下是此数据库的注册表键:

HKEY_LOCAL_MACHINE\SYSTEM\ControlSetXXX\Services

其中XXX是保存在以下注册表值中的值:HKEY_LOCAL_MACHINE\System\Select\LastKnownGood。

如果一个具有SERVICE_ERROR_CRITICAL错误控制级别的自动启动服务启动失败,SCM会使用LKG配置重新启动计算机。如果已经使用LKG配置,则引导失败。

可以通过调用ChangeServiceConfig2函数并使用SERVICE_CONFIG_DELAYED_AUTO_START_INFO将自动启动服务配置为延迟自动启动服务。此更改将在下次系统启动后生效。有关更多信息,请参阅SERVICE_DELAYED_AUTO_START_INFO。

.按需启动服务

用户可以使用服务控制面板实用程序启动服务。用户可以在“启动参数”字段中指定服务的参数。服务控制程序可以使用StartService函数启动服务并指定其参数。

当服务启动时,SCM执行以下步骤:

检索存储在数据库中的帐户信息。 登录服务帐户。 加载用户配置文件。 创建挂起状态的服务。 将登录令牌分配给进程。 允许进程执行。

.服务记录列表

在从已安装服务的数据库中读取每个服务条目时,SCM会为该服务创建一个服务记录。服务记录包括以下内容:

  • 服务名称
  • 启动类型(自动启动或按需启动)
  • 服务状态(参见 SERVICE_STATUS 结构)
  • 类型
  • 当前状态
  • 可接受的控制代码
  • 退出码
  • 等待提示
  • 依赖列表指针

在安装服务时,会指定帐户的用户名和密码。SCM将用户名存储在注册表中,将密码存储在本地安全机构(LSA)的安全部分中。系统管理员可以创建永不过期的带密码的帐户。或者,系统管理员可以创建具有过期密码的帐户,并定期更改密码来管理这些帐户。

SCM保留用户帐户密码的两个副本,一个是当前密码,一个是备份密码。第一次安装服务时指定的密码被存储为当前密码,并且备份密码未初始化。当SCM尝试以用户帐户的安全上下文运行服务时,它使用当前密码。如果当前密码成功使用,它也会保存为备份密码。如果使用ChangeServiceConfig函数或服务控制面板实用程序修改密码,则新密码存储为当前密码,先前的密码存储为备份密码。如果SCM尝试启动服务并且当前密码失败,则使用备份密码。如果备份密码成功使用,则保存为当前密码。

当服务使用SetServiceStatus函数向SCM发送状态通知时,SCM会更新服务状态。SCM通过查询I/O系统来维护驱动程序服务的状态,而不是像从服务中接收状态通知那样。

服务可以通过调用SetServiceBits函数注册附加类型信息。NetServerGetInfo和NetServerEnum函数获取支持的服务类型。

.SCM(Service Control Manager)句柄

SCM支持句柄类型,以允许访问以下对象:

  1. 已安装服务的数据库。
  2. 一个服务。
  3. 数据库锁。

SCManager对象代表已安装服务的数据库。它是一个容器对象,保存着服务对象。OpenSCManager函数返回一个句柄,用于表示指定计算机上的SCManager对象。在安装、删除、打开和枚举服务以及锁定服务数据库时,使用该句柄。

服务对象代表一个已安装的服务。CreateService和OpenService函数返回已安装服务的句柄。

OpenSCManager、CreateService和OpenService函数可以请求对SCManager和服务对象的不同类型的访问权限。所请求的访问权限将根据调用进程的访问令牌和与SCManager或服务对象相关联的安全描述符而被授予或拒绝。

CloseServiceHandle函数用于关闭对SCManager和服务对象的句柄。当您不再需要这些句柄时,请确保关闭它们。

服务程序

.服务程序

一个服务程序包含一个或多个服务的可执行代码。使用类型为SERVICE_WIN32_OWN_PROCESS创建的服务程序仅包含一个服务的代码。使用类型为SERVICE_WIN32_SHARE_PROCESS创建的服务程序包含多个服务的代码,使它们可以共享代码。一个实现这一功能的服务程序的示例是通用服务主机进程Svchost.exe,它托管内部的Windows服务。请注意,Svchost.exe被操作系统保留使用,非Windows服务不应使用它。开发人员应该实现自己的服务托管程序。

一个服务程序可以配置为在内置(本地)、主要或受信任的域的用户帐户上下文中执行。它还可以配置为在特殊的服务用户帐户中运行。

以下主题描述了服务程序必须包含的服务控制管理器(SCM)接口要求:

服务入口点 服务ServiceMain函数 服务控制处理程序函数 这些主题不适用于驱动程序服务。有关驱动程序服务的接口要求,请参阅Windows驱动程序开发工具包(WDK)。

服务作为后台进程运行,可能会影响系统的性能、响应性、能效和安全性。有关服务优化指南,请参阅为Windows开发高效的后台进程。以下主题描述了其他编程注意事项:

服务状态转换 在服务中接收事件 多线程服务 服务和注册表 服务和重定向驱动器 服务触发事件 请注意,如果服务程序充当RPC服务器,应使用动态端点和相互认证。

.服务入口点

服务通常以控制台应用程序的形式编写。控制台应用程序的入口点是它的主函数(main function)。主函数从注册表键的ImagePath值中接收服务的参数。有关详细信息,请参阅CreateService函数的备注部分。

当服务控制管理器(SCM)启动一个服务程序时,它会等待该程序调用StartServiceCtrlDispatcher函数。请按照以下准则操作:

对于类型为SERVICE_WIN32_OWN_PROCESS的服务,应立即从主线程中调用StartServiceCtrlDispatcher。在服务启动后,您可以执行任何初始化操作,如ServiceMain函数中所述。 如果服务类型为SERVICE_WIN32_SHARE_PROCESS,并且程序中所有服务都具有共同的初始化操作,您可以在调用StartServiceCtrlDispatcher之前,在主线程中执行初始化操作,前提是该操作耗时不超过30秒。否则,您必须创建另一个线程来执行共同的初始化操作,而主线程调用StartServiceCtrlDispatcher。在服务启动后,仍然应执行任何特定于服务的初始化操作。 StartServiceCtrlDispatcher函数对于进程中包含的每个服务,都需要一个SERVICE_TABLE_ENTRY结构。每个结构指定了服务名称和服务的入口点。有关示例,请参阅编写服务程序的main函数。

如果StartServiceCtrlDispatcher成功,调用线程将一直等待,直到进程中的所有正在运行的服务都进入了SERVICE_STOPPED状态。SCM通过命名管道向此线程发送控制请求。该线程充当控制分派程序,执行以下任务:

当启动新服务时,创建一个新线程来调用适当的入口点。

调用适当的处理程序函数来处理服务控制请求。

.服务ServiceMain函数

服务控制程序请求运行一个新的服务时,服务控制管理器(SCM)会启动该服务并发送启动请求给控制分派程序。控制分派程序创建一个新线程来执行服务的ServiceMain函数。有关示例,请参阅编写ServiceMain函数。

ServiceMain函数应执行以下任务:

  1. 初始化所有全局变量。

  2. 立即调用RegisterServiceCtrlHandler函数注册处理程序函数,用于处理服务的控制请求。RegisterServiceCtrlHandler的返回值是一个服务状态句柄,将在向SCM通知服务状态的调用中使用。

  3. 执行初始化操作。如果初始化代码的执行时间预计非常短(少于一秒),可以直接在ServiceMain中进行初始化。

  4. 如果初始化时间预计超过一秒钟,服务应使用以下初始化技术之一:

    • 调用SetServiceStatus函数报告SERVICE_RUNNING状态,但在初始化完成之前不接受任何控制请求。服务通过在SERVICE_STATUS结构中将dwCurrentState设置为SERVICE_RUNNING,dwControlsAccepted设置为0,调用SetServiceStatus来实现此目的。这确保SCM在服务准备就绪之前不会发送任何控制请求,并使SCM能够管理其他服务。特别是对于自动启动服务,推荐使用此初始化方法以提高性能。
    • 报告SERVICE_START_PENDING状态,不接受任何控制请求,并指定一个等待提示。如果服务的初始化代码执行的任务预计需要比初始等待提示值更长的时间,则代码必须定期调用SetServiceStatus函数(可能使用修订后的等待提示值)来指示正在进行进度。确保仅在初始化正在取得进展时调用SetServiceStatus。否则,SCM会等待服务进入SERVICE_RUNNING状态,假定服务正在取得进展,并阻止其他服务启动。除非您确定执行初始化的线程确实取得进展,否则不要从单独的线程调用SetServiceStatus。
    • 使用此方法的服务还可以指定一个检查点值,并在长时间初始化期间定期递增该值。启动服务的程序可以调用QueryServiceStatus或QueryServiceStatusEx从SCM获取最新的检查点值,并将该值用于向用户报告增量进度。
  5. 初始化完成后,调用SetServiceStatus将服务状态设置为SERVICE_RUNNING,并指定服务准备接受的控制请求。有关控制请求的列表,请参见SERVICE_STATUS结构。

  6. 执行服务任务,或者如果没有待处理任务,则将控制返回给调用者。任何服务状态的更改都需要调用SetServiceStatus来报告新的状态信息。

  7. 如果服务在初始化或运行期间发生错误,服务应调用SetServiceStatus将服务状态设置为SERVICE_STOP_PENDING,如果清理工作需要很长时间。清理完成后,调用SetServiceStatus从最后一个终止的线程将服务状态设置为SERVICE_STOPPED。确保将SERVICE_STATUS结构的dwServiceSpecificExitCode和dwWin32ExitCode成员设置为标识错误的值。

.服务控制句柄函数

每个服务都有一个控制处理程序,即Handler函数,当服务进程接收到来自服务控制程序的控制请求时,它将由控制调度程序调用。因此,该函数在控制调度程序的上下文中执行。例如,要查看编写控制处理程序函数的示例

服务调用RegisterServiceCtrlHandler或RegisterServiceCtrlHandlerEx函数来注册其服务控制处理程序函数。

当服务控制处理程序被调用时,服务必须调用SetServiceStatus函数向SCM报告其状态,仅当处理控制码导致服务状态发生更改时。如果处理控制码不会导致服务状态发生更改,则无需调用SetServiceStatus。

服务控制程序可以使用ControlService函数发送控制请求。所有服务必须接受和处理SERVICE_CONTROL_INTERROGATE控制码。您可以通过调用SetServiceStatus启用或禁用其他控制代码的接受。要接收SERVICE_CONTROL_DEVICEEVENT控制代码,您必须调用RegisterDeviceNotification函数。服务还可以处理其他用户定义的控制代码。

如果服务接受了SERVICE_CONTROL_STOP控制代码,则必须在接收时停止,转到SERVICE_STOP_PENDING或SERVICE_STOPPED状态。在SCM发送此控制代码之后,它将不发送其他控制代码。

Windows XP:如果服务返回NO_ERROR并继续运行,则它将继续接收控制代码。自Windows Server 2003和Windows XP Service Pack 2(SP2)开始,此行为已更改。

控制处理程序必须在30秒内返回,否则SCM将返回错误。如果服务在执行控制处理程序时必须进行长时间的处理,则应创建一个辅助线程来执行长时间的处理,然后从控制处理程序返回。这可以防止服务绑定控制调度程序。例如,在处理需要很长时间的服务停止请求时,创建另一个线程来处理停止进程。控制处理程序只需使用SERVICE_STOP_PENDING消息调用SetServiceStatus并返回即可。

当用户关闭系统时,所有调用SetServiceStatus并使用SERVICE_ACCEPT_PRESHUTDOWN控制代码的控制处理程序都会收到SERVICE_CONTROL_PRESHUTDOWN控制代码。服务控制管理器等待服务停止或指定的预关闭超时值到期(可以使用ChangeServiceConfig2函数设置此值)。仅在特殊情况下应使用此控制代码,因为处理此通知的服务将阻止系统关闭,直到服务停止或预关闭超时间隔到期。

完成预关闭通知后,所有调用SetServiceStatus并使用SERVICE_ACCEPT_SHUTDOWN控制代码的控制处理程序都会收到SERVICE_CONTROL_SHUTDOWN控制代码。它们按安装的服务数据库中出现的顺序接收通知。默认情况下,服务具有大约20秒的时间来执行清理任务,然后系统关闭。此时间到期后,无论服务关闭是否完成,系统关闭都会继续。请注意,如果系统处于关闭状态(未重新启动或关机),则服务将继续运行。

如果服务需要更多时间来进行清理,则发送STOP_PENDING状态消息以及等待提示,以便服务控制器知道在报告服务关闭完成之前等待的时间长度。但是,为了防止服务停止关闭,服务控制器等待的时间有限。如果通过“服务”选项卡关闭服务,则限制为125秒或125,000毫秒。如果操作系统正在重新启动,则时间限制在以下注册表键的WaitToKillServiceTimeout值(以毫秒为单位)中指定:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control

重要提示:

服务不应尝试通过修改此值来增加时间限制。如果确实需要手动设置WaitToKillServiceTimeout,则该值应以毫秒为单位。

客户需要快速关闭操作系统。例如,如果在UPS电源上运行的计算机在UPS耗尽电力之前无法完成关闭,则可能会丢失数据。因此,服务应尽快完成其清理任务。最好的做法是通过定期保存数据、跟踪保存到磁盘的数据并仅在关闭时保存未保存的数据来最小化未保存的数据。因为正在关闭计算机,所以不要花时间释放已分配的内存或其他系统资源。如果需要通知服务器您正在退出,请最小化等待回复的时间,因为网络问题可能会延迟您的服务的关闭。

请注意,在服务关闭期间,默认情况下,SCM不考虑依赖关系。SCM枚举运行中的服务列表并发送SERVICE_CONTROL_SHUTDOWN命令。因此,由于其依赖的另一个服务已经停止,服务可能会失败。

要手动设置服务的关闭顺序,请创建一个包含按应关闭的顺序列出的服务名称的多字符串注册表值,并将其分配给Control键的PreshutdownOrder值,如下所示:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\PreshutdownOrder="Shutdown Order"

要从应用程序中设置依赖服务的关闭顺序,请使用SetProcessShutdownParameters函数。SCM使用此函数为其处理程序分配0x1E0优先级。当其控制处理程序被调用时,SCM发送SERVICE_CONTROL_SHUTDOWN通知并等待服务退出,然后才从其控制处理程序返回。

.服务状态转换

服务负责将其状态的更改报告给服务控制管理器(SCM)。服务控制程序和系统只能从SCM获取服务的状态,因此服务正确报告其状态非常重要。服务通过调用SetServiceStatus函数并提供完整初始化的SERVICE_STATUS结构的指针来报告其状态。结构中的dwCurrentState成员包含要报告的服务状态。

服务的初始状态是SERVICE_STOPPED。当SCM启动服务时,它将服务状态设置为SERVICE_START_PENDING,并调用服务的ServiceMain函数。然后,服务使用Service

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值