|< Windows Services Programming 2 | Main | Windows Services Programming 4 | Site Index | Download |


 

 

 

 

MODULE EE

WINDOWS SERVICES

Part 3: MORE PROGRAM EXAMPLES

 

 

 

 

What do we have in this Module?

  1. Writing a service program's main function example.

  2. Writing a ServiceMain function example.

  3. Writing a control handler function example.

  4. Creating a multithreaded service example.

  5. Creating an interactive process example.

  6. Handling logoff events example.

 

 

 

 

 

 

 

 

 

 

 

 

 

My Training Period: xx hours. Before you begin, read some instruction here.

 

The programming abilities:

  • Able to collect and understand all the required information related to Windows Services.

  • Able to understand, build and run various Windows Service application programs.

Note: For Multithreaded program examples, you have to set your project to Multithread project type.

 

 

Writing a Service Program's main Function

 

The main function of a service program calls the StartServiceCtrlDispatcher() function to connect to the SCM and start the control dispatcher thread. The dispatcher thread loops, waiting for incoming control requests for the services specified in the dispatch table. This thread does not return until there is an error or all of the services in the process have terminated. When all services in a process have terminated, the SCM sends a control request to the dispatcher thread telling it to shut down. The thread can then return from the StartServiceCtrlDispatcher() call and the process can terminate.

 

The following example is a service process that supports only one service. It takes two parameters: a string that can contain one formatted output character and a numeric value to be used as the formatted character. The SvcDebugOut() function prints informational messages and errors to the debugger. To output debugging information, this code calls SvcDebugOut(). The source code for SvcDebugOut() is given in Writing a Service Program's main Function example.

 

// For WinXp, don't forget to link to

// Advapi32.lib library if needed...

#define _WIN32_WINNT 0x0501

#include <windows.h>

#include <stdio.h>

 

// Prototypes, just empty skeletons...

void SvcDebugOut(LPSTR String, DWORD Status);

void  WINAPI MyServiceCtrlHandler(DWORD opcode);

void  MyServiceStart(DWORD argc, LPTSTR *argv);

DWORD MyServiceInitialization(DWORD argc, LPTSTR *argv, DWORD *specificError);

 

void main()

{

       // Using 2-D array as a table...

       // The name of a service to be run in this service process - "MyService",

       // The function as the starting point for a service - MyServiceStart or

       // a pointer to a ServiceMain() function...

       // The members of the last entry in the table must have NULL values

       // to designate the end of the table...

       SERVICE_TABLE_ENTRY  DispatchTable[] = {{"MyService", (LPSERVICE_MAIN_FUNCTION)MyServiceStart}, {NULL, NULL}};

 

   if (!StartServiceCtrlDispatcher(DispatchTable))

       SvcDebugOut("StartServiceCtrlDispatcher() failed, error: %d\n", GetLastError());

   else

       printf("StartServiceCtrlDispatcher() looks OK.\n");

   return;

}

 

// ==========================================================================

// Prototype definitions...just skeletons here...

void  WINAPI MyServiceCtrlHandler(DWORD opcode)

{

       // Service control information here...

       return;

}

void  MyServiceStart(DWORD argc, LPTSTR *argv)

{

       // Starting service information here...

       return;

}

 

DWORD MyServiceInitialization(DWORD argc, LPTSTR *argv, DWORD *specificError)

{

       // Service initialization information here...

       return 0;

}

 

// Very simple info to the standard output...

void SvcDebugOut(LPSTR String, DWORD Status)

{

   CHAR  Buffer[1024];

   printf("In SvcDebugOut() lol!\n");

   if (strlen(String) < 1000)

   {

      sprintf(Buffer, String, Status);

      OutputDebugStringA(Buffer);

   }

   else

      printf("String too long...\n");

 

   return;

}

 

Windows services: Writing service program main function

 

If your service program supports multiple services, the implementation of the main function will differ slightly. The names of the additional services should be added to the dispatch table so they can be monitored by the dispatcher thread.

 

Writing a ServiceMain Function

 

The MyServiceStart() function in the following example is the ServiceMain() function for the service. MyServiceStart() has access to the command-line arguments, in the way that the main function of a console application does. The first parameter contains the number of arguments being passed to the service. There will always be at least one argument. The second parameter is a pointer to an array of string pointers. The first item in the array always points to the service name. The MyServiceStart() function first fills in the SERVICE_STATUS structure including the control codes that it accepts. Although this service accepts SERVICE_CONTROL_PAUSE and SERVICE_CONTROL_CONTINUE, it does nothing significant when told to pause. SERVICE_ACCEPT_PAUSE_CONTINUE was included for illustration purposes only; if pausing does not add value to your service, do not support it.

The MyServiceStart() function then calls the RegisterServiceCtrlHandler() function to register MyService as the service's Handler() function and begin initialization. The following sample initialization function, MyServiceInitialization(), is included for illustration purposes; it does not perform any initialization tasks such as creating additional threads. If your service's initialization performs tasks that are expected to take longer than one second, your code must call the SetServiceStatus() function periodically to send out wait hints and check points indicating that progress is being made. When initialization has completed successfully, the example calls SetServiceStatus() with a status of SERVICE_RUNNING and the service continues with its work. If an error has occurred in initialization, MyServiceStart() reports SERVICE_STOPPED with the SetServiceStatus() function and returns. Because this sample service does not complete any real tasks, MyServiceStart() simply returns control to the caller. However, your service should use this thread to complete whatever tasks it was designed to do. If a service does not need a thread to do its work (such as a service that only processes RPC requests), its ServiceMain() function should return control to the caller. It is important for the function to return, rather than call the ExitThread() function, because returning allows for cleanup of the memory allocated for the arguments. You can provide for additional cleanup by calling the RegisterWaitForSingleObject() function on an event before returning. The thread that is running the ServiceMain() function terminates, but the service itself continues to run. The service control handler can then signal your event when the service stops, and a thread from the thread pool executes your callback to perform any additional cleanup you need and to call SetServiceStatus() with SERVICE_STOPPED. This technique is not illustrated in the sample code below, however.

To output a simple debugging information, MyServiceStart() calls SvcDebugOut() as used in the previous example.

 

// For WinXp

// Just another skeleton example as previous one...

#define _WIN32_WINNT 0x0501

#include <windows.h>

#include <stdio.h>

 

SERVICE_STATUS          MyServiceStatus;

SERVICE_STATUS_HANDLE   MyServiceStatusHandle;

 

DWORD MyServiceInitialization(DWORD   argc, LPTSTR  *argv, DWORD *specificError);

// Application define ServiceMain()...

void WINAPI MyServiceStart(DWORD argc, LPTSTR *argv);

void WINAPI MyServiceCtrlHandler(DWORD opcode);

void SvcDebugOut(LPSTR String, DWORD Status);

 

void main()

{

   SERVICE_TABLE_ENTRY DispatchTable[] = {{"MyService", MyServiceStart}, {NULL, NULL}};

   if (!StartServiceCtrlDispatcher(DispatchTable))

   {

       SvcDebugOut("StartServiceCtrlDispatcher() failed, error: %d.\n", GetLastError());

   }

   else

       printf("StartServiceCtrlDispatcher() looks OK.\n");

   return;

}

 

// Stub initialization function...

DWORD MyServiceInitialization(DWORD argc, LPTSTR *argv, DWORD *specificError)

{

    *argv;

    argc;

    specificError;

    // should return status...

    return 0;

}

 

void WINAPI MyServiceStart(DWORD argc, LPTSTR *argv)

