第10天 在Windows系统中以后台服务方式运行程序
今天要学习的案例对应的源代码目录:src/chapter02/ks02_09。本案例不依赖第三方类库。程序运行效果如图2-54所示。
图2-54 第10天案例程序运行效果
今天的目标是掌握如下内容:Windows系统中如何让程序以服务方式运行。
第9天的内容中介绍了如何让进程在Linux中以守护进程方式运行。那么,在Windows中怎样实现类似的功能呢?在Windows中让进程以服务方式运行比在Linux中要稍微复杂一些,因为需要将进程注册到Windows的服务管理器中。打开Windows服务管理器方法如下。
(1)如图2-55所示,在资源管理器中右击【此电脑】,在弹出的菜单中选择【管理】,会弹出【计算机管理】界面。
(2)如图2-56所示,在【计算机管理】界面中选择【服务和应用程序】中的【服务】,就会出现图2-54所示的界面。
图2-55 打开Widnows服务管理器第一步
图2-56 打开Widnows服务管理器第二步
在图2-54所示的服务列表中,右击某个服务就会弹出如图2-57所示的菜单。可以选择【启动】或【停止】等菜单项来控制服务的运行状态。选择【属性】菜单项时弹出如图2-58所示界面,可以修改【启动类型】,将服务改为手动启动或自动启动。
图2-57 在服务上右击时弹出的菜单
图2-58 更改服务的启动类型
有些Windows版本对安全要求比较高,可能要配置登录信息,否则服务无法正常启动。可以单击图2-58所示的【登录】页,然后为服务配置登录信息,也就是登录Windows操作系统的账户信息。在【登录】页面配置登录账户,需要输入账户名称、密码,如图2-59所示。
图2-59 为服务配置登录信息
下面介绍具体开发方法。
1.注册/注销服务
在Windows中让一个进程以服务方式运行,需要先将该进程注册到Windows服务列表。这需要提供3个信息:“注册用的服务名”“显示用的服务名”“服务描述信息”。其中“显示用的服务名”就是图2-54中的ks02_09,而“服务描述信息”就是图2-54中的【C++老鸟日记】。如代码清单2-18所示,regist()接口用来注册服务,该接口提供3个参数,分别对应“注册用的服务名”“显示用的服务名”“服务描述信息”。unregist()接口用来注销服务,该接口只需要提供“注销用的服务名”。
代码清单2-18
regist()、unregist()接口的实现见代码清单2-19。在regist()中,在标号①处,引入Advapi32.lib库,否则将导致链接错误,另一种解决方法是在pro中配置LIBS+=-lAdvapi32。在标号②处,定义两个全局变量SERVICE_NAME、SERVICE_SHOWNAME分别用来表示注册(注销)用的服务名、显示用的服务名。在标号③处打开服务控制管理器并得到操作句柄hSCM,在标号④处利用该句柄创建服务。同样的,在unregist()中,也是通过服务控制管理器实现服务的注销操作,见标号⑤处。
代码清单2-19
注意:如果程序编译时出现编译错误,无法将参数2从“LPSTR”转换为“LPCWSTR”,需要在项目的pro中添加DEFINES -= UNICODE。
2.启动服务
完成服务的注册后,可以启动服务,启动服务的接口定义为start_service(const char*service_name, const char* service_showname)。
start_service()的实现如下。该接口通过调用“::StartServiceCtrlDispatcher()”实现了服务的启动。
为了兼容Windows、Linux系统,对api_start_as_service()接口做改动,增加“注册用的服务名”“显示用的服务名”。
api_start_as_service()在Windows上的实现如下,它通过调用start_service()来启动服务。
该接口在Linux的实现未做改动,只是为了同Windows保持一致增加了2个接口参数,这2个接口参数并未在函数体内使用。
3.在进程中增加对注册、注销、启动服务接口的调用代码
完成服务的注册、注销、启动接口后,就可以在应用进程中调用这些接口了。首先,为程序增加启动参数regist、unregist分别用来处理注册、注销事务,见代码清单2-20中标号①、标号②处。在标号③处完成程序的初始化工作。初始化工作结束后,在Windows系统中,如果启动参数中不带-term,就以服务方式启动,见标号④处,这里针对非Windows系统增加了对工作线程运行状态的模拟监视。
代码清单2-20
注意:必须等待程序的初始化工作结束、所有工作线程都已启动的情况下,才能展示菜单或者让程序以服务方式运行。也就是说,CommandProc()或者api_start_as_service()必须在程序初始化工作结束后才能调用。
4.Windows中引入User32库
在Windows系统中,可能出现如下的链接错误。
解决的方法是,在pro中Windows分支的配置中引入User32库。
如果没有引入Advapi32库,将导致如下链接错误。
5.工程化
至此,开发工作完成了。但是,在Windows系统中,进程需要先注册到服务控制管理器之后才能以服务方式运行。这需要先将软件部署到客户运行环境,然后手工执行命令来实现。
ks02_09_d -regist
注意:有些Windows版本对于安全性要求比较高,需要以管理员身份启动终端,然后执行注册命令,否则会导致服务启动失败。如果执行注销操作,也要先以管理员身份启动终端。以管理员身份启动终端的方法是,右击终端,然后选择【更多】|【以管理员身份运行】,如图2-60所示。
图2-60 以管理员身份启动终端