Windows处事编写道理及探讨(2)
副标题#e#
(二)对处事的深入接头之上
上一章其实只是归纳综合性的先容,下面开始才是真正的细节地址。在进入点函数内里要完成ServiceMain的初始化,精确点说是初始化一个SERVICE_TABLE_ENTRY布局数组,这个布局记录了这个处事措施内里所包括的所有处事的名称和处事的进入点函数,下面是一个SERVICE_TABLE_ENTRY的例子:
SERVICE_TABLE_ENTRY service_table_entry[] =
{
{ "MyFTPd" , FtpdMain },
{ "MyHttpd", Httpserv},
{ NULL, NULL },
};
第一个成员代表处事的名字,第二个成员是ServiceMain回调函数的地点,上面的处事措施因为拥有两个处事,所以有三个SERVICE_TABLE_ENTRY元素,前两个用于处事,最后的NULL指明数组的竣事。
接下来这个数组的地点被通报到StartServiceCtrlDispatcher函数:
BOOL StartServiceCtrlDispatcher(
LPSERVICE_TABLE_ENTRY lpServiceStartTable
)
这个Win32函数表白可执行文件的历程奈何通知SCM包括在这个历程中的处事。就像上一章中讲的那样,StartServiceCtrlDispatcher为每一个通报到它的数组中的非空元素发生一个新的线程,每一个历程开始执行由数组元素中的lpServiceStartTable指明的ServiceMain函数。
SCM启动一个处事措施之后,它会期待该措施的主线程去调StartServiceCtrlDispatcher。假如谁人函数在两分钟内没有被挪用,SCM将会认为这个处事有问题,并挪用TerminateProcess去杀死这个历程。这就要求你的主线程要尽大概快的挪用StartServiceCtrlDispatcher。
StartServiceCtrlDispatcher函数则并不当即返回,相反它会驻留在一个轮回内。当在该轮回内时,StartServiceCtrlDispatcher悬挂起本身,期待下面两个事件中的一个产生。第一,假如SCM要去送一个节制通知给运行在这个历程内一个处事的时候,这个线程就会激活。当节制通知达到后,线程激活并挪用相应处事的CtrlHandler函数。CtrlHandler函数处理惩罚这个处事节制通知,并返回到StartServiceCtrlDispatcher。StartServiceCtrlDispatcher轮回归去后再一次悬挂本身。
#p#副标题#e#
第二,假如处事线程中的一个处事中止,这个线程也将激活。在这种环境下,该历程将运行在它内里的处事数减一。假如处事数为零,StartServiceCtrlDispatcher就会返回到进口点函数,以便可以或许执行任何与历程有关的排除事情并竣事历程。假如尚有处事在运行,哪怕只是一个处事,StartServiceCtrlDispatcher也会继承轮回下去,继承期待其它的节制通知可能剩下的处事线程中止。
上面的内容是关于进口点函数的,下面的内容则是关于ServiceMain函数的。还记得以前讲过的ServiceMain函数的的原型吗?但实际上一个ServiceMain函数凡是忽略通报给它的两个参数,因为处事一般不怎么通报参数。配置一个处事最好的要领就是配置注册表,一般处事在
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Service\ServiceName\Parameters
子键下存放本身的配置,这里的ServiceName是处事的名字。事实上,大概要写一个客户应用措施去举办处事的配景配置,这个客户应用措施将这些信息存在注册表中,以便处事读取。当一个外部应用措施已经改变了某个正在运行中的处事的配置数据的时候,这个处事可以或许用RegNotifyChangeKeyValue函数去接管一个通知,这样就答允处事快速的从头配置本身。
前面讲到StartServiceCtrlDispatcher为每一个通报到它的数组中的非空元素发生一个新的线程。接下来,一个ServiceMain要做些什么呢?MSDN内里的原文是这样说的:The ServiceMain function should immediately call the RegisterServiceCtrlHandler function to specify a Handler function to handle control requests. Next, it should call the SetServiceStatus function to send status information to the service control manager. 为什么呢?因为发出启动处事请求之后,假如在一按时间之内无法完成处事的初始化,SCM会认为处事的启动已经失败了,这个时间的长度在Win NT 4.0中是80秒,Win2000中不详…
基于上面的来由,ServiceMain要迅速完成自身事情,首先是必不行少的两项事情,第一项是挪用RegisterServiceCtrlHandler函数去通知SCM它的CtrlHandler回调函数的地点:
SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(
LPCTSTR lpServiceName, //处事的名字
LPHANDLER_FUNCTION lpHandlerProc //CtrlHandler函数地点
)
第一个参数指明你正在成立的CtrlHandler是为哪一个处事所用,第二个参数是CtrlHandler函数的地点。lpServiceName必需和在SERVICE_TABLE_ENTRY内里被初始化的处事的名字相匹配。RegisterServiceCtrlHandler返回一个SERVICE_STATUS_HANDLE,这是一个32位的句柄。SCM用它来独一确定这个处事。当这个处事需要把它其时的状态陈诉给SCM的时候,就必需把这个句柄传给需要它的Win32函数。留意:这个句柄和其他大大都的句柄差异,你无需封锁它。
#p#分页标题#e#
SCM要求ServiceMain函数的线程在一秒钟内挪用RegisterServiceCtrlHandler函数,不然SCM会认为处事已经失败。但在这种环境下,SCM不会终止处事,不外在NT 4中将无法启动这个处事,同时会返回一个不正确的错误信息,这一点在Windows 2000中获得了批改。
在RegisterServiceCtrlHandler函数返回后,ServiceMain线程要当即汇报SCM处事正在继承初始化。详细的要领是通过挪用SetServiceStatus函数通报SERVICE_STATUS数据布局。
BOOL SetServiceStatus(
SERVICE_STATUS_HANDLE hService, //处事的句柄
SERVICE_STATUS lpServiceStatus //SERVICE_STATUS布局的地点
)
这个函数要求通报给它指明处事的句柄(方才通过挪用RegisterServiceCtrlHandler获得),和一个初始化的SERVICE_STATUS布局的地点:
typedef struct _SERVICE_STATUS
{
DWORD dwServiceType;
DWORD dwCurrentState;
DWORD dwControlsAccepted;
DWORD dwWin32ExitCode;
DWORD dwServiceSpecificExitCode;
DWORD dwCheckPoint;
DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;
SERVICE_STATUS布局含有七个成员,它们反应处事的现行状态。所有这些成员必需在这个布局被通报到SetServiceStatus之前正确的配置。
成员dwServiceType指明处事可执行文件的范例。假如你的可执行文件中只有一个单独的处事,就把这个成员配置成SERVICE_WIN32_OWN_PROCESS;假如拥有多个处事的话,就配置成SERVICE_WIN32_SHARE_PROCESS。除了这两个符号之外,假如你的处事需要和桌面产生交互(虽然不推荐这样做),就要用“OR”运算符附加上SERVICE_INTERACTIVE_PROCESS。这个成员的值在你的处事的保留期内绝对不该该改变。
成员dwCurrentState是这个布局中最重要的成员,它将汇报SCM你的处事的现行状态。为了陈诉处事仍在初始化,应该把这个成员配置成SERVICE_START_PENDING。在今后详细报告CtrlHandler函数的时候详细表明其它大概的值。
成员dwControlsAccepted指明处事愿意接管什么样的节制通知。假如你答允一个SCP去暂停/继承处事,就把它设成SERVICE_ACCEPT_PAUSE_CONTINUE。许多处事不支持暂停或继承,就必需本身抉择在处事中它是否可用。假如你答允一个SCP去遏制处事,就要配置它为SERVICE_ACCEPT_STOP。假如处事要在操纵系统封锁的时候获得通知,配置它为SERVICE_ACCEPT_SHUTDOWN可以收到预期的功效。这些符号可以用“OR”运算符组合。
成员dwWin32ExitCode和dwServiceSpecificExitCode是答允处事陈诉错误的要害,假如但愿处事去陈诉一个Win32错误代码(预界说在WinError.h中),它就配置dwWin32ExitCode为需要的代码。一个处事也可以陈诉它自己特有的、没有映射到一个预界说的Win32错误代码中的错误。为了这一点,要把dwWin32ExitCode配置为ERROR_SERVICE_SPECIFIC_ERROR,然后还要配置成员dwServiceSpecificExitCode为处事特有的错误代码。当处事运行正常,没有错误可以陈诉的时候,就配置成员dwWin32ExitCode为NO_ERROR。
最后的两个成员dwCheckPoint和dwWaitHint是一个处事用来陈诉它当前的事件希望环境的。当成员dwCurrentState被配置成SERVICE_START_PENDING的时候,应该把dwCheckPoint设成0,dwWaitHint设成一个颠末多次实验后确定较量符合的数,这样处事才气高效运行。一旦处事被完全初始化,就应该从头初始化SERVICE_STATUS布局的成员,变动dwCurrentState为SERVICE_RUNNING,然后把dwCheckPoint和dwWaitHint都改为0。
dwCheckPoint成员的存在对用户是有益的,它答允一个处事陈诉它处于历程的哪一步。每一次挪用SetServiceStatus时,可以增加它到一个能指明处事已经执行到哪一步的数字,它可以辅佐用户抉择多长时间陈诉一次处事的希望环境。假如抉择要陈诉处事的初始化历程的每一步,就应该配置dwWaitHint为你认为达到下一步所需的毫秒数,而不是处事完成它的历程所需的毫秒数。
在处事的所有初始化都完成之后,处事挪用SetServiceStatus指明SERVICE_RUNNING,在那一刻处事已经开始运行。凡是一个处事是把本身放在一个轮回之中来运行的。在轮回的内部这个处事历程悬挂本身,期待指明它下一步是应该暂停、继承或遏制之类的网络请求或通知。当一个请求达到的时候,处事线程激活并处理惩罚这个请求,然后再轮回归去期待下一个请求/通知。
假如一个处事由于一个通知而激活,它会先处理惩罚这个通知,除非这个处事获得的是遏制或封锁的通知。假如然的是遏制或封锁的通知,处事线程将退出轮回,执行须要的排除操纵,然后从这个线程返回。当ServiceMain线程返回并中止时,引起在StartServiceCtrlDispatcher内睡眠的线程激活,并像在前面表明过的那样,淘汰它运行的处事的计数。