{

    DWORD status;

    DWORD specificError;

    // Type of service, application or driver...

    MyServiceStatus.dwServiceType        = SERVICE_WIN32;

    // The service is starting...

    MyServiceStatus.dwCurrentState       = SERVICE_START_PENDING;

    // The service can be stopped & can be paused and continued.

    MyServiceStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;

    MyServiceStatus.dwWin32ExitCode      = 0;

    MyServiceStatus.dwServiceSpecificExitCode = 0;

    MyServiceStatus.dwCheckPoint         = 0;

    MyServiceStatus.dwWaitHint           = 0;

 

    MyServiceStatusHandle = RegisterServiceCtrlHandler("MyService", MyServiceCtrlHandler);

 

    if (MyServiceStatusHandle == (SERVICE_STATUS_HANDLE)0)

    {

       SvcDebugOut("[MY_SERVICE] RegisterServiceCtrlHandler() failed, error: %d.\n", GetLastError());

        return;

    }

    else

       printf("RegisterServiceCtrlHandler() looks OK.\n");

 

    //===============================================================

       // Initialization code goes here. Return the status...

       status = MyServiceInitialization(argc, argv, &specificError);

    // Handle error condition

    if (status != NO_ERROR)

    {

        // The service is not running...

        MyServiceStatus.dwCurrentState       = SERVICE_STOPPED;

        MyServiceStatus.dwCheckPoint         = 0;

        MyServiceStatus.dwWaitHint           = 0;

        MyServiceStatus.dwWin32ExitCode      = status;

        MyServiceStatus.dwServiceSpecificExitCode = specificError;

        SetServiceStatus(MyServiceStatusHandle, &MyServiceStatus);

        return;

    }

 

    // Initialization complete - report running status.

    MyServiceStatus.dwCurrentState       = SERVICE_RUNNING;

    MyServiceStatus.dwCheckPoint         = 0;

    MyServiceStatus.dwWaitHint           = 0;

 

    if (!SetServiceStatus(MyServiceStatusHandle, &MyServiceStatus))

    {

       status = GetLastError();

       SvcDebugOut("SetServiceStatus() error: %ld\n", status);

    }

    else

       printf("SetServiceStatus() looks OK.\n");

    // By assuming starting and initializing the service is OK, this

    // is where the service does its work...

    SvcDebugOut("Returning the Main Thread.\n", 0);

       return;

}

 

void  WINAPI MyServiceCtrlHandler(DWORD opcode)

{

       // Just a skeleton here...

}

 

void SvcDebugOut(LPSTR String, DWORD Status)

{

   CHAR  Buffer[1024];

 

   if (strlen(String) < 1000)

   {

      sprintf(Buffer, String, Status);

      OutputDebugStringA(Buffer);

   }

   else

      printf("String too long...\n");

}

 

Windows services: Writing ServiceMain function

 

Writing a Control Handler Function

 

We add another function, MyServiceCtrlHandler() in our previous program. The MyServiceCtrlHandler() function in the following example is the Handler() function. When this function is called by the dispatcher thread, it handles the control code passed in the Opcode parameter and then calls the SetServiceStatus() function to update the service's status. Every time a Handler() function receives a control code, it is appropriate to return status with a call to SetServiceStatus() regardless of whether the service acts on the control.

When the pause control is received, MyServiceCtrlHandler() simply sets the dwCurrentState member of the SERVICE_STATUS structure to SERVICE_PAUSED. Likewise, when the continue control is received, the state is set to SERVICE_RUNNING. Therefore, MyServiceCtrlHandler() is not a good example of how to handle the pause and continue controls. Because MyServiceCtrlHandler() is a template for a Handler() function, code for the pause and continue controls is included for completeness. A service that supports either the pause or continue control should handle these controls in a way that makes sense. Many services support neither the pause or continue control. If the service indicates that it does not support pause or continue with the dwControlsAccepted parameter, then the SCM will not send pause or continue controls to the service's Handler() function.

To output debugging information, MyServiceCtrlHandler() calls SvcDebugOut(). Also, note that the MyServiceStatus() variable is a global variable and should be initialized as demonstrated in Writing a ServiceMain() function.

 

 

// For WinXp

#define _WIN32_WINNT 0x0501

#include <windows.h>

#include <stdio.h>

 

SERVICE_STATUS          MyServiceStatus;

SERVICE_STATUS_HANDLE   MyServiceStatusHandle;

 

DWORD MyServiceInitialization(DWORD argc, LPTSTR *argv, DWORD *specificError);

void WINAPI MyServiceStart(DWORD argc, LPTSTR *argv);

void WINAPI MyServiceCtrlHandler(DWORD opcode);

void SvcDebugOut(LPSTR String, DWORD Status);

 

void main()

{

   SERVICE_TABLE_ENTRY DispatchTable[] = {{"MyService", MyServiceStart}, {NULL, NULL}};

   if (!StartServiceCtrlDispatcher(DispatchTable))

      SvcDebugOut("StartServiceCtrlDispatcher() failed, error: %d.\n", GetLastError());

   else

      printf("StartServiceCtrlDispatcher() looks OK.\n");

   return;

}

 

// Stub initialization function...

DWORD MyServiceInitialization(DWORD argc, LPTSTR *argv, DWORD *specificError)

{

       *argv;

       argc;

       specificError;

 

       return 0;

}

 

