用 ATL 编写 Windows 服务
用 ATL 编写 Windows 服务
作者:Alon
有时候,我们需要自己写的程序在没有用户登陆的情况下,只要Windows系统启动就运行,那我们可以把我们的程序写成一个Windows服务。
服务是能够为各种用户(包括本地用户和远程用户)所用的,拥有用户授权级进行管理的能力,并且不论用户是否物理的与正在运行该应用程序的计算机相连都能正常执行。
下面,我将用一个简单的例子说明如何用ATL来编写Windows服务程序。
首先,我们新建一个Project。如图一所示,选择 "ATL COM AppWizard",工程名为:ServiceDemo。

图一
点击 "OK ", 出现图二,选择Service [EXE]。点击 Finish。

图二
完成以上的步骤,一个"什么也不做"的服务就完成了!编译… 打开"控制面板"->"管理工具"
->"服务",嗯?我们写的服务怎么没有在服务管理器(service control manager ,简称(SCM))里面列出来呢?呵呵,被我骗了?不要着急,我们还需要做一些工作。
首先先大概介绍一下向导为我们生成的代码:
程序的进入点是全局函数_tWinMain, 仔细看一下这个函数,我们会发现当我们运行程序时,可以加上参数,例如: ServiceDemo /RegServer
或者 ServiceDemo -RegServer,这个是用来本地服务器注册(Register as Local S Register as
Service erver); ServiceDemo / Service 或者 ServiceDemo
-Service,这个是服务的注册(Register as Service);ServiceDemo /UnRegServer 或者
ServiceDemo -UnRegServer ,这个是服务的删除。所以,当我们写好了服务程序,只要运行的时候加上参数 Service
,这个时候在SCM中就会看到我们的服务了。可以试一下在SCM中对这个什么也不做的服务"启动","停止",改变一下它的启动方式。
每次编码后测试都要在命令行中加参数运行服务才可以在SCM中列出来是不是很麻烦呢?我再介绍一个偷懒的方法,选择VC IDE的菜单Project
-> Setting, 再选择Custom Build面板,如图三:

图三
在"$(TargetPath)" /RegServer的下面加上:"$(TargetPath)"
/Service,这样当我们每次编码后编译程序,就不用再在命令行中去加参数执行我们的服务程序完成服务的注册了。
继续介绍向导生成的代码:向导为我们建立了一个类:CServiceModule,全局变量_Module就是这个类的实例。
Init():这个函数用于完成一些初始化工作;
Run():这个函数就是服务开始运行后的内容,我们接下来要修改的内容也就是从这里入手。
Install():
看一下Install()的这一部分:
SC_HANDLE hService = ::CreateService(hSCM,
m_szServiceName,
m_szServiceName,SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
szFilePath,
NULL,
NULL,
_T("RPCSS\0"),
NULL,
NULL);
函数的原型如下:
SC_HANDLE CreateService(
SC_HANDLE hSCManager, // handle to SCM database
LPCTSTR lpServiceName, // name of service to start
LPCTSTR lpDisplayName, // display name
DWORD dwDesiredAccess, // type of access to service
DWORD dwServiceType, // type of service
DWORD dwStartType, // when to start service
DWORD dwErrorControl, // severity of service failure
LPCTSTR lpBinaryPathName, // name of binary file
LPCTSTR lpLoadOrderGroup, // name of load ordering group
LPDWORD lpdwTagId, // tag identifier
LPCTSTR lpDependencies, // array of dependency names
LPCTSTR lpServiceStartName, // account name
LPCTSTR lpPassword // account password
);
具体的细节可以查一下MSDN,我只说一下第六个和第十一个参数。第六个参数是服务的启动类型。
SERVICE_DEMAND_START是手动启动,SERVICE_AUTO_START是自动启动。第十一个参数是服务的依存关系,比如说服务的启动想要依存SQL
Server的启动,那我们可以把这个参数写成:_T("MSSQLSERVER\0");
如果我们写的服务不依存于其他的任何服务,那我们就将此参数设置为NULL就可以了。
接下来,我们为上面的"什么也不做"的服务添加一个简单的功能:做数字的累加,并且把结果写到系统的"应用程序日志"中去。
首先,我们在类CServiceModule中添加一个成员变量:int n; 在Init()中对n进行初始化:
n = 0;
然后在类CServiceModule中添加一个成员函数Adder():
void CServiceModule::Adder()
{
n ++;
CString str;
str.Format("%i",n);
LogEvent(str);
}
编译…出错了。??,提示 CString 没有定义,难道在ATL中无法用 MFC 吗?让我们看看设置:菜单Project-Setting ,General面板,默认的设置是:Use MFC in a Static Library。那为什么不可以用MFC中的类呢?原来是头文件没有包含,这个不知道算不算 VC 的一个 Bug : ,设置中默认是用MFC,可是却没有包含相应的头文件。那我们就自己加上好了。在StdAfx.h中加上:#include ,注意要加到#include 的前面,要不然又要编译出错了。接下来,我们在程序中再添加一个Timer,让这个Timer每两秒钟调用一次Adder,做一次累加。在:MSG msg;while (GetMessage(&msg, 0, 0, 0))DispatchMessage(&msg);的前面加上代码:SetTimer(NULL,1,2000,(TIMERPROC)OnTimerProc); 注意一定要加在前面,因为要是加到while循环的下面,就没有机会执行了。再添加一个全局的回调函数OnTimerProc 如下:VOID CALLBACK OnTimerProc(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime){_Module.Adder();} 好了,大功告成。编译,然后在SCM中启动我们的服务。在控制面板中打开"事件查看器",看一下运行的结果,如下图四:
