SFL 2.0: Service Framework Library for Native Windows Service Applications, Part II

Inevitable Introduction

In my previous article, Part I, I about service application styles existing in the Windows world; I called them Windows NT style and Windows 2000 style respectively. I have to warn you about these style names; they are unofficial (you never will find them in MSDN), and I needed to invent some names for easier topic comprehension—for myself first. Well, after making this point clear, it's time to get into this a bit deeper and see why this point is so important and why NT and 2000 are mentioned in this context.

The Windows NT system was completely silent in respect to notifying user-level applications about hardware-level events. For this reason, service applications have had correspondingly simple APIs that provided only user-to-service communication abilities.

But since Windows 2000, some absolutely new functionality has been introduced for service applications—in brief, they had become able to process hardware environment changes and device notifications, and Windows provides several new control codes for this purpose sent to services from the system kernel.

Once this new functionality obviously did not match with previously written service applications, the Windows API was added with extended versions of handler-aware API entry—RegisterServiceCtrlHandlerEx. And since Windows 2000, two service styles—the legacy Windows NT service and the modern Windows 2000 one—became equally supported.

Now, the service that registers the old-style handler function is considered a legacy application that never requires new-style hardware event notification. Nevertheless, this service application can be launched in any Windows NT-based system—NT4/2000/XP/2003/Vista—because of Windows system backward compatibility.

And, the service that registers the extended handler function and claims itself accepting hardware environment notifications is considered a new-style service, and the Windows system notifies it about the requested type of hardware environment changes. Of course, this type of service application is totally compatible with Windows 2000/XP/2003/Vista versions, but naturally becomes incompatible with the Windows NT4 system; therefore, it cannot be launched in that type of system.

Note: For detailed information regarding the mentioned old-fashioned and modern service behaviors, please refer to MSDN articles Handler, HandlerEx, RegisterServiceCtrlHandler, and RegisterServiceCtrlHandlerEx.

Digressing for a moment, it worth mentioning an interesting fact that ATL Service registers the old-style handler function; therefore, this service becomes able to operate in the Windows NT system as well. For the same reason, it never can be notified about network and hardware configuration changes—of course, I mean the standard way that Windows does this for modern-style services. This consideration must be taken into account when making decisions about service implementation, or when advising other people on the question, with no doubt.

But, let me get back to SFL and show you how it solves the service style issue.

Windows 2000-Style SFL Application

To be sure the concept really works, you can create a service that requires some kind of device notification. The first thought that crossed my mind was a CD/DVD-ROM volume mounting event, so you'll try that. Because the only thing that matters in the case is the service style, the application part will remain similar to previous article's demo application.

Note: Actually, the application part does differ slightly from the previous Part I demo. The SFL_SERVICE_ENTRY(CDeviceService, IDS_DEVICE_SERVICE) macro is used in the application service map unlike the previously used SFL_SERVICE_ENTRY2. Please don't be confused by not seeing the service name here; this macro just implies that the service name is kept as a resource string with the IDS_DEVICE_SERVICE identifier.

And now, you can focus on the service class of the current demo.

#pragma once

class CDeviceService: public CServiceBaseT< CDeviceService,
   SERVICE_ACCEPT_STOP |
   SERVICE_ACCEPT_HARDWAREPROFILECHANGE >
{
   SFL_DECLARE_SERVICECLASS_FRIENDS(CDeviceService)

   SFL_BEGIN_CONTROL_MAP_EX(CDeviceService)
   SFL_HANDLE_CONTROL_STOP()
   SFL_HANDLE_CONTROL_EX( SERVICE_CONTROL_DEVICEEVENT,
                          OnDeviceChange )
   SFL_END_CONTROL_MAP_EX()

   DWORD OnStop(DWORD& dwWin32Err, DWORD& dwSpecificErr,
                BOOL& bHandled);
   DWORD OnDeviceChange(DWORD& dwState,
                        DWORD& dwWin32Err,
                        DWORD& dwSpecificErr,
                        BOOL&  bHandled,
                        DWORD  dwEventType,
                        LPVOID lpEventData,
                        LPVOID lpContext);
   void LogEvent(DWORD dwEvent, LPVOID lpParam);

#if defined(_MSC_VER) && (_MSC_VER < 1300)
public:
#endif
   LPVOID GetServiceContext() { return this; }
   BOOL InitInstance(DWORD dwArgc, LPTSTR* lpszArgv,
                     DWORD& dwSpecificErr);

   CDeviceService();
   virtual ~CDeviceService();

private:
   HDEVNOTIFY m_hDevNotify;
   LPTSTR     m_logfile;
};

You easily can see that the service class structure is recognizable: The service class friends declaration macro (this will be explained in the next article), service control map (though of the new extended style) along with control code handlers, and some auxiliary stuff, such as GetServiceContext and InitInstance, that were never introduced before.

Note: The GetServiceContext function is never used in this sample and is provided just for reference. Its purpose and possible usage will be explained in the next article.

The really important points to focus on, and which actually make the service able to process device notifications, are these two:

  • Service accept flag: SERVICE_ACCEPT_HARDWAREPROFILECHANGE
  • Extended version of service control map: SFL_BEGIN(END)_CONTROL_MAP_EX

The extended version of the control map implements the modern style HandlerEx handler function and makes the framework register it with the appropriate API, RegisterServiceCtrlHandlerEx.

The mentioned accept flag informs the Windows system that the service must be notified about hardware configuration changes and device events.

SFL 2.0: Service Framework Library for Native Windows Service Applications, Part II

So far, all seems rather straightforward, but one point must be mentioned specially. The service, to be able to get notified, must perform an additional initialization routine that gives the Windows system information about what exact notification is expected. Here, InitInstance enters the scene. In case your service class implements the InitInstance member function, the framework calls it while executing ServiceMain.

Note: If you are using Microsoft Visual C++ 6, please remember to make InitInstance (and GetServiceContext also) public because of some VC++6 specifics. You can see, I solve this by checking the _MSC_VER value.
BOOL CDeviceService::InitInstance(DWORD   dwArgc,
                                  LPTSTR* lpszArgv,
                                  DWORD&  dwSpecificErr)
{
   TCHAR cdDrive[] = _T("\\\\.\\A:\\");
   DWORD drives = GetLogicalDrives();

   for( int i = 0; i < 27; i++ )
   {
      if( i == 26 )
         return FALSE;    // no cd-rom
      if( (drives % 2) && (DRIVE_CDROM ==
                           GetDriveType( cdDrive + 4 )) )
      {
         cdDrive[6] = _T('\0');
         break;
      }
      cdDrive[4]++;
      drives >>= 1;
   }

   DEV_BROADCAST_HANDLE nf = {0};
   nf.dbch_size = sizeof(nf);
   nf.dbch_devicetype = DBT_DEVTYP_HANDLE;
   nf.dbch_handle     = CreateFile( cdDrive, GENERIC_READ,
                                    FILE_SHARE_READ, NULL,
                                    OPEN_EXISTING, 0, NULL );

   if( INVALID_HANDLE_VALUE == nf.dbch_handle )
   {
      dwSpecificErr = GetLastError();
      CErrCodeMsg err(dwSpecificErr);
      MessageBox( NULL, err.GetString(),
         TEXT("DeviceService"),
         MB_OK | MB_SERVICE_NOTIFICATION );
      return FALSE;
   }

   m_hDevNotify = RegisterDeviceNotification( m_status.GetHandle(),
      (LPVOID)&nf,
      DEVICE_NOTIFY_SERVICE_HANDLE );
   if(!m_hDevNotify)
   {
      dwSpecificErr = GetLastError();
      CErrCodeMsg err(dwSpecificErr);
      MessageBox( NULL, err.GetString(),
         TEXT("DeviceService"),
         MB_OK | MB_SERVICE_NOTIFICATION );
      return FALSE;
   }

   LogEvent(0, NULL);
   return TRUE;
}

Well, the code iterates through the system drives and detects the first CD-ROM volume available in the system. Then, the code opens a handle for this volume and registers it for notification.

Note: This procedure does not take very much time, so there's no special code that handles the lengthy initialization operations. But, once your service requires a sufficiently longer procedure—in fact, longer that 30 seconds—you must take care about processing service check points (see the SERVICE_STATUS MSDN article regarding the dwCheckPoint member). I will cover this point later in Part IV.

Here is one point you must understand completely: To let your service start, InitInstance must return TRUE; otherwise, the framework will stop the service.

The extended map is compatible with regular control entry macros as well as with new extended ones. As you can see, this extended macro binds the SERVICE_CONTROL_DEVICEEVENT control code to a handler function with a prototype you never met before. In fact, this handler looks similar to the SFL regular handler function, but has three extra parameters.

DWORD OnDeviceChange(DWORD& dwState,
                     DWORD& dwWin32Err,
                     DWORD& dwSpecificErr,
                     BOOL&  bHandled,
                     DWORD  dwEventType,
                     LPVOID lpEventData,
                     LPVOID lpContext);

Okay, if you take a look at the HandlerEx protype, you no doubt recognize those three. So, this kind of handler just provides a notification information in addition to the standard SFL handler parameters, but the handler implementation style differs too. Take a look at one.

DWORD CDeviceService::OnDeviceChange(DWORD& dwState,
                                     DWORD& dwWin32Err,
                                     DWORD& dwSpecificErr,
                                     BOOL&  bHandled,
                                     DWORD  dwEventType,
                                     LPVOID lpEventData,
                                     LPVOID lpContext)
{
   LogEvent(dwEventType, lpEventData);
   bHandled = TRUE;
   return NO_ERROR;
}

The fact of handling a notification must be confirmed by setting the bHandled to TRUE—exactly as a standard SFL handler does. But the return code differs; NO_ERROR is returned instead of the service status, as it is done with a standard handler. The return value in the extended handler must follow the return code rules defined for the HandlerEx function.

Concluding this Part . . .

That's all about all the new application specifics. In all other aspects, this service application is similar to its sibling MinSvc2_demo (see Part I). Here are the service installation/de-installation commands you can use to test this demo.

sc.exe create MidSvc_Device binPath= "<full_app_path>"
sc.exe delete MidSvc_Device
Warning: Please remember, all command spaces matter! And, make sure you placed the actual fully qualified path to your executable instead of the corresponding placeholder.

After running the installed service, please insert a disk into your target CD/DVD-ROM drive (remember, this sample service finds the very first one in the list of drives) and after a pause, sufficient for volume mounting, eject the disk. After stopping the service, please find the log file MidSvc_device.log next to the executable module. It must contain something similar to the following:

04.11.2007-21:38:01 * START *
04.11.2007-21:38:40 * EvType = 32774 (DBT_CUSTOMEVENT),
   EventGUID = GUID_IO_MEDIA_ARRIVAL
04.11.2007-21:38:52 * EvType = 32774 (DBT_CUSTOMEVENT),
   EventGUID = GUID_IO_MEDIA_REMOVAL 
04.11.2007-21:39:19 * STOP *

What's Next?

The next topic—in Part III—will cover the SFL architecture, showing the library class hierarchy and giving a description of framework entry points other than the InitInstance you already met in this part.



Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds