全部博文(584)
分类: WINDOWS
2010-09-22 18:06:46
_tWinMain函数中,_Module初始化并设置m_bService为TRUE,在一些分析命令行和判断是否为服务的代码之后,使用_Module.Start()进入主要的执行部分。CServiceModule::Start()中,结构体SERVICE_TABLE_ENTRY建立了服务名与相应处理函数的映射。在这里,如果m_bService为TRUE,则调用StartServiceCtrlDispatcher进入一种类似win32程序的消息处理的过程,用SERVICE_TABLE_ENTRY中的处理函数让程序执行下去。如果m_bService不为TRUE,则直接执行Run()函数。
在SERVICE_TABLE_ENTRY中,我们看到服务处理函数为_ServiceMain,继续跟踪下去,发现是ServiceMain函数。在ServiceMain中又调用RegisterServiceCtrlHandler为服务增加了一个_Handler函数。对服务程序来说,我们可以在前面打开的服务列表中对它们进行“启动”,“停止”,“暂停”,“恢复”等操作。这实际上是由_Handler来处理不同的信号。_Handler内部调用Handler,在Handler中,对传入的dwOpcode参数作出处理。比如如果是SERVICE_CONTROL_STOP,也就是我们“停止”服务时,将使用PostThreadMessage对主线程发出一个退出的信号。回到ServiceMain函数,在里面同样是在调用Run()函数。也就是说程序以服务身份和非服务身份运行时,区别在于以服务身份运行时多了一个Handler函数,处理用户对服务程序发出的一些信号。
需要注意的是,这个程序注册为服务时并不是直接写注册表,而是在Install中使用了OpenSCManager,CreateService等函数来完成的任务。显然,这比直接写注册表要好一些,因为有时候我们并不太清楚要怎么去修改注册表项的值来适应不同的服务程序配置,而这些函数有参数可以做到。
说到这里,就涉及到我们自己编写的代码了。
比如现在我们已经建立了一个MFC的程序,想让它成为一个服务程序,那要怎么做呢?
我现在建立一个MFC EXE的项目mfc1,基于对话框。那么把它变为一个服务程序的最简单的方法就是把CServiceModule给拿过来使用。因为我们已经看到CServiceModule类已经把安装服务,卸载服务,运行服务这些操作封装得很好。
打开test1的stdafx.h文件,复制CServiceModule的声明及相关头文件和变量到mfc1的stdafx.h中。
然后是把test1的test1.cpp中对CServiceModule类的实现,复制到mfc1中的mfc1.cpp中。
在stdafx.h中CServiceModule类声明前加上#include
编译后出现以下类似错误:
D:vc6_testmfc1mfc1.cpp(52) : error C2065: ''IDR_Test1'' : undeclared identifier
D:vc6_testmfc1mfc1.cpp(336) : error C2065: ''CoInitializeSecurity'' : undeclared identifier
D:vc6_testmfc1mfc1.cpp(337) : error C2065: ''EOAC_NONE'' : undeclared identifier
D:vc6_testmfc1mfc1.cpp(362) : error C2065: ''IDS_SERVICENAME'' : undeclared identifier
D:vc6_testmfc1mfc1.cpp(362) : error C2065: ''LIBID_TEST1Lib'' : undeclared identifier
我们可以在test1中找到IDR_Test1的声明,放到mfc1中,解决第一条错误。但我们也可以去掉CServiceModule中与COM有关的一些代码。这里我们删除RegisterServer,UnregisterServer两个函数,并让Run函数成为
void CServiceModule::Run()
{
_Module.dwThreadID = GetCurrentThreadId();
LogEvent(_T("Service started"));
if (m_bService)
SetServiceStatus(SERVICE_RUNNING);
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
DispatchMessage(&msg);
} 增加资源IDS_SERVICENAME为“mfc1”。
注释掉CServiceModule::Init中“CComModule::Init(p, h, plibid);”一行。
注释_tWinMain函数(技巧:用#if 0和#endif注释)。
现在编译程序,应该没有错误了,但加入的CServiceModule还没有起到作用。
在mfc1中的IDD_MFC1_DIALOG上加入两个按钮,分别是“安装服务”,“卸载服务”。增加的单击事件代码为:
“安装服务”按钮:void CMfc1Dlg::OnButton1() { _Module.Install(); }
“卸载服务”按钮:void CMfc1Dlg::OnButton2() { _Module.Uninstall(); }
下面在CMfc1App::InitInstance()中加入一些代码:
_Module.Init(ObjectMap, this->m_hInstance, IDS_SERVICENAME, NULL);
_Module.m_bService = TRUE;
_Module.Start();
地点是在原来产生对话框的代码的地方。而原有的生成对话框的代码转移到Run()中,位置是在使用了SetServiceStatus函数设置服务状态之后,并注释掉其后的消息处理代码,因对话框自身有消息处理机制。
编译时若出现如下错误,将Install()和Uninstall()前的inline参数去掉即可:
mfc1Dlg.obj : error LNK2001: unresolved external symbol "public: int __thiscall CServiceModule::Install(void)" (?Install@CServiceModule@@QAEHXZ)
mfc1Dlg.obj : error LNK2001: unresolved external symbol "public: int __thiscall CServiceModule::Uninstall(void)" (?Uninstall@CServiceModule@@QAEHXZ)
现在可以编译运行了。然后点击“安装服务”,就可以在服务列表中看到mfc1了。
四、这一服务程序运行时没有图形界面?
不错,刚才直接运行mfc1.exe时我们看到了图形界面,但在服务列表中用右键菜单中的“启动”时却看不到任何界面。这该怎么办?
我们还需要在使用CreateService函数时(Install()中),加上一个参数,这样才能允许程序与桌面交互,也就是可以显示界面。这个参数是SERVICE_INTERACTIVE_PROCESS。
填加后的CreateService:
SC_HANDLE hService = ::CreateService(
hSCM, m_szServiceName, m_szServiceName,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
szFilePath, NULL, NULL, _T("RPCSS"), NULL, NULL);
再次编译mfc1,卸载服务后,安装服务。我们可以看到,通过服务列表启动mfc1,原有的对话框出现了。
如需将服务设为自动启动,则将 SERVICE_DEMAND_START 改为 SERVICE_AUTO_START。