Stingray® Foundation : Chapter 12 Developing Applications : Basic Architecture
Basic Architecture
This section describes SFL’s overall architecture, explaining how the application, message management, and windowing classes work together. To help illustrate how SFL works, you’ll go through a simple SFL-based application named HelloSFL.
HelloSFL
The HelloSFL application is a bare-bones application that simply shows a window, runs a message loop, and processes window messages. HelloSFL illustrates SFL’s basic facilities and how the framework maps to fundamental SDK-style programming.
As with regular SDK-style programming, in which applications conceptually include both an application portion and a window message handling portion, so does SFL. SFL’s basic architecture consists mainly of an application class and a collection of one or more window classes. The application class manages the message loop and window creation while the window class (or classes) handles the events. Figure 13 shows a high-level view of SFL’s architecture.
Figure 13 – Architecture of the Stingray Foundation Library
The main components of an SFL-based application include a WinMain() function and a single instance of an application class derived from CComModule. The application class holds a message loop class and an initializer class that are passed in as template parameters. You’ll start with SFL’s application class, named CApp.
HelloSFL’s Application
Every Windows application needs a place to store global information such as the main window handle and the instance handle. In addition, COM servers need a place to store global reference counts for the server. ATL provides a class named CComModule for managing details global to the server. SFL’s architecture provides a class named CApp that inherits from CComModule. CApp’s job is to manage the global details for an application. Example 94 shows how HelloSFL declares the CApp class.
Example 94 – HelloSFL.H
#pragma once
class CMainFrame;
////////////////////////////////////////////////////// CHelloSFLApp
typedef CApp < CComModule,
CMessageLoop < CMainFrame>,
CNoopInitializer > CHelloSFLApp;
The declaration of an application class usually occurs in the main header file of the application, as shown in Example 94. Notice that CApp takes three template parameters: a base class, a message loop class, and an initializer class. Deriving from CComModule is an ATL requirement; ATL expects applications to have a single instance of CComModule. To that end, CApp expects as its first template parameter a class derived from CComModule. CApp uses CComModule as the default base class. Second, because CApp is expected to manage the message loop, CApp takes a message loop class as a second parameter. CApp uses the class passed in as a second template parameter to the application’s message loop. The final template parameter is a class that implements initialization steps. Now take a closer look at SFL’s message loop class.
HelloSFL’s Message Loop
Many application architectures hard code the message loop in the framework as part of the base application class. Instead of hard coding the message loop into the framework, SFL’s message loop is componentized and added to the base application class as a template parameter. In most cases, the job of the message loop component is to create the window on the screen and pump messages. The base class for SFL’s message loop classes is named CMessageLoopBase. -CMessageLoopBase is an abstract class—it has two pure virtual functions CreateMainWindow() and DestroyMainWindow() that SFL expects to be implemented by classes derived from CMessageLoopBase. You’ll see how that’s done in a minute.
Example 95 shows the pseudo-code for CMessageLoopBase:
Example 95 – Pseudo-code for SFL’s message loop
class CMessageLoopBase
{
public:
 
int Run()
{
CreateMainWindow()
RunMessageLoop()
DestroyMainWindow()
}
 
protected:
virtual void CreateMainWindow() = 0;
virtual void DestroyMainWindow() = 0;
 
int RunMessageLoop()
{
while (not quit message) {
 
while(PeekMessage() {
if (OnIdle()) {
}
}
 
GetMessage()
PreTranslateMessage()
DispatchMessage()
}
}
 
virtual bool PreTranslateMessage ()
{
::TranslateMessage()
}
 
// override to change idle processing
virtual bool OnIdle ()
{
}
};
SFL has two classes filling in the implementation of the message loops—-CCreateWindowMessageLoop and CCreateDialogMessageLoop. Both template classes accept a window type and a message loop type. The window type parameter names the kind of window to create and the message loop type (which defaults to CMessageLoopBase) determines how the message loop is to run. Example 96 illustrates the declaration of CCreateWindowMessageLoop.
Example 96 – SFL’s CCreateWindowMessageLoop class
template <typename _WindowClass,
typename _Base = CMessageLoopBase>
class CCreateWindowMessageLoop:
public _Base
{
typedef _Base _baseClass;
public:
typedef _WindowClass WindowClass;
 
void CreateMainWindow();
void DestroyMainWindow();
 
protected:
_WindowClass* m_pwndMain;
 
};
For convenience, SFL defines a generic window message creation loop named CMessageLoop. Notice that CMessageLoop is the second template parameter passed into HelloSFL’s CApp class. Example 97 shows the CMessageLoop class.
Example 97 – SFL’s generic message loop
template <typename WindowClass>
class CMessageLoop :
public CCreateWindowMessageLoop<WindowClass,
CMessageLoopDefaultImpl<> >
{
};
Once an SFL-based application declares a class derived from CApp, a global instance of the class must appear once in the application. Furthermore, the single application class must be named “_Module” and be derived from ATL’s -CComModule class. (These are ATL’s requirements.)
The _Module class is usually declared externally in the STDAFX.H file. The declaration needs to appear globally because ATL makes several references to the name _Module. Example 98 shows how the _Module class is defined within HelloSFL’s stdafx.h file.
Example 98 – HelloSFL’s STDAFX.H file
-----------------------------------
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#pragma once
 
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0400
 
#include <atlbase.h>
 
#include <Foundation\apps\Application.h>
using namespace stingray;
using namespace foundation;
 
#include "HelloSFL.h" // CHelloSFLApp class definition
 
extern CHelloSFLApp _Module;
 
#include <atlwin.h>
If you go back and look at the definition of HelloSFL’s CApp class, you’ll notice that the second template parameter, the message loop, requires its own template parameters. SFL’s message loop class is responsible for actually creating the application’s main window. The message loop class needs to know what kind of window to create, so that’s passed in as a template parameter. Next you’ll take a look at HelloSFL’s main window.
HelloSFL’s Main Window
HelloSFL’s main window class named CMainFrame is derived from -CFrameWindowImpl. Example 99 shows how HelloSFL uses SFL’s CFrameWindowImpl.
Example 99 – MAINFRAME.H
// MainFrame.h
 
#pragma once
 
#include <Foundation\Apps\Application.h>
#include <Foundation\Apps\FrameWnd.h>
 
class CMainFrame : public CFrameWindowImpl<CMainFrame, IDR_HelloSFL>
{
public:
typedef CFrameWindowImpl<CMainFrame, IDR_HelloSFL> _BaseClass;
 
BEGIN_MSG_MAP(CMainFrame)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
COMMAND_ID_HANDLER(ID_APP_EXIT, OnAppExit)
CHAIN_MSG_MAP(_BaseClass)
END_MSG_MAP()
 
LRESULT OnAppExit(WORD, WORD, HWND, BOOL& rb)
{
DestroyWindow();
rb = TRUE;
return 0;
}
 
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam,
BOOL& bHandled)
{
HDC hDC = NULL;
 
PAINTSTRUCT ps;
hDC = ::BeginPaint(m_hWnd, &ps);
 
int x = strlen("Salutations, world");
BOOL b = TextOut(hDC, 1, 30, "Salutations, world", x);
 
::EndPaint(m_hWnd, &ps);
bHandled = TRUE;
return 0L;
}
};
CMainFrame’s job is simply to show itself and process window messages. -CMainFrame derives from SFL’s class named CFrameWindowImpl, which in turn derives from ATL’s CWindow class, giving CMainFrame the capability to manage a message map. The two messages the main window cares about include the WM_PAINT message and the WM_COMMAND message (with the command ID ID_APP_EXIT). Notice that CMainFrame’s message map includes these two entries.
Just as the CApp class is a template class, so is CFrameWindowImpl. -CFrameWindowImpl actually takes four template parameters, though only two are shown in the listing above. The first parameter to CMainFrame is the type of derived class and the second parameter is the number identifying the menu resource, the icon for the window, an accelerator table for the window, and a caption string. The other two parameters for CFrameWindowImpl include the window creation flags and the base class. The window creation flags default to the ATL-defined WinTraits class for frame windows, which includes the following creation flags: WS_OVERLAPPEDWINDOW, WS_CLIPSIBLINGS, WS_CLIPCHILDREN, WS_EX_APPWINDOW, and WS_EX_WINDOWEDGE. The base class for -CFrameWindowImpl defaults to ATL’s CWindow, which gives CMainFrame ATL’s basic message handling capabilities through the message maps.
CMainFrame responds to the ID_APP_EXIT command by destroying the window. CMainFrame responds to the WM_PAINT message by creating a paint device context and drawing on it using regular Win32 GDI calls.
If you’re accustomed to SDK-style programming, you’ll notice the basic bones of a Windows application within the window class and the various constituents of the application class. The final link in the chain is WinMain(). Example 100 shows how HelloSFL implements WinMain().
Example 100 – HELLOSFL.CPP
// HelloSFL.cpp : Defines the entry point for the application.
//
 