void WINAPI MyServiceStart(DWORD argc, LPTSTR *argv)

{

    DWORD status;

    DWORD specificError;

    // Type of service, application or driver...

    MyServiceStatus.dwServiceType        = SERVICE_WIN32;

    // The service is starting...

    MyServiceStatus.dwCurrentState       = SERVICE_START_PENDING;

    // The service can be stopped & can be paused and continued.

    MyServiceStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;

    MyServiceStatus.dwWin32ExitCode      = 0;

    MyServiceStatus.dwServiceSpecificExitCode = 0;

    MyServiceStatus.dwCheckPoint         = 0;

    MyServiceStatus.dwWaitHint           = 0;

 

    MyServiceStatusHandle = RegisterServiceCtrlHandler("MyService", MyServiceCtrlHandler);

 

    if (MyServiceStatusHandle == (SERVICE_STATUS_HANDLE)0)

    {

       SvcDebugOut("RegisterServiceCtrlHandler() failed, error: %d.\n", GetLastError());

       return;

    }

    else

       printf("RegisterServiceCtrlHandler() looks OK.\n");

 

    // Initialization code goes here...return the status...

    status = MyServiceInitialization(argc, argv, &specificError);

 

    // Handle error condition

    if (status != NO_ERROR)

    {

        // The service is not running...

        MyServiceStatus.dwCurrentState       = SERVICE_STOPPED;

        MyServiceStatus.dwCheckPoint         = 0;

        MyServiceStatus.dwWaitHint           = 0;

        MyServiceStatus.dwWin32ExitCode      = status;

        MyServiceStatus.dwServiceSpecificExitCode = specificError;

        SetServiceStatus(MyServiceStatusHandle, &MyServiceStatus);

        return;

    }

 

    // Initialization complete - report running status.

    MyServiceStatus.dwCurrentState       = SERVICE_RUNNING;

    MyServiceStatus.dwCheckPoint         = 0;

    MyServiceStatus.dwWaitHint           = 0;

 

    if (!SetServiceStatus(MyServiceStatusHandle, &MyServiceStatus))

    {

       status = GetLastError();

       SvcDebugOut("SetServiceStatus() error: %ld\n", status);

    }

    else

       printf("SetServiceStatus() looks OK.\n");

 

    // This is where the service does its work...

    SvcDebugOut("Returning the Main Thread.\n", 0);

       return;

}

 

// Handler function - receives Opcode, calls SetServiceStatus()

void WINAPI MyServiceCtrlHandler(DWORD Opcode)

{

   DWORD status;

 

   switch(Opcode)

   {

              case SERVICE_CONTROL_PAUSE:

                // Do whatever it takes to pause here...

                MyServiceStatus.dwCurrentState = SERVICE_PAUSED;

                break;

              case SERVICE_CONTROL_CONTINUE:

                // Do whatever it takes to continue here...

                MyServiceStatus.dwCurrentState = SERVICE_RUNNING;

                break;

              case SERVICE_CONTROL_STOP:

                // Do whatever it takes to stop here...

                MyServiceStatus.dwWin32ExitCode = 0;

                MyServiceStatus.dwCurrentState  = SERVICE_STOPPED;

                MyServiceStatus.dwCheckPoint    = 0;

                MyServiceStatus.dwWaitHint      = 0;

                if (!SetServiceStatus(MyServiceStatusHandle, &MyServiceStatus))

                {

                    status = GetLastError();

                    SvcDebugOut("[MY_SERVICE] SetServiceStatus() error: %ld\n", status);

                }

                SvcDebugOut("Leaving MyService.\n", 0);

                return;

               case SERVICE_CONTROL_INTERROGATE:

                // Fall through to send current status.

                break;

              default:

                // else

                SvcDebugOut("Unrecognized opcode %ld.\n", Opcode);

   }

 

   // Send current status.

   if (!SetServiceStatus(MyServiceStatusHandle, &MyServiceStatus))

   {

       status = GetLastError();

       SvcDebugOut("SetServiceStatus error %ld.\n", status);

       return;

   }

   else

       printf("SetServiceStatus() is OK.\n");

}

 

// Some debug info...

void SvcDebugOut(LPSTR String, DWORD Status)

{

   CHAR  Buffer[1024];

   if (strlen(String) < 1000)

   {

      sprintf(Buffer, String, Status);

      OutputDebugStringA(Buffer);

   }

   else

      printf("Too many string...\n");

}

 

Windows services: Writing a control handler function

 

Creating a Multithreaded Service

 

The following sample code demonstrates how a simple service can spawn worker threads (A worker thread is commonly used to handle background tasks that the user shouldn't have to wait for to continue using your application. Tasks such as recalculation and background printing are good examples of worker threads), respond to the SCM, notify the threads to exit, keep the SCM notified of the state and progress of the service, and report to the SCM that the service is stopped. To install the service, build it as a console application and use the SC utility included with either the Windows NT Resource Kit or the Platform SDK. Use the Service Control utility in the Control Panel to start and stop the service.

 

// For WinXp

#define _WIN32_WINNT 0x0501

#include <windows.h>

#include <tchar.h>

#include <stdio.h>

 

HANDLE hStopEvent;

// Three thread handle...

HANDLE hThreads[3] = {NULL, NULL, NULL};

LPTSTR lpszServiceName;

SERVICE_STATUS_HANDLE ssh;

 

DWORD WINAPI ThreadProc(LPVOID lpParameter);

// Service start...

void  WINAPI  Service_Main(DWORD dwArgc, LPTSTR *lpszArgv);

// Service control...

void  WINAPI  Service_Ctrl(DWORD dwCtrlCode);

// Stop service...

void  ErrorStopService(LPTSTR lpszAPI);

void  SetTheServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwCheckPoint, DWORD dwWaitHint);

 

// Entry point for service. Calls StartServiceCtrlDispatcher()

// and then blocks until the Service_Main() function returns...

// void wmain(int argc, wchar_t *argv[ ])

void _tmain(int argc, TCHAR *argv[ ])

{

   SERVICE_TABLE_ENTRY ste[ ] = {{TEXT("Myservice"), (LPSERVICE_MAIN_FUNCTION)Service_Main}, {NULL, NULL}};

 

   if (!StartServiceCtrlDispatcher(ste))

   {

      TCHAR error[256];

      wsprintf(error, TEXT("Error code for StartServiceCtrlDispatcher(): %u.\n"), GetLastError());

      OutputDebugString(error);

   }

   else

      OutputDebugString(TEXT("StartServiceCtrlDispatcher() returned!\n"));

}

 

// Called by the service control manager after the call to StartServiceCtrlDispatcher()...

void WINAPI Service_Main(DWORD dwArgc, LPTSTR *lpszArgv)

{

   DWORD ThreadId;

   DWORD t;

   DWORD dwWaitRes;

   // Obtain the name of the service...

   lpszServiceName = lpszArgv[0];

   // Register the service ctrl handler...

   ssh = RegisterServiceCtrlHandler(lpszServiceName, (LPHANDLER_FUNCTION)Service_Ctrl);

   // Create the event to signal the service to stop...

   hStopEvent = CreateEvent(NULL, TRUE, FALSE, "ThisIsMyEvent");

   if (hStopEvent == NULL)

      ErrorStopService(TEXT("CreateEvent()"));

   else

      printf("CreateEvent() looks OK.\n");

   // Insert one-time work that you want to complete before starting.

   for (t = 0; t < 3; t++)

   {

      hThreads[t] = CreateThread(NULL, 0, ThreadProc, (LPVOID)t, 0, &ThreadId);

      if (hThreads[t] == INVALID_HANDLE_VALUE)

         ErrorStopService(TEXT("CreateThread()"));

      else

       printf("CreateThread() looks OK.\n");

       printf("Thread ID: %lu.\n", ThreadId);

   }

 

   // The service has started...

   SetTheServiceStatus(SERVICE_RUNNING, 0, 0, 0);

   OutputDebugString(TEXT("SetTheServiceStatus(), SERVICE_RUNNING.\n"));

   // Main loop for the service...

   while(WaitForSingleObject(hStopEvent, 1000) != WAIT_OBJECT_0)

   {

          printf("Main loop for the service...\n");

       /***************************************************************/

       // Main loop for service.

       /***************************************************************/

   }

 

   // Wait for threads to exit...

   for (t = 1; TRUE; t++)

   {

      if ((dwWaitRes = WaitForMultipleObjects(3, hThreads, TRUE, 1000)) == WAIT_OBJECT_0)

         break;

      else if ((dwWaitRes == WAIT_FAILED) || (dwWaitRes==WAIT_ABANDONED))

         ErrorStopService(TEXT("WaitForMultipleObjects()"));

      else

         SetTheServiceStatus(SERVICE_STOP_PENDING, 0, t, 3000);

   }

 

   // Close the event handle and the thread handle.

   if (!CloseHandle(hStopEvent))

      ErrorStopService(TEXT("CloseHandle()"));

   if (!CloseHandle(hThreads[0]))

      ErrorStopService(TEXT("CloseHandle()"));

   if (!CloseHandle(hThreads[1]))

      ErrorStopService(TEXT("CloseHandle()"));

   if (!CloseHandle(hThreads[2]))

      ErrorStopService(TEXT("CloseHandle()"));

 

   // Stop the service.

   OutputDebugString(TEXT("SetTheServiceStatus(), SERVICE_STOPPED.\n"));

   SetTheServiceStatus(SERVICE_STOPPED, NO_ERROR, 0, 0);

}

 

// Handles control signals from the service control manager...

// WINAPI - using __stdcall convention explicitly instead of __cdecl...

void WINAPI Service_Ctrl(DWORD dwCtrlCode)

{

   DWORD dwState = SERVICE_RUNNING;

   switch (dwCtrlCode)

   {

      case SERVICE_CONTROL_STOP:

         dwState = SERVICE_STOP_PENDING;

         break;

      case SERVICE_CONTROL_SHUTDOWN:

         dwState = SERVICE_STOP_PENDING;

         break;

      case SERVICE_CONTROL_INTERROGATE:

         break;

      default:

         break;

   }

 

   // Set the status of the service.

   SetTheServiceStatus(dwState, NO_ERROR, 0, 0);

   OutputDebugString(TEXT("SetTheServiceStatus(), Service_Ctrl() function\n"));

   // Tell Service_Main thread to stop...

   if ((dwCtrlCode == SERVICE_CONTROL_STOP) || (dwCtrlCode == SERVICE_CONTROL_SHUTDOWN))

   {

      if (!SetEvent(hStopEvent))

         ErrorStopService(TEXT("SetEvent()"));

      else

         OutputDebugString(TEXT("Signal Service_Main() thread.\n"));

   }

}

 

// Thread procedure for all five worker threads...

DWORD WINAPI ThreadProc(LPVOID lpParameter)

{

   int nThreadNum = (int)lpParameter;

   TCHAR szOutput[25];

   while(WaitForSingleObject(hStopEvent, 1000) != WAIT_OBJECT_0)

   {

      // Just to have something to do, it will beep every second.

      Sleep(2000);

      wsprintf(szOutput, TEXT("\nThread %d says Beep\n"), nThreadNum);

      //Send visual to debugger.

      OutputDebugString(szOutput);

   }

   return 0;

}

 

//  Wraps SetServiceStatus()...

void SetTheServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwCheckPoint, DWORD dwWaitHint)

{

    // Current status of the service.

       SERVICE_STATUS ss;

   // Disable control requests until the service is started.

   if (dwCurrentState == SERVICE_START_PENDING)

      ss.dwControlsAccepted = 0;

   else

      ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;

   // Other flags include SERVICE_ACCEPT_PAUSE_CONTINUE

   // and SERVICE_ACCEPT_SHUTDOWN.

   // Initialize ss structure.

   ss.dwServiceType             = SERVICE_WIN32_OWN_PROCESS;

   ss.dwServiceSpecificExitCode = 0;

   ss.dwCurrentState            = dwCurrentState;

   ss.dwWin32ExitCode           = dwWin32ExitCode;

   ss.dwCheckPoint              = dwCheckPoint;

   ss.dwWaitHint                = dwWaitHint;

   // Send status of the service to the Service Controller.

   if (!SetServiceStatus(ssh, &ss))

      ErrorStopService(TEXT("SetServiceStatus()"));

   else

      printf("SetServiceStatus() looks OK.\n");

   return;

}

 

//  Handle API errors or other problems by ending the service and

//  displaying an error message to the debugger.

void ErrorStopService(LPTSTR lpszAPI)

{

   INT t;

   TCHAR   buffer[256]  = TEXT("");

   TCHAR   error[1024]  = TEXT("");

   LPVOID lpvMessageBuffer;

   DWORD  dwWaitRes;

   wsprintf(buffer, TEXT("API = %s, "), lpszAPI);

   lstrcat(error, buffer);

   ZeroMemory(buffer, sizeof(buffer));

   wsprintf(buffer, TEXT("error code = %d, "), GetLastError());

   lstrcat(error, buffer);

   // Obtain the error string.

   FormatMessage(

      FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,

      NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),

      (LPTSTR)&lpvMessageBuffer, 0, NULL);

   ZeroMemory((LPVOID)buffer, (DWORD)sizeof(buffer));

   wsprintf(buffer, TEXT("message = %s"), (TCHAR *)lpvMessageBuffer);

   lstrcat(error, buffer);

   // Free the buffer allocated by the system.

   LocalFree(lpvMessageBuffer);

   // Write the error string to the debugger.

   OutputDebugString(error);

   // If you have threads running, tell them to stop. Something went

   // wrong, and you need to stop them so you can inform the SCM.

   SetEvent(hStopEvent);

   // Wait for the threads to stop.

   for (t = 1; TRUE; t++)

   {

      if ((dwWaitRes = WaitForMultipleObjects(3, hThreads, TRUE, 1000)) == WAIT_OBJECT_0)

         break;

      else if ((dwWaitRes == WAIT_FAILED)|| (dwWaitRes == WAIT_ABANDONED))

         break; // Our wait failed

      else

      {

         SetTheServiceStatus(SERVICE_STOP_PENDING, 0, t, 3000);

      }

   }

 

   // Stop the service.

   SetTheServiceStatus(SERVICE_STOPPED, GetLastError(), 0, 0);

   return;

}

 

Windows services: Building multithreaded services examples

 

Creating an Interactive Process

 

The following code fragment shows how a service application could use the STARTUPINFO structure in a CreateProcess() call to create a process that has access to the user's interactive window station and default desktop. A non-interactive service application could use this technique to interact with the logged on user. The new process could then use a named pipe or some other means of interprocess communication to communicate with the service application. To create an interactive process as shown in this example, a service application must be logged in to the LocalSystem account.

#include <windows.h>

 

STARTUPINFO si;

PROCESS_INFORMATION ProcessInformation;

 

si.cb = sizeof(STARTUPINFO);

si.lpReserved = NULL;

si.lpTitle = NULL;

si.lpDesktop = "WinSta0\\Default";

si.dwX = si.dwY = si.dwXSize = si.dwYSize = 0L;

si.dwFlags = 0;

si.wShowWindow = SW_SHOW;

si.lpReserved2 = NULL;

si.cbReserved2 = 0;

if (CreateProcess(NULL, lpszCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &ProcessInformation))

{

    CloseHandle(ProcessInformation.hProcess);

    CloseHandle(ProcessInformation.hThread);

}

Handling Logoff Events

 

Service applications that interact with the user should be prepared to handle logoff events. When a logoff event occurs, the service application must close all handles to the user's window station and desktop.

The following code snippet sample demonstrates how the message box in the interaction example code should be dismissed at logoff. The ConsoleCtrlHandler() function in this example is a HandlerRoutine() that was specified by a call to the SetConsoleCtrlHandler() function.

BOOL CALLBACK EnumProc(HWND hwnd, LPARAM lParam)

{

    // Send a WM_CLOSE to destroy the window, because DestroyWindow does not work across threads.

    SendMessage(hwnd, WM_CLOSE, 0, 0);

    return TRUE;

}

 

BOOL ConsoleCtrlHandler(DWORD dwCtrlType)

{

    if (dwCtrlType == CTRL_LOGOFF_EVENT && dwGuiThreadId != 0)

    {

        SetThreadDesktop(GetThreadDesktop(dwGuiThreadId));

        EnumThreadWindows(dwGuiThreadId, EnumProc, 0);

    }

    return FALSE;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

 

  1. Structure, enum, union and typedef story can be found C/C++ struct, enum, union & typedef.

  2. For Multibytes, Unicode characters and Localization please refer to Locale, wide characters & Unicode (Story) and Windows users & groups programming tutorials (Implementation).

  3. Windows data types are Windows data types.

  4. Microsoft Visual C++, online MSDN.

  5. Check the best selling C / C++ and Windows books at Amazon.com.

 

 

 

 

 

 

|< Windows Services Programming 2 | Main | Windows Services Programming 4 | Site Index | Download |