65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.40/5 (7投票s)

2009 年 5 月 19 日

CPOL

3分钟阅读

viewsIcon

34713

downloadIcon

1025

本文解释了 NT 服务的不同部分以及使用邮件槽进行进程间通信。

引言

本文解释了 NT 服务的不同部分以及使用邮件槽进行进程间通信。

NT 服务的不同方面

编写 NT 服务时需要涵盖的主要方面在下面详细说明。本文涵盖的主要方面如下:

  1. 创建/注册服务
  2. 删除服务
  3. 注册ServiceMain()
  4. ServiceHandler()
  5. 设置服务状态

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日:首次发布
© . All rights reserved.