#include "stdafx.h"
#include "resource.h"
#include <Foundation\Apps\AppImpl.h>
#include <Foundation\Layout\LayoutFactory.h>
#include "MainFrame.h"
 
CHelloSFLApp _Module;
 
BEGIN_LAYOUT_MAP()
END_LAYOUT_MAP()
 
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
_Module.Init(nCmdShow, 0, hInstance);
_Module.Run();
_Module.Term();
return 0;
}
The main job of nearly every version of WinMain() is to create a window, run the message loop until it’s time to quit, and then clean up after everything is finished. HelloSFL’s main C++ source file declares an instance of CHelloSFLApp and names the instance “_Module”. The naming convention is an ATL requirement; ATL expects to see a CComModule-derived object named _Module. The WinMain() function calls the module’s Init() function. HelloSFL’s version of Init() simply calls CComModule’s version of Init() and then the Initializer’s version of Init(). (Remember, the initializer was passed in as a template class.) HelloSFL uses the NoopInitializer, which does nothing. SFL’s other initializers perform operations like initializing the common controls library and initializing COM.
Next, WinMain() calls the module’s Run() function. Run() is actually implemented by the message loop object. Run() calls the virtual function named CreateMainWindow(), which creates a window type passed in as a template parameter. Run then calls RunMessageLoop(), which picks messages off the queue and dispatches them, allowing for idle processing in between. When the application falls out of the message loop, WinMain() calls the module’s Term() function, which calls the initializer’s and ATL’s termination code.
HelloSFL shows how SFL’s basic application architecture works. Next you’ll take a more detailed look at SFL’s architecture. You’ll start by examining the various mutations of SFL’s application classes.