NT 服务和使用邮件槽的进程间通信






4.40/5 (7投票s)
本文解释了 NT 服务的不同部分以及使用邮件槽进行进程间通信。
引言
本文解释了 NT 服务的不同部分以及使用邮件槽进行进程间通信。
NT 服务的不同方面
编写 NT 服务时需要涵盖的主要方面在下面详细说明。本文涵盖的主要方面如下:
- 创建/注册服务
- 删除服务
- 注册
ServiceMain()
ServiceHandler()
- 设置服务状态
1) 创建/注册服务
可以通过打开 SCM 来创建服务。这通常在WinMain()
或main()
函数中完成,基于传递的命令行参数。OpenSCManager()
将返回 SCM句柄。使用此句柄,必须调用CreateService()
来创建服务。
一旦服务 EXE 准备好,要创建服务,请打开命令提示符并移动到服务二进制文件所在的目录,然后键入以下内容:SigmaSrv /register。在二进制文件中创建服务的调用如下所示
SC_HANDLE schSCManager = NULL; // Service Control Manager handle
SC_HANDLE schService = NULL; // Service Handle
if(0 == _tcsicmp((const _TCHAR *)lpCmdLine, (const _TCHAR *)"/register"))
{
// Open SCM and register service
schSCManager = OpenSCManager(NULL, // local machine
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
// Get the executable file path
TCHAR szFilePath[_MAX_PATH];
::GetModuleFileName(NULL, szFilePath, _MAX_PATH);
schService = CreateService(schSCManager, // Handle to SCM
TEXT(SIGMA_SUPPORT_SERVICE_NAME), // name of service
TEXT("Sigma Support Service"), // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // own process
SERVICE_DEMAND_START, // SigmaMain will start this service
SERVICE_ERROR_NORMAL, // error control type
szFilePath, // path to service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL);
if(NULL == schService)
{
MessageBox(NULL, TEXT(lpCmdLine), TEXT("Sigma"), MB_OK);
}
CloseServiceHandle(schSCManager);
2) 删除服务
与创建服务类似,删除服务也是基于命令行参数启动的。在这种情况下,也必须使用OpenSCManager()
打开 SCM,然后使用OpenService()
以所有权限打开服务。然后必须调用DeleveService()
来删除服务。
要删除服务,请打开命令提示符并移动到服务二进制文件所在的目录,然后键入以下内容:SigmaSrv /unregister。在二进制文件中完成此操作的调用如下所示
else if(0 == _tcsicmp((const _TCHAR *)lpCmdLine, (const _TCHAR *)"/unregister"))
{
schSCManager = NULL;
schService = NULL; // Service Handle
// Open SCM and register service
schSCManager = OpenSCManager(NULL, // local machine
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
schService = OpenService(schSCManager, // SCManager database
TEXT("SigmaSrv"), // name of service
SC_MANAGER_ALL_ACCESS); // only need DELETE access
if (schService == NULL)
{
//Log this state
}
if (!DeleteService(schService) )
{
//Log if not able to delete the service
}
CloseServiceHandle(schService);
3) 注册 ServiceMain()
ServiceMain()
是一个回调函数,也是服务的起点。通过在WinMain()
中调用StartServiceCtrlDispatcher()
来引用ServiceMain()
。SERVICE_TABLE_ENTRY
结构用于通过StartServiceCtrlDispatcher()
调用传递服务名称及其关联的起点(在本例中为ServiceMain()
)。
可以通过此结构在同一服务中容纳多个服务。{NULL, NULL}
用于标记此数组的结尾。ServiceMain()
函数可以使用任何您想要的名称,它不一定是ServiceMain()
。
SERVICE_TABLE_ENTRY srvDispatchTable[]=
{{ TEXT(SIGMA_SUPPORT_SERVICE_NAME), (LPSERVICE_MAIN_FUNCTION)ServiceMain },
{ NULL, NULL }};
DWORD dw_Error = 0;
g_hServiceStatusThread = CreateThread(0,
0,
(LPTHREAD_START_ROUTINE) SigmaSupportServiceStatusThread,
0,
NULL,
&dw_Error);
//Start service
StartServiceCtrlDispatcher(srvDispatchTable);
4) SeviceHandler()
ServiceHandler
是另一个回调函数,它通过在ServiceMain()
中调用RegisterServiceCtrlHandler()
来注册。这是在ServiceMain()
函数中完成的。此函数根据收到的控制请求(例如:停止服务的控制请求、暂停服务等)调用用户定义的服务函数。RegisterServiceCtrlHandler()
的参数是服务名称和ServiceHandler()
过程名称。这里ServiceHandler()
也可以使用任何名称。注册ServiceHandler()
的调用如下所示
g_hServiceStatusHandle = RegisterServiceCtrlHandler(TEXT(SIGMA_SUPPORT_SERVICE_NAME)
, ServiceHandler);
5) 设置服务状态
应该调用SetServiceStatus()
来设置服务的状态。这是 SCM 正确理解服务状态所必需的。在本例的源代码中,SetServiceStatus()
包含在函数SetSigmaServiceStatus()
中。
邮件槽
为了显示服务正在运行,本示例使用了邮件槽。邮件槽是一种进程间通信机制。服务将定期将其状态更新到客户端应用程序的状态栏中。此客户端应用程序可在 CodeProject 上找到,网址为:Sigma.aspx。(请耐心等待客户端应用程序更新。)
客户端将打开邮件槽,此服务将不断将其状态更新到邮件槽中。客户端需要定期读取这些信息并将信息显示/传达给用户。
服务使用CreateFile()
打开邮件槽,并使用WriteFile()
将其状态消息写入邮件槽。在示例中,WriteFile()
包含在函数PostToMailSlot()
中。以下是服务使用的代码片段
LPTSTR lpszSlotName = TEXT("\\\\.\\mailslot\\sigmamain");
.
.
g_hFile = CreateFile(lpszSlotName,
GENERIC_WRITE,
FILE_SHARE_READ,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
.
.
bResult = WriteFile(g_hFile,
lpszMessage,
(DWORD) lstrlen(lpszMessage) + 1, // add null termination
&dw_MsgWrittenLen,
(LPOVERLAPPED) NULL);
客户端获取消息的步骤是:创建邮件槽、获取邮件槽信息和读取邮件槽消息。客户端获取邮件槽消息的调用如下所示
#define MAILSLOTNAME \\\\.\\mailslot\\sigmamain
.
.
ghSlot = CreateMailslot(TEXT(MAILSLOTNAME),
0, // no maximum message size
MAILSLOT_WAIT_FOREVER, // no time-out for operations
(LPSECURITY_ATTRIBUTES) NULL); // default security
.
.
bResult = GetMailslotInfo(ghSlot, // mailslot handle
(LPDWORD) NULL, // no maximum message size
&dw_MsgSize, // size of next message
&dw_MsgCount, // number of messages
(LPDWORD) NULL); // no read time-out
.
.
// if there are any messages then we read the mailslot
DWORD dw_MsgSize = 0;
DWORD dw_MsgCount = 0;
DWORD dw_MsgRead = 0;
LPTSTR lpszBuffer;
BOOL bResult;
HANDLE hEvent;
OVERLAPPED ov;
hEvent = CreateEvent(NULL, FALSE, FALSE, TEXT("SigmaMailSlot"));
ov.Offset = 0;
ov.OffsetHigh = 0;
ov.hEvent = hEvent;
bResult = GetMailslotInfo(ghSlot, // mailslot handle
(LPDWORD) NULL, // no maximum message size
&dw_MsgSize, // size of next message
&dw_MsgCount, // number of messages
(LPDWORD) NULL); // no read time-out
while (dw_MsgCount != 0) // retrieve all messages
{
// memory for the message.
lpszBuffer = (LPTSTR) GlobalAlloc(GPTR,
dw_MsgSize);
if( NULL == lpszBuffer )
return FALSE;
lpszBuffer[0] = '\0';
bResult = ReadFile(ghSlot,
lpszBuffer,
dw_MsgSize,
&dw_MsgRead,
&ov);
//Write or display the message in lpszBuffer
}
//Call GetMailSlotInfo() again and repeat the above loop to get further messages.
环境
VC++ 6.0、UNICODE、C++ 和 XP SP3
此示例仅在 Windows XP SP3 中进行了测试。
历史
- 2009年5月19日:首次发布