如何用ATL编写Windows服务

有时候,我们需要自己写的程序在没有用户登陆的情况下,只要Windows系统启动就运行,那我们可以把我们的程序写成一个Windows服务。
  服务是能够为各种用户(包括本地用户和远程用户)所用的,拥有用户授权级进行管理的能力,并且不论用户是否物理的与正在运行该应用程序的计算机相连都能正常执行。


下面,先用VC6做一个简单的例子说明如何用ATL来编写Windows服务程序
首先,我们新建一个Project。如图一所示,选择 "ATL COM AppWizard",工程名为:ServiceDemo。

如何用ATL编写Windows服务

图一

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

如何用ATL编写Windows服务

图二

  完成以上的步骤,一个"什么也不做"的服务就完成了!编译… 打开"控制面板"->"管理工具"
->"服务",嗯?我们写的服务怎么没有在服务管理器(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 面板,如图三:

如何用ATL编写Windows服务

图三

在"$(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取值类型如下:
    共有五种启动类型。前三种类型是:SERVICE_AUTO_START、SERVICE_DISABLED 和 SERVICE_DEMAND_START。对应的标准启动类型:自动、禁用和手动,通常使用“计算机管理”管理工具中的“服务”进行配置。后两种类型是:SERVICE_BOOT_START 和 SERVICE_SYSTEM_START,通常用于配置加载设备驱动程序的方式。例如,在启动计算机时或启动 Windows 2000 时加载。

 

第十一个参数是服务的依存关系,比如说服务的启动想要依存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中启动我们的服务。在控制面板中打开"事件查看器",看一下运行的结果,如下图四:

如何用ATL编写Windows服务

图四

 

在VC++6与VC++.Net 2003中使用ATL开发Windows服务时,是有一些区别的。  
   其原因是VC++6与VC++.Net 2003中ATL的版本不同。
   VC++.Net 2003中,ATL的版本为7.0,用于处理Windows服务的部分已被封装。
   在VC++.Net 2003中使用ATL开发Windows服务的基本步骤如下:  
   
   1、创建ATL工程。在ATL工程向导中,选择“Application    Setting”,  
   服务器类型(Server    type)选择“Service(EXE)”,其中,“Attributed”我通常是不选的。
   工程创建完毕后,ATL工程向导将自动生成两个工程,“xxxx”和“xxxxPS”,    如:Test,    TestPS。
   通常我是删掉“xxxxPS”这个工程。     
   2、在ATL工程向导自动创建的类CxxxxModule中,重载CAtlServiceModuleT的两个方法:  
           PreMessageLoop、PostMessageLoop  
   其中,PreMessageLoop用于Windows服务启动前的初始化工作,并创建一个工作线程,以完成具体的处理。
   (启动服务将调用PreMessageLoop方法)。注意:重载PreMessageLoop方法时,必须返回S_OK。
    
   PostMessageLoop用于Windows服务停止后释放某些相关资源,并终止工作线程的运行。
         (停止服务时将调用PostMessageLoop方法)。  
   
   3、如果要对命令行参数进行处理,则重载ParseCommandLine方法。  
   
   下面是一个Windows服务的代码片段:  
   
   class    CTestModule      public    CAtlServiceModuleT<    CTestModule,    IDS_SERVICENAME    
   
   public    
   DECLARE_LIBID(LIBID_TestLib)  
   DECLARE_REGISTRY_APPID_RESOURCEID(IDR_Test,    "{CE42CD12-B8BD-44F3-9BC6-56CA0790E71B}")  
   HRESULT    InitializeSecurity()    throw()  
   
   //    TODO      Call    CoInitializeSecurity    and    provide    the    appropriate    security    settings    for  
   //    your    service  
   //    Suggested      PKT    Level    Authentication,  
   //    Impersonation    Level    of    RPC_C_IMP_LEVEL_IDENTIFY  
   //    and    an    appropiate    Non    NULL    Security    Descriptor.  
   
   return    S_OK;  
   
   
           //  
           //    在ATL    7.0中,用于处理Windows服务的部分已被封装。  
           //  
           //              通过重载PreMessageLoop与PostMessageLoop方法来初始化或释放  
           //      Windows服务的相关资源。  
           //  
           HRESULT    PreMessageLoop(int    nShowCmd)    throw()  
           
                   HRESULT    hr      S_OK;  
   
                   //  
                   //    启动服务的工作线程  
                   //  
                   hr=CAtlServiceModuleT<CTestModule,IDS_SERVICENAME>::PreMessageLoop(nShowCmd);   
                   memset(m_szServer,0,_MAX_PATH);  
                   m_dwPort=0;  
                   if(!m_Listener.Create(m_szServer,m_dwPort))  
                   
                           PrintMessage(_T("绑定网络地址%s:%d失败,请检查网络配置,服务终止。"),  
                                                           m_szServer,    m_dwPort    );  
                           return    E_FAIL;  
                   
   
                   //  
                   //    启动Socket监听线程  
                   //  
                   if(!m_Listener.Run())  
                   
                           PrintMessage(    _T("异常!服务线程启动失败,服务终止。")    );  
                           return    E_FAIL;  
                   
   
                   return    S_OK;  
           
   
           HRESULT    PostMessageLoop()    throw()  
           
                   HRESULT    hr      S_OK;  
                   hr=CAtlServiceModuleT<CTestModule,IDS_SERVICENAME>::PostMessageLoop();  
   
                   //  
                   //    终止Socket监听线程  
                   //  
                   m_Listener.Shutdown();  
                   return    hr;  
           
   
           bool    ParseCommandLine(LPCTSTR    lpCmdLine,HRESULT*    pnRetCode)    throw()  
           
                   bool          bResult;  
                   bResult      CAtlServiceModuleT<CTestModule,IDS_SERVICENAME>::ParseCommandLine(lpCmdLine,    pnRetCode);  

                   if(!bResult)  
                   
                           TCHAR    szTokens[] =    _T("-/");  
   
                           LPCTSTR    lpszToken= FindOneOf(lpCmdLine,szTokens);  
                           while    (lpszToken    !=    NULL)  
                           
                                   if    (WordCmpI(lpszToken,    _T("Service"))==0)  
                                   
                                           SetupRegistry();  
                                           break;  
                                   
                                   lpszToken      FindOneOf(lpszToken,    szTokens);  
                           
                   
   
                   return    bResult;  
           
   private:  
   
           CListener    m_Listener;  
           char         m_szServer[_MAX_PATH];  
           DWORD        m_dwPort;  
   };  

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值