Toward Better Design in Native C++

When you need to write a Windows service application in C++, does it end up looking something like this? If you don’t understand this code, don’t worry we’ll get to that in a minute.

SERVICE_STATUS_HANDLE handle = 0;

SERVICE_STATUS status = { SERVICE_WIN32_OWN_PROCESS,
                          SERVICE_STOPPED,
                          SERVICE_ACCEPT_STOP };

CEvent stopped(true, // manual
               false); // not signaled

void UpdateState(DWORD state)
{
    status.dwCurrentState = state;

    VERIFY(::SetServiceStatus(handle,
                              &status));
}

void WINAPI Handler(DWORD control)
{
    ASSERT(SERVICE_CONTROL_STOP == control);

    UpdateState(SERVICE_STOP_PENDING);

    // Perform shutdown steps here.

    ::WaitForSingleObject(stopped,
                          INFINITE);

    UpdateState(SERVICE_STOPPED);
}

void WINAPI ServiceMain(DWORD,
                        PWSTR*)
{
    handle = ::RegisterServiceCtrlHandler(0,
                                          Handler);
    ASSERT(0 != handle);

    UpdateState(SERVICE_START_PENDING);

    // Perform any startup steps here.

    UpdateState(SERVICE_RUNNING);

    while (SERVICE_RUNNING == status.dwCurrentState)
    {
        // Perform main service function here.
    }

    stopped.Set();
}

int main()
{
    SERVICE_TABLE_ENTRY serviceTable[] =
    {
        { L"", ServiceMain },
        { 0, 0 }
    };

    VERIFY(::StartServiceCtrlDispatcher(serviceTable));
}

I guess the reason so many developers come up with code like this is because many consider the C run-time library and the Win32 API to be the de facto library for C++. Perhaps this is because it has taken so long for true C++ libraries to emerge as standards. Let’s look at the equivalent code in C#.

class EntryPoint
{
    static void Main()
    {
        SampleService service = new SampleService();
        ServiceBase.Run(service);
    }
}

class SampleService : ServiceBase
{
    public SampleService()
    {
        m_stopped = new ManualResetEvent(false);
    }

    protected override void OnStart(string[] args)
    {
        // Perform any startup steps here.

        ThreadPool.QueueUserWorkItem(new WaitCallback(ServiceThread));
    }

    protected override void OnStop()
    {
        // Perform shutdown steps here.

        m_stopping = true;
        m_stopped.WaitOne();
    }

    private void ServiceThread(object state)
    {
        while (!m_stopping)
        {
            // Perform main service function here.
        }

        m_stopped.Set();
    }

    ManualResetEvent m_stopped;
    bool m_stopping;
}

What’s the difference between the C++ and C# examples? The C# example makes use of a library that makes it natural to write a Windows service in C#. The C++ example reverts to procedural C++ akin to C programming. Does that mean that C# is a much more elegant programming language? No. C# is a great language but much of its strength comes from the .NET Framework class library. Is there some magical service that the CLR provides that precludes other environments from exhibiting the same good design in libraries? Certainly not, but the C# language, and many other languages targeting the CLR, have been given a big boost by the availability of a good base class library.

In this piece I plan to use writing a Windows service to illustrate how you can write elegant code in C++ by spending a bit of time thinking about design. Before we get to that let us quickly recap how Windows services work.

Windows Services under the Hood

I’m going to limit this discussion to single-service processes (SERVICE_WIN32_OWN_PROCESS) as it is more familiar to most developers.

When the service control manager (SCM) is asked to start a service, through the StartService function, it starts the process using the CreateProcess function and waits for the process to call the StartServiceCtrlDispatcher function. This function establishes a connection that the SCM can use to send control commands to the service. StartServiceCtrlDispatcher will not return until the service has indicated that it has stopped. Once the connection to the SCM is established, StartServiceCtrlDispatcher creates a secondary thread that is the real starting point for the service. You indicate the address of the thread procedure you wish to use in the SERVICE_TABLE_ENTRY structure passed to StartServiceCtrlDispatcher. This function is typically called ServiceMain. ServiceMain must call RegisterServiceCtrlHandler to register a callback function that the control dispatcher, inside StartServiceCtrlDispatcher, can use to pass control requests to the service. RegisterServiceCtrlHandler also returns a handle that is used in calls to the SetServiceStatus function to update the SCM’s status information about the service.

When the SCM is asked to stop a service, using the SERVICE_CONTROL_STOP control code with the ControlService function, it forward the control request to the control dispatcher which then forwards it to the handler function that was previously registered. The service then indicates that it is stopping by indicating its status as SERVICE_STOP_PENDING using SetServiceStatus. The service then stops what its doing and indicates its status as SERVICE_STOPPED in a final call to SetServiceStatus. At this point, the StartServiceCtrlDispatcher function returns and the main thread unwinds, allowing the process to exit.

In a nutshell, that is how services come to life.

Now onto Design

So about that design. The C++ sample above is problematic because it mixes service-specific functionality with the plumbing needed to communicate with the SCM. It would be a lot more manageable if I could write C++ code like this.

class SampleService : public ServiceBase
{
public:

    SampleService() :
        m_stopping(false)
    {
        // Do nothing
    }

    virtual void Start(DWORD control)
    {
        // Perform any startup steps here.

        ThreadPool::QueueUserWorkItem(ServiceThread,
                                      this);
    }

    virtual void Stop(DWORD control)
    {
        // Perform shutdown steps here.

        m_stopping = true;
        m_stopped.Wait();
    }

private:

    void ServiceThread()
    {
        while (!m_stopping)
        {
            // Perform main service function here.
        }

        m_stopped.Signal();
    }

    ManualResetEvent m_stopped;
    bool m_stopping;

};

int main()
{
    SampleService service;
    ServiceBase::Run(service);
}

Here I am making use of a number of classes that I have written that resemble classes in the .NET Framework. The thing to remember when copying a design to a different programming environment is to be careful not to copy it too literally. Not all concepts translate directly. For example, say you wanted to create a native C++ version of the FileStream class to read and write binary data. It would not make much sense to also create C++ versions of the BinaryReader and BinaryWriter classes with all their ReadXxx methods and Write overloaded methods. The reason is that C++ has a much more natural way of streaming values using operator << and operator >>. Of course if you wanted to support asynchronous I/O, explicit Read and Write methods may well be needed to allow you to pass in an OVERLAPPED structure and wait for completion.

Now back to our service example. The advantage of separating the service plumbing in such a way is evident when you need to be a little creative in the way you run your service. For example, you may want to run the service in a console window for debugging purposes.

Here’s a modified main function that illustrates the possibilities.

int main()
{
    CommandLineParser args;

    if (args.IsFlagPresent(L"debugbreak"))
    {
        ::DebugBreak();
    }

    if (args.IsFlagPresent(L"console"))
    {
        SampleService service;
        service.Start(0);
       
        std::wcout << L"Press the Enter key to stop the service." << std::endl;
        std::wcin.get();

        service.Stop(0);
    }
    else if (args.IsFlagPresent(L"service"))
    {
        SampleService service;
        ServiceBase::Run(service);
    }
}

Well that’s all for today. You can find a full sample service project including a set of classes to help you in building better designed C++ services here. To test the service you’ll need to register it. This can be done using the SC command line tool.

sc create sampleservice binPath= "C:\SampleService\Debug\SampleService.exe /service"

This is just a sample. Don’t expect any support for this code, but I would be happy to hear from you if you found this useful.


© 2004 Kenny Kerr

 

4 Comments

Comments have been disabled for this content.