Framework for Writing Services and Multithreaded Applications, Part 1

I recently started a home project developing a set of middleware tools I am hoping to flog on the Internet. Part of this involved writing NT services and lightweight server side multithreaded applications. While doing this, I got a little sidetracked (as I often do when time permits) and decided to knock up a generic framework for things I find myself (re)writing frequently. This includes:

  1. Lightweight classes add stack tracing to debug and release builds. This can be enabled/disabled at will by using an external utility that uses named kernel objects to send signals.
  2. Performance testing classes to write code timings to CSV files for analysis with Excel.
  3. Generic logging/tracing framework and string building classes.
  4. Generic classes/template classes for thread pooling. This includes mechanisms for starting/ending threads in a synchronous manner by using a single worker thread. An implementation of a high performance IO Completion port thread pool.
  5. Classes that wrap kernel objects and critical sections to protect access to shared resources. Not that useful in itself but the classes can also write diagnostics to CSV files to show locking orders to help trace deadlock scenarios. This also includes a read/write lock class for when there is a requirement for many read threads but few write threads.
  6. Simple-to-use macros for ANSI to UNICODE conversion.
  7. Classes/template classes and macros for rapid development of NT services.
  8. I may also add a few developer hints and tips I find useful along the way.

Some of this you may have seen before, but its nice to find things in one place and, as they say, plagiarism is the best form of progress (as Bill probably once said to Xerox). I also like to think I have expanded on these ideas sufficiently to justify the existance of these articles.

You'll probably note that there are much newer developer tools out there than VC++ 6.0 that I have used to develop this. I'm using VC++ 6.0 just because it is my favourite editor, it has a relatively small footprint, and I'm too lazy to convert some VBS macros I use for comments and other useful stuff (written by an extremely talented ex-colleague of mine).

All that will be discussed should be compilable in the latest version of Visual Studio or will require just a minor port.

To get the juices flowing (and to see if anyone is still interested in this kind of stuff), this article will discuss implementing an NT service using my framework with a minimal number of lines of application code. If I see enough interest in this article, I will follow with subsequent articles discussing the other points described above.

I've actually posted a version of this service framework in my "Start a Command As Any User" article I posted yonks ago, but I never really demonstrated usage of the source because this was not the main object of the article. I've recently updated the code for easy of use and installation.

What's Involved in Writing an NT Service?

Without going into too much detail, writing an NT service requires that you write a ServiceMain and ServiceHandler to handle control requests from the service control manager. These will be called on different threads and can be tricky to implement or (at the very least) requires reading a lot of boring documentation.

This framework provides an implementation for all this and allows you to write code for one or more services to be hosted in the executable by deriving from the MbServices::MbServiceEntry class and providing an implementation of the MbRun virtual function.

Mart's hot tip:
You'll probably notice as you peruse my code that I tend to use namespaces frequently. I've fairly recently (over the last few years or so) become a big fan of using namespaces. It's amazing how often you find yourself writing a class with a name you have used many times before. It's also nice to group together a set of logically related classes with a well designed set of namespaces.

After providing this implementation, which will be called to perform the service functionality, you use a bunch of macros to define a map of the services your executable will provide to the service control manager via the dispatch table.

Once the map has been provided, it's just a matter of installing and starting the service(s). I've added a helper class: MbServices::MbServiceStartup to do this.

Here's a trivial 'Hello World' example:

/*
 ******************************************************************
 * MbMain.cpp : Implementation file.
 * Console entry point.  
 * 
 * @version                    V1.0
 * 
 * @author                     Martyn C Brown
 * 
 * @changeHistory
 *   4th January      2007     (V1.0) Creation (MCB)
 ******************************************************************
 */

/*
 ******************************************************************
 * Include all necessary include files
 ******************************************************************
 */
#include "..\MbCommon\MbService.hpp"

/**
 ******************************************************************
 * <P> Derive from MbServices::MbServiceEntry and implement a
 * MbRun(). ; </P>
 *
 * @version     V1.0
 *
 * @author      Martyn C Brown
 *
 * @changeHistory
 *   4th January      2007     (V1.0) Creation (MCB)
 ******************************************************************
 */
class MbGoodbyeWorld : public MbServices::MbServiceEntry
{
public:
   /*
    ***************************************************************
    * Implement the run function.
    ***************************************************************
    */
    virtual void MbRun()
    {
        ::OutputDebugString(_T("Goodbye cruel world!\r\n"));
        ::Sleep(2000);
    }
};

/*
 ******************************************************************
 * The service map needs defining once in a source file to describe
 * the available services.
 ******************************************************************
 */
MbBEGINSERVICEMAP()
    MbSERVICEENTRY5(MbGoodbyeWorld)
MbENDSERVICEMAP()

void _tmain(int argc, TCHAR * argv[])
{       
   /*
    ***************************************************************
    * Use the helper class for service startup.
    * On the command line pass: /i to install, /u to uninstall.
    * Anything else then the exe assumes it is being called by the
    * service control manager and attempts to start the service.
    ***************************************************************
    */
    MbServices::MbServiceStartup service;
    service.MbStart(argc, argv);
}

So, if you ignore all the comments, you have just developed a service in 15 lines of code. I hope you agree that this saves a lot of the leg work in writing all the service code you would normally have to worry about.

Because of how MbServices::MbServiceStartup::MbStart() is implemented, you can install the service by calling it from the command line and passing /i as a switch...

Start the services applet and you should see the new service...

Start the service and DebugView to monitor the debug stream and you'll see the output from the service...

There are a few shortcomings in this service. The main one is that this is little control over the name of the service (which is just the name of the class derived from MbServices::MbServiceEntry) and there is no helpful description of the service that you can view in the services applet.

Mart's hot tip:

Personally, I find pre-compiled headers a pain. They just don't always work and you end up spending an hour to find a bug that isn't there but is a side affect from incorrectly generated code related to pre-compiled headers being out of line.

I prefer to rebuild a project each time from scratch (I don't even tend to trust incremental linking too much either). If you are working on a network drive, disabling these options can result in a much longer build time so I always change the location of the generated intermediate files to a local C drive.

I generally base the name of this directory on my project. For example, C:\temp\mcbServiceLib\Debug

You'll be suprised how much quicker this makes your build.

Let's have a look at another example service that allows you more control over the service name and description. This is done by using a different MbSERVICEENTRYx macro to describe the service to the service control manager (for details, see the comments in the source). Just for fun, you will also host two services in the exe...

/*
 ******************************************************************
 * MbMain.cpp : Implementation file.
 * Console entry point.  
 * 
 * @version                       V1.0
 * 
 * @author                        Martyn C Brown
 * 
 * @changeHistory
 *   30th December    2006        (V1.0) Creation (MCB)
 ******************************************************************
 */

/*
 ******************************************************************
 * Include all necessary include files
 ******************************************************************
 */
#include "..\MbCommon\MbService.hpp"

/**
 ******************************************************************
 * <P> Derive from MbServices::MbServiceEntry and implement a
 * MbRun().  </P>
 *
 * @version     V1.0
 *
 * @author      Martyn C Brown 
 *
 * @changeHistory  
 *   30th December    2006       (V1.0) Creation (MCB)
 ******************************************************************
 */
class MbFazor : public MbServices::MbServiceEntry
{
public:
   /*
    ***************************************************************
    * Implement the run function.
    ***************************************************************
    */
    virtual void MbRun() 
    {
        ::OutputDebugString(_T("Fazors are cool!\r\n"));
        ::Sleep(2000);
    }
};

/**
 ******************************************************************
 * <P> Derive from MbServices::MbServiceEntry and implement a
 * MbRun().  </P>
 *
 * @version     V1.0
 *
 * @author      Martyn C Brown 
 *
 * @changeHistory  
 *   30th December    2006      (V1.0) Creation (MCB)
 ******************************************************************
 */
class MbGixxer : public MbServices::MbServiceEntry
{
public:
   /*
    ***************************************************************
    * Implement the run function.
    ***************************************************************
    */
    virtual void MbRun() 
    {
        ::OutputDebugString(_T("Gixxers are the best!\r\n"));
        ::Sleep(2000);
    }
};
/*
 ******************************************************************
 * The service map needs defining once in a source file to describe
 * the available services.
 ******************************************************************
 */
MbBEGINSERVICEMAP()
    MbSERVICEENTRY2(MbFazor, _T("MartsFazor"), _T("Martyn's Fazor"),
        _T("998cc Silver, slight scratch damage due to careless
            driver"))

    MbSERVICEENTRY2(MbGixxer, _T("MartsGixxer"), _T("Martyn's Gixxer"),
        _T("998cc Blue and white, two bald tyres"))
MbENDSERVICEMAP()

void _tmain(int argc, TCHAR * argv[])
{       
   /*
    ***************************************************************
    * Use the helper class for service startup.
    * On the command line pass: /i to install, /u to uninstall.
    * Anything else then the exe assumes it is being called by the
    * service control manager and attempts to start the service.
    ***************************************************************
    */
    MbServices::MbServiceStartup service;
    service.MbStart(argc, argv);
}

Now, if you install the services using /i...

...and refresh the services applet, you'll see more verbose details...

Mart's hot tip:

You may disagree, but when I'm writing a re-usable class I like to just be able to include it as a header file and not have to dig around for the .cpp. Adding the .cpp is fine if there are only a couple to add, but when you have many source files it can be a pain.

To work around this, one popular approach is to create a static code library, and then use a pragma in a header file to automatically include the lib...

#pragma comment(lib, "..\\Bin\\MbServiceLibDbgU.lib")

This approach works fine most of the time, and this is how I have developed the service code, but can be painful when you have many variations of your code (for example, UNICODE versions, ANSI versions, DEBUG versions, RELEASE versions, and so on). Then, you end up with a horrible nested precompilation steps with many variations...

#if defined(_DEBUG) || defined(DEBUG)
   #if defined(UNICODE) || defined(_UNICODE)

      #if defined(_DLL)
         #if defined(_MT)
            #pragma comment(lib,
               "..\\Debug\\MbStackTraceLibDbgUDllMT.lib")
         #else
            #pragma comment(lib,
              "..\\Debug\\MbStackTraceLibDbgU.lib")
         #endif //defined(_MT) || defined(_MT)
      #else //defined(_MT)
      #endif //defined(_MT)
   #endif
#else //defined(UNICODE) || defined(_UNICODE)

   #if defined(_DEBUG) || defined(DEBUG)
      #pragma comment(lib, "..\\Debug\\MbStackTraceLib.lib")
   #else //defined(UNICODE) || defined(_UNICODE)
      #pragma comment(lib, "..\\Release\\MbStackTraceLib.lib")
   #endif //defined(UNICODE) || defined(_UNICODE)

#endif //defined(UNICODE) || defined(_UNICODE)

You also have to ensure your library builds all the different configurations.

You also have the worry about versioning—what if you find a bug in your library? Obviously, you need to fix the bug and recompile. Do you then want to recompile everything that uses the library? Otherwise, you will need to maintain different versions of the library so you can recompile the different projects.

Another approach I like to use when possible to resolve some of these issues is to write re-usable classes as template classes. I'll then create a single specialisation of the template (the actual parameter used not important and unused) so that the code is all contained within the header file. This can also alleviate some of the versioning issues by taking a copy of the header file (or shared code directory) per project.

I've done this in this project with MbWinErrorImpl<>. This example is a simple class to convert windows errors codes into string descriptions and manage the associated memory...

template <class nNotUsed> class MbWinErrorImpl
{
public:
   /*
    *****************************************************
    * Return a windows error formatted as a string.
    *****************************************************
    */
    LPTSTR MbGetWinError(DWORD dwErr = 0);
    ...
};

template <class nNotUsed>
LPTSTR MbWinErrorImpl<nNotUsed>::MbGetWinError(DWORD dwErr)
{
    ...
    return m_lpMsg;
}

typedef mcbWinErrorImpl MbWinError;
Caveat: You have to be careful here to implement the function outside of the class itself so that the code is not generated as inline.

Having done this, I then can contain all the source code in one header file and just provide the #include to get the required functionality.

Through the course of these articles, I'll use this technique quite a lot. You could argue that, in a purist form, this isn't a correct use of template classes. If you don't like this approach, feel free to split the files into .cpp/.hpp combinations.

Framework for Writing Services and Multithreaded Applications, Part 1

How the Framework Works

So, what is happening under the covers?

The bones of hooking up your code to the service control manager is achieved by the service map you describe with the MbSERVICEENTRYx() macros. These macros describe the dispatch map that is passed to the Windows API ::StartServiceCtrlDispatcher().

The macros instantiate a template class MbServiceCtrl<> that implements a ServiceMain (MbServiceCtrl::MbServiceMain) and ServiceHandler (MbServiceCtrl::MbServiceHandler) functions to talk to your service.

Effectively, these functions pass on calls to the base class MbServiceEntry from which your service implementation class derives.

The service handler function implemented as MbServiceEntry::MbServiceHandler receives notifications from the service control manager when a change in state is requested (for example, when someone attempts to pause or stop the service). Internally, this then uses Windows messaging to pass the request onto the thread running the service main function implemented in MbServiceEntry::MbServiceMain.

MbServiceEntry::MbServiceMain sits in a message loop checking for the custom windows messages generated from MbServiceEntry::MbServiceHandler. When a stop or pause request is found in the message queue, a virtual function will be called to stop (MbServiceEntry::MbStop()) or pause (MbServiceEntry::MbPause()) the service. You can override these to implement the behaviour required to stop or pause the service.

If no Windows message is in the queue, MbServiceEntry::MbServiceMain() will repeatedly call your MbRun() override to do whatever you want the service to do.

Incidently, the MbRun() override will only be called if the service's current state in running. In other words, if all the activity of your service occurs in MbRun(), you get service pause functionality for free.

I'll repeat the above point because I think it's important. The service main function will repeatedly call MbRun() when it is not processing Windows messages to change service state. This means that, while your MbRun() is being called, the service main function will not respond to messages from the service handler.

The upshot of this is that, if you perform a lengthy operation in MbRun() and someone attempts to stop your service, you may see errors in the services applet (for example, the service failed to stop within the time specified).

To resolve this, you can give an indication to the service control manager of how long your service will take to change state by overriding one of the following:

  • MbServiceEntry::MbGetStartWaitHint()
  • MbServiceEntry::MbGetStopWaitHint()
  • MbServiceEntry::MbGetPauseWaitHint()
  • MbServiceEntry::MbGetContinueWaitHint()

The exact one you use depends on whether your lengthy processing occurs when starting, stopping, pausing, or continuing (respectively). You also can, in your implementation of MbServiceEntry::MbStart(), MbServiceEntry::MbStop(), MbServiceEntry::MbPause(), and MbServiceEntry::MbContinue() functions, request more time than you initially hinted at by calling MbServiceEntry::MbUpdateCurrentWaitHint(dwTime).

By using the above techniques, you can stop errors from being generated if your service is taking a while to complete something because you are programmatically handholding with the service control manager and requesting more time.

However, the service will still look unresponsive to the end user in this scenario and they will be shouting at the screen while watching those little blue progress bars Microsoft does so well.

Ideally, you should code MbRun() to return within a reasonably short time frame (2 or 3 seconds) so your service looks responsive to the user. So, how do you perform a lengthy operation here?

  1. One way would be to make MbRun() re-entrant to remember state so that when called again you can pickup where you left off. The problem with this approach is that the code could get messy and you may be talking to something external (for example, a long running COM server) that you have no control over.
  2. Another option I would suggest is to do little in MbRun() and instead control worker threads using the overrides provided to start (MbStart()) and stop (MbStop()) the threads.

    While I am on this note, you need to make sure that the MbRun() override yields its timeslice if you intend to do this. Otherwise, you will be repeatedly called when there are no requests from the service control manager and waste cpu.

    This is easily achieved with a ::Sleep(), as I have used in the examples.

    This now becomes a whole new ball game because you have to start worrying about thread pools and synchronisation issues. I'm going to save this for another article if there is enough interest, because my fingers are bleeding.

How to Include the Framework in Your Projects

To use this in your own project, you will just need to include the header file: "..\MbCommon\MbService.hpp". A pragma within here will automatically suck in the static code library which implements the service framework.

I'm using a directory tree that keeps all the binaries in the "<workspace>\Bin" directory and the pragma's within MbService.hpp. Look in this directory for the static code libraries.

I differentiate between debug and release versions by postfixing the output debug binaries with Dbg. For example, MbServiceLibDbg.lib and MbServiceLib.lib.

You also will note that the project also includes build configurations for UNICODE. For UNICODE, I postfix the output binaries with U. For example, MbServiceLibU.lib and MbServiceLibDbgU.lib.

You will need to use a similar directory structure to use MbService.hpp as it stands. (Otherwise, you will just need to change the path to the static code libraries to wherever you want.)

For my services, I've created dependancies on my service library, This was for ease of development; you won't need to do this unless you plan to change the library.

Don't forget to ensure your project uses a multithreaded runtime library—services are always multithreaded.

Happy new year!



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

  • IBM Worklight is a mobile application development platform that lets you extend your business to mobile devices. It is designed to provide an open, comprehensive platform to build, run and manage HTML5, hybrid and native mobile apps.

  • On-demand Event Event Date: October 23, 2014 Despite the current "virtualize everything" mentality, there are advantages to utilizing physical hardware for certain tasks. This is especially true for backups. In many cases, it is clearly in an organization's best interest to make use of physical, purpose-built backup appliances rather than relying on virtual backup software (VBA - Virtual Backup Appliances). Join us for this webcast to learn why physical appliances are preferable to virtual backup appliances, …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds