VS2005 ATL WINDOWS服务感想

来源:互联网 发布:骨龄鉴定软件 编辑:程序博客网 时间:2024/05/22 13:28

 

最近写的WINDOWS 服务,以前用VC6写过简单的服务。VC6 带的ATL 创建服务后,会生成一个继承于CcomModule 的类,并覆盖了START、STOP等(不记得了),只需在START、STOP里写相应的代码就行了。
 
现在已用VS2005,当然不用VC6了,VC8中的服务项目是基于.NET的,考虑.NET的性能对服务不合适,还是选择ATL8。但创建完项目就发现生成的类除了安全检测外什么也没有。
 
生成的类继承于CatlServiceModuleT的,在网上搜了一下CatlServiceModuleT,资料很少,例子较全的是一个英文网站上的,拿来一用出现一些问题,经过几天的摸索(先把作业叫了,再摸索的),大体弄清CatlServiceModuleT。
 
CatlServiceModuleT 用T* pT = static_cast<T*>(this) 来实现对子类函数的调用(如果子类重写了该函数)。我们可重写的函数有ParseCommandLine、Start、Run、PreMessageLoop、RunMessageLoop、PostMessageLoop、InitializeSecurity、OnStop、OnPause、OnContinue、OnShutdown、ServiceMain、Handler、RegisterAppId等。
 
网上一般是重写PreMessageLoop来做服务的开始工作,PostMessageLoop做结束工作。RegisterAppId 用来更改服务注册信息。
 
主线程有一个消息循环,它不做什么,只等收到消息推出程序。PreMessageLoop里面可以启动自己的线程,并进入服务处理循环。主线程和工作线程可用事件来交互,(如果多线程可能要用到互斥量、信号量),比如主线程OnStop的时候结束工作线程。
 
RegisterAppId 里面可以更改服务注册信息,主要用函数ChangeServiceConfig2来更改服务的描述,也就是在服务管理里面每个服务的描述,不改就为空。
 
结束工作可用PostMessageLoop,但我是直接在OnStop里面写的,也许主类中有要释放的资源才用PostMessageLoop。我的资源全在工作类中。
 
现在是一些问题:
 
1,调试:怎样才能在VS2005中调试。
看CatlServiceModuleT中的WinMain,检查输入参数有效就调用START。START先检测服务有没有注册过,没有注册的就直接返回退出。所以一定要先注册。检测到有注册就查看是否注册为LocalService(也就是 -Service),如果为LocalService就把_ServiceMain连接到SCM(服务管理器),但在调试模式下不能连接成功,或者必须由SCM来启动,要不它认为已启动。所以不能注册为服务,只能注册为RegSever(-RegSever)。调试完后,再注册为服务。如果只有在服务里面才出错,可用CatlServiceModuleT带的LogEvent 把调试信息写到windows事件里面。MSDN中讲可以服务进程连到VS2005我没试过,因为全在RegSever模式下解决了。
 
2,更改服务的启动模式。
没找到更改服务启动模式的方法。查看代码发现在CatlServiceModuleT的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);
 
SERVICE_DEMAND_START表示手动在SCM启动。有个方法,在重写的RegisterAppId用ChangeServiceConfig修改,但看一下ChangeServiceConfig的参数,好多呀,太麻烦。加上我觉得写一个服务就应该自动启动,什么时候处理任务是服务的事,而且不想用了可以在SCM里禁用。所以我做了一个不道德的地方――把CatlServiceModuleT源程序改了。把SERVICE_DEMAND_START改为SERVICE_AUTO_START。
         SC_HANDLE hService = ::CreateService(
              hSCM, m_szServiceName, m_szServiceName,
              SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
              SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
         szFilePath, NULL, NULL, _T("RPCSS/0"), NULL, NULL);
谁叫他不道德没流一个好的方法。当然你可以用ChangeServiceConfig,自己查一下。
 
       3,暂停和继续,
我想用暂停和继续来实现配置的更新,因为不想改配置的时候,要停止服务,再启动,那样服务会断。还有就是用命名管道或全局消息让配置工具和服务通讯,但太麻烦,我只是想重新应有配置。
       第一个问题,在SMC中暂停和继续的菜单是灰的。找了很久,找到::SetServiceStatus(m_hServiceStatus, &m_status) ,还好m_status是Public ,用m_status. dwControlsAccepted=m_status.dwControlsAccepted| SERVICE_ACCEPT_PAUSE_CONTINUE;搞定。
       另一个问题,重写了OnPause但更本没进去。用LogEvent调试,发现CatlServiceModuleT的_Handler都没进去(LocalService就把_ServiceMain连接到SCM,_ServiceMain调用ServiceMain,ServiceMain中注册Handler为SCM处理函数,处理STOP、PAUSE等),_Handler 中是这样调用的((T*)_pAtlModule)->Handler(dwOpcode); 不知到_pAtlModule是指向谁的实例。我重写了Handler,就可以进来了。在自己的Handler中调用PAUSE,OnContinue后,就调用父类的Handler。
 
 
终于完成了,代码如下,工作线程没写出来。
 
class CNVSStoreServerModule : public CAtlServiceModuleT< CNVSStoreServerModule, IDS_SERVICENAME >
{
private:
     CWorkThread * pWork; //工作类,没写出来。
public :
     DECLARE_LIBID(LIBID_NVSStoreServerLib)
     DECLARE_REGISTRY_APPID_RESOURCEID(IDR_NVSSTORESERVER, "{CF40AF29-C742-4D52-906C-5915A611F2D6}")
     HRESULT InitializeSecurity() throw()
     {
         return S_OK;
     }
     void OnPauze() throw()
     {
         SetEvent(pWork->mServerStopEvent);
         SetServiceStatus(SERVICE_PAUSED);
         __super::OnPause();
     }
     void OnStop() throw()
     {
         SetEvent(pWork->mServerStopEvent);
         WaitForSingleObject(pWork->mCanStopEvent,INFINITE);
         __super::OnStop();
     }
     void Handler(DWORD dwOpcode) throw()
     {
         switch (dwOpcode)
         {
         case SERVICE_CONTROL_PAUSE:
              OnPauze();
              break;
         case SERVICE_CONTROL_CONTINUE:
              OnContinue();
              break;
         }
         __super::Handler(dwOpcode);
     }
     CServiceStatus GetServiceStatus() throw()
     {
         return this->m_ServiceStatus;
     }
     void OnContinue( ) throw( )
     {
         SetServiceStatus(SERVICE_RUNNING);
         __super::OnContinue();
     }
     HRESULT PreMessageLoop(int nShowCmd) throw()
     {
         m_status.dwControlsAccepted =m_status.dwControlsAccepted | SERVICE_ACCEPT_PAUSE_CONTINUE;
         HRESULT hr = __super::PreMessageLoop(nShowCmd);
         if (hr == S_FALSE) hr = S_OK; //要这句才能走下去
         pWork=new CWorkThread();
         return hr;
     }
     HRESULT RegisterAppId(bool bService = false) throw()
     {          
         HRESULT hr = S_OK;
         BOOL res = __super::RegisterAppId(bService);
         if (bService)
         {
              if (IsInstalled())
              {       
                   SC_HANDLE hSCM = ::OpenSCManagerW(NULL, NULL, SERVICE_CHANGE_CONFIG);
                   SC_HANDLE hService = NULL;
                   if (hSCM == NULL)
                       hr = AtlHresultFromLastError();
                   else
                   {
                       hService = ::OpenServiceW(hSCM, m_szServiceName, SERVICE_CHANGE_CONFIG);
                       if (hService != NULL)
                       {
                            const int m_szServiceNameLen = 4096;
                            const int m_szServiceDescriptionLen = 2000;
                            WCHAR m_szServiceDescription[m_szServiceDescriptionLen]=L"你的服务描述";
                            SERVICE_DESCRIPTION sdBuf = {m_szServiceDescription};
                            res = ChangeServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, &sdBuf);
                            ::CloseServiceHandle(hService);
                       }
                       else
                            hr = AtlHresultFromLastError();
                       ::CloseServiceHandle(hSCM);
                   }
 
              }
         }
         return hr;
     }
};
 
CNVSStoreServerModule _AtlModule;
 
extern "C" int WINAPI _tWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/,
                                     LPTSTR /*lpCmdLine*/, int nShowCmd)
{
     return _AtlModule.WinMain(nShowCmd);
}