MFC Application: Adding Service Mode Support

Introduction

Quite often an application has an opportunity to extend its existence on the market but must first meet new requirements, such as being able to run service mode. Imagine that you have developed a huge MFC based server application for years. You’ve already put a great deal of effort into teaching it various tricks and features with a lot of useful functionality but missed one point during all the years of your product’s life; your app is able to run in interactive user session only. Now, your customers are no longer allowed to have endless interactive sessions, therefore they want 24/7, they want remote system reboots with your server to start automatically, without any administrator intervention.

What options do you have now? First, you can fully rewrite your server using some framework or programming language, like C#, that provides the ability to run the required mode. Full rewrite; you think–disaster.

Second, you can split your server into a number of elementary libraries, and reuse those in your new service project. A little better, you think, but not good enough.

Third, you can make your current app to be launched in service session, with tools like the well-known SRVANY, which never makes your app to be a real service…

I have to confess here. Some time ago I was in a situation exactly like this. None of the above appeared to be a viable alternative, as I wanted to make my server become a dual mode app able to run both interactive and service modes. I’m going to give you an idea of the enhancement.

The Server as it Now is

To start this tutorial with something material, let’s see how a simple MFC server might perform a simple function running interactive mode. The function will be logging timestamps to a file with 1 second intervals. This means no rich GUI at all, to let us focus on the server part only.

The server starts, spawns a worker thread that performs iterative logging to the file. Closing the main server window results in stopping the worker thread with further application closure.

#include <afxwin.h>
#include <atltime.h>
#include <share.h>
#include <process.h>

CRITICAL_SECTION g_csLog;
CString g_log;

struct LogInit
{
    LogInit() { InitializeCriticalSection(&g_csLog); }
    ~LogInit() { DeleteCriticalSection(&g_csLog); }
} logInit;

void PutLog(LPCTSTR entry)
{
    EnterCriticalSection(&g_csLog);
    FILE* f = _tfsopen(g_log, TEXT("at+"), _SH_DENYWR);
    if (f)
        _ftprintf(f, TEXT("%sn"), entry);
    fclose(f);
    LeaveCriticalSection(&g_csLog);
}

class CMainFrame: public CFrameWnd
{
    HANDLE  m_hStop;
    HANDLE  m_hWorker;

    DECLARE_MESSAGE_MAP()

public:
    CMainFrame():
        m_hStop(NULL),
        m_hWorker(NULL)
    {}

private:
    static UINT WINAPI WorkerThread(LPVOID pVoid)
    {
        CMainFrame* pThis = (CMainFrame*)pVoid;
        if (pThis)
            _endthreadex(pThis->Worker());
        return 0;
    }

    DWORD Worker()
    {
        HANDLE hStop = m_hStop;
        PutLog(TEXT("n++ Worker started ++"));
        BOOL loop = TRUE;
        while (loop)
        {
            DWORD waitRes = WaitForSingleObject(hStop, 1000);
            switch (waitRes)
            {
            case WAIT_OBJECT_0:
                PutLog(TEXT("Worker stop requested"));
                loop = FALSE;
                break;

            case WAIT_TIMEOUT:
                PutLog(CTime::GetCurrentTime().Format(TEXT("%#c")));
                break;
            }
        }
        PutLog(TEXT("-- Worker finished --n"));
        return 0;
    }

    BOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pcx)
    {
        TCHAR path[MAX_PATH] = {0};
        if (GetModuleFileName(NULL, path, MAX_PATH))
        {
            g_log = path;
            g_log += TEXT(".log");
        }

        m_hStop = CreateEvent(NULL, TRUE, FALSE, NULL);

        UINT threadId;
        m_hWorker = (HANDLE)_beginthreadex(NULL, 0, WorkerThread, this, 0, &threadId);

        return TRUE;
    }

    void CloseWorker()
    {
        SetEvent(m_hStop);
        WaitForSingleObject(m_hWorker, INFINITE);
    }

    void OnClose()
    {
        PutLog(TEXT("Closing..."));
        CloseWorker();
        CWnd::OnClose();
    }

};

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ON_WM_CLOSE()
END_MESSAGE_MAP()

class CMyApp: public CWinApp
{
    BOOL InitInstance()
    {
        CMainFrame* pFr = new CMainFrame;
        if (!pFr->Create(NULL, TEXT("EasyStart")))
        {
            return FALSE;
        }

        m_pMainWnd = pFr;
        pFr->ShowWindow(SW_SHOW);
        pFr->UpdateWindow();
        return TRUE;
    }
};

CMyApp theApp;

The log produced by the server: (to be compared with the service version later).

++ Worker started ++
Sunday, January 22, 2012 18:28:27
Sunday, January 22, 2012 18:28:28
Sunday, January 22, 2012 18:28:29
Closing...
Worker stop requested
-- Worker finished --

The code above seems pretty simple. The application class creates the frame instance. The frame class creates the worker thread and signals it to stop when it gets the WM_CLOSE message. The worker thread waits 1 second to put another log entry. Trivial. But great for our purpose nevertheless. Now it’s time to turn it into a service.

More by Author

Must Read