服务是能够为各种用户(包括本地用户和远程用户)所用的,拥有用户授权级进行管理的能力,并且不论用户是否物理的与正在运行该应用程序的计算机相连都能正常执行。
下面,先用VC6做一个简单的例子说明如何用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, //服务控制管理程序维护的登记数据库的句柄,由系统函数OpenSCManager 返回 LPCTSTR lpServiceName, //以NULL结尾的服务名,用于创建登记数据库中的关键字 LPCTSTR lpDisplayName, //以NULL 结尾的服务名,用于用户界面标识服务 DWORD dwDesiredAccess, //指定服务返回类型 DWORD dwServiceType, //指定服务类型 DWORD dwStartType, //指定何时启动服务 DWORD dwErrorControl, //指定服务启动失败的严重程度 LPCTSTR lpBinaryPathName, //指定服务程序二进制文件的路径 LPCTSTR lpLoadOrderGroup, //指定顺序装入的服务组名 LPDWORD lpdwTagId, //忽略,NULL LPCTSTR lpDependencies, //指定启动该服务前必须先启动的服务或服务组 LPCTSTR lpServiceStartName, //以NULL 结尾的字符串,指定服务帐号。如是NULL,则表示使用LocalSystem 帐号 LPCTSTR lpPassword //以NULL 结尾的字符串,指定对应的口令。为NULL表示无口令。但使用LocalSystem时填NULL );
第六个参数dwStartType取值类型如下:
第十一个参数是服务的依存关系,比如说服务的启动想要依存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中启动我们的服务。在控制面板中打开"事件查看器",看一下运行的结果,如下图四:
图四
在VC++6与VC++.Net 2003中使用ATL开发Windows服务时,是有一些区别的。