最近写的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); }
