Introduction
This article explains about different parts of an NT service and inter-process communication using Mailslots.
Different Aspects of an NT Service
Major aspects that need to be covered when writing an NT service are detailed out below. Following are the major aspects that are covered in this article:
- Creating/Registering a service
- Removing a service
- Registering
ServiceMain()
ServiceHandler()
- Setting Service status
1) Creating/Registering a Service
Service can be created by opening the SCM. This is generally done in the WinMain()
or main()
function based on a command line parameter that is passed. OpenSCManager()
will give the SCM handle. With this handle, CreateService()
will have to be called to get the service created.
Once the service EXE is ready, to create the service, go to command prompt and move to the directory where the service binary is present and type the following: SigmaSrv /register. Calls within the binary to create the service are shown below:
SC_HANDLE schSCManager = NULL; SC_HANDLE schService = NULL; if(0 == _tcsicmp((const _TCHAR *)lpCmdLine, (const _TCHAR *)"/register"))
{
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); TCHAR szFilePath[_MAX_PATH];
::GetModuleFileName(NULL, szFilePath, _MAX_PATH);
schService = CreateService(schSCManager, TEXT(SIGMA_SUPPORT_SERVICE_NAME), TEXT("Sigma Support Service"), SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, szFilePath, NULL, NULL, NULL, NULL, NULL);
if(NULL == schService)
{
MessageBox(NULL, TEXT(lpCmdLine), TEXT("Sigma"), MB_OK);
}
CloseServiceHandle(schSCManager);
2) Removing a Service
Similar to creating a service, removing a service is also initiated based on the command line argument. In this case also SCM will have to be opened using OpenSCManager()
, followed by opening the service with all rights using OpenService()
. Then DeleveService()
will have to be called to delete the service.
To remove the service, go to command prompt and move to the directory where the service binary is present and type the following: SigmaSrv /unregister. Calls within the binary to get this done are shown below:
else if(0 == _tcsicmp((const _TCHAR *)lpCmdLine, (const _TCHAR *)"/unregister"))
{
schSCManager = NULL;
schService = NULL; schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); schService = OpenService(schSCManager, TEXT("SigmaSrv"), SC_MANAGER_ALL_ACCESS);
if (schService == NULL)
{
}
if (!DeleteService(schService) )
{
}
CloseServiceHandle(schService);
3) Registering ServiceMain()
ServiceMain()
is a callback function and the starting point for a service. Reference to ServiceMain()
is given via a call to StartServiceCtrlDispatcher()
in WinMain()
. SERVICE_TABLE_ENTRY
structure is used to pass the service name and its associated starting point (ServiceMain()
in this case) through the StartServiceCtrlDispatcher()
call.
There could be multiple services housed in the same service via this structure. {NULL, NULL}
is required to mark the end of this array. ServiceMain()
function can have any name you wish, it need not be ServiceMain()
always. Calls to register ServiceMain()
are shown below. This is done in WinMain()
:
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);
StartServiceCtrlDispatcher(srvDispatchTable);
4) SeviceHandler()
ServiceHandler
is another callback function which is registered in ServiceMain()
by calling RegisterServiceCtrlHandler()
. This is done within the ServiceMain()
function. This function calls the user defined service functions based on the control requests received (Example: control request to stop the service, pause the service, etc.). Arguments for RegisterServiceCtrlHandler()
are the service name and the ServiceHandler()
proc name. Here also ServiceHandler()
can be given any name. Call to register the ServiceHandler()
is shown below:
g_hServiceStatusHandle = RegisterServiceCtrlHandler(TEXT(SIGMA_SUPPORT_SERVICE_NAME)
, ServiceHandler);
5) Setting Service Status
SetServiceStatus()
should be called to set the status of the service. This is required for the SCM to correctly understand the status of the service. In this article's source, SetServiceStatus()
is encased in the function SetSigmaServiceStatus()
.
Mailslot
To show that the service is running, mailslots had been made use of in this sample. Mailslot is an interprocess communication mechanism. Service will periodically update its status into the status bar of a client application. This client application is available on CodeProject at the following location: Sigma.aspx. (Please bear with me till the client application is updated.)
Client will have the mailslot open and this service will keep updating its status into the mailslot. It is up to the client to read this on a periodic basis and display/convey the information to the user.
Service opens the mailslot using CreateFile()
and keeps writing its status message into mailslot using WriteFile()
. In the sample, WriteFile()
is encased in the function PostToMailSlot()
. Following is the code snippet that the service uses:
LPTSTR lpszSlotName = TEXT("<a>\\\\.\\mailslot\\sigmamain</a>");
.
.
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, &dw_MsgWrittenLen,
(LPOVERLAPPED) NULL);
Steps to get the message on the client side are: Create the mailslot, Get mailslot information and Read mailslot messages. The calls on the client side to get the mailslot messages are shown below:
#define MAILSLOTNAME <a>\\\\.\\mailslot\\sigmamain</a>
.
.
ghSlot = CreateMailslot(TEXT(MAILSLOTNAME),
0, MAILSLOT_WAIT_FOREVER, (LPSECURITY_ATTRIBUTES) NULL); .
.
bResult = GetMailslotInfo(ghSlot, (LPDWORD) NULL, &dw_MsgSize, &dw_MsgCount, (LPDWORD) NULL); .
.
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, (LPDWORD) NULL, &dw_MsgSize, &dw_MsgCount, (LPDWORD) NULL); while (dw_MsgCount != 0) {
lpszBuffer = (LPTSTR) GlobalAlloc(GPTR,
dw_MsgSize);
if( NULL == lpszBuffer )
return FALSE;
lpszBuffer[0] = '\0';
bResult = ReadFile(ghSlot,
lpszBuffer,
dw_MsgSize,
&dw_MsgRead,
&ov);
}
Environment
VC++ 6.0, UNICODE, C++ and XP SP3
This sample has been tested only in Windows XP SP3.
History
- 19th May, 2009: Initial post