Introduction
Many concurrent, distributed and real-time applications have to tightly co-work with other objects, which are called service providers. A service is formally specified by a set of primitives (operations) available to service users (applications). These primitives describe some action, or report on an action taken by a peer component/entity to be performed. The service primitives can be classified into four categories: request, indication, response, and confirm. [1]
This article describes how to run state machine application framework based Win32/WinCE programs, using the window message hooking technology.
Why Run State Machines Based Win32/WinCE Applications?
It is natural to use state machines in modeling such applications, as an application that must sequence a series of actions, or handle inputs (responses and indications) differently depending on what state it's in, is often best implemented as a state machine.
The state machine application framework is widely used for embedded systems development. Embedded system developers may use Windows platform as a modeling and simulation environment, so that software development and debugging can commence long before a hardware prototype is available. In a simulation environment, a developer can design some simulators using Windows programs as service providers. These simulators will have identical interfaces with the target service providers' interfaces. On the target environment, a developer may take little effort to integrate state machine applications with these service providers in real environment.
However, for Windows applications, in particular with the emerging of the WinCE operating system for smart phones and PDA applications, such methodologies will become increasingly important as systems hardware and software become more complex, and as the systems themselves become more connected and distributed.
Traditional State Machines Implementation
A typical state machine thread works just like the following:
SmeRun()
{
do {
Wait for an external event which is posted to this running thread;
if ( the event is valid)
{
Dispatch this event to active applications and trigger state transitions.
} else break;
} while(1);
}
The disadvantage of this running mode is that we have to create a separate thread for state machine applications.
Hooking Technique in Windows® Environments
Hooking in programming is a technique employing the so called hooks to make a chain of procedures as a handler. Thus, after the handled event occurs, control flow follows the chain in a specific order. A new hook registers its own address as a handler for the event, and is expected to call the original handler at some point, usually at the end. Each hook is required to pass the execution to the previous handler, eventually arriving to the default one, otherwise the chain is broken. Unregistering the hook means setting the original procedure as the event handler.
Hooking can be used for many purposes including debugging and extending the original functionality, but also can be misused to inject (potentially malicious) code to the event handler. [2]
And since Windows-based applications are event-driven, hooking seems to be very interesting. In fact, these applications do not make explicit function calls (such as C run-time library calls) to obtain the input. Instead, they wait for the system to pass input to them. The system passes all the input for an application to the various windows in the application. Each window has a function, called a window procedure, that the system calls whenever it has input for the window. If we hook the window messages, when service providers (other objects) post external events (with a specific type of Windows message) to this hooked window, the state machine engine will get the event data and then dispatches them to the active state machine applications.
In UML StateWizard (a UML dynamic model tool for concurrent, distributed real time application development), the procedure is defined as follows:
1. Hooking Window Messages
The following function MfcHookWnd()
hooks a HWND
object by subclassing it, in the Windows sense, that is by inserting its own window proc ahead of whatever procedure is there currently, usually AfxWndProc
. [3]
class CEgnSubclassWnd: public CSubclassWnd
{
public:
CEgnSubclassWnd();
virtual ~CEgnSubclassWnd();
virtual LRESULT WindowProc(HWND hwnd, UINT msg, WPARAM wp,LPARAM lp);
LRESULT OnExtEvent(MSG& WinMsg);
};
CEgnSubclassWnd::CEgnSubclassWnd()
{
}
CEgnSubclassWnd::~CEgnSubclassWnd()
{
}
LRESULT CEgnSubclassWnd::WindowProc(HWND hwnd, UINT msg, WPARAM wp,LPARAM lp)
{
struct SME_EVENT_T* pExtEvent=NULL;
MSG WinMsg;
WinMsg.hwnd = hwnd;
WinMsg.message =msg;
WinMsg.wParam = wp;
WinMsg.lParam = lp;
LRESULT ret = 0;
switch (msg)
{
case WM_EXT_EVENT_ID:
OnExtEvent(WinMsg);
break;
default:
break;
}
ret = CSubclassWnd::WindowProc(hwnd,msg,wp,lp);
return ret;
}
2. Dispatching Events to State Machines
Usually, all applications (state machines) run at one thread only. However, a state machine engine allows applications to be divided to some groups, and the applications in each group run on separate threads at a time. The following figure, Run State Machines with MFC, illustrates some groups of applications running at each group's respective thread. At each thread, the state machine engine hooks a window which runs on this thread.
When the state machine engine receives a message dedicated to state machine applications, the state machine engine translates this message to an external event and dispatches it to the destination application port if it is not null
; otherwise, it dispatches it to all active applications which run in the same application thread context.
When the hooked window is destroyed, the window message hook is removed automatically.
struct SME_EVENT_T * GetEventFromQueue();
BOOL DispatchInternalEvents(SME_THREAD_CONTEXT_PT pThreadContext);
BOOL DispatchEventToApps(SME_THREAD_CONTEXT_PT pThreadContext,
SME_EVENT_T *pEvent);
LRESULT CEgnSubclassWnd::OnExtEvent(MSG& WinMsg)
{
SME_APP_T *pApp;
SME_THREAD_CONTEXT_PT pThreadContext=NULL;
SME_EVENT_T *pEvent=TranslateEvent(&WinMsg);
if (pEvent==NULL)
return 0;
if (g_pfnGetThreadContext)
pThreadContext = (*g_pfnGetThreadContext)();
if (!pThreadContext) return 0;
pApp = pThreadContext->pActAppHdr;
if (pThreadContext->fnOnEventComeHook)
(*pThreadContext->fnOnEventComeHook)
(SME_EVENT_ORIGIN_EXTERNAL, pEvent);
DispatchEventToApps(pThreadContext, pEvent);
DispatchInternalEvents(pThreadContext);
if (pThreadContext->fnDelExtEvent && pEvent)
{
(*pThreadContext->fnDelExtEvent)(pEvent);
SmeDeleteEvent(pEvent);
}
return 0;
}
CEgnSubclassWnd EngSubclassWnd;
BOOL MfcHookWnd(HWND hWndHooked)
{
if (hWndHooked==NULL || !IsWindow(hWndHooked))
return FALSE;
CWnd *pWnd = CWnd::FromHandle(hWndHooked);
return EngSubclassWnd.HookWindow(pWnd);
}
BOOL MfcUnhookWnd(HWND hWndHooked)
{
if (hWndHooked==NULL || !IsWindow(hWndHooked))
return FALSE;
CWnd *pWnd = CWnd::FromHandle(hWndHooked);
return EngSubclassWnd.HookWindow(pWnd);
}
Sample
Suppose we have a simple player application whose state diagram is as follows:
The following sample shows how to hook dialog messages and dispatch external events to the Player state machine application. Declare an application thread context. When the dialog opens, initialize the state machine engine in the thread context through SmeInitEngine()
. In the Windows edition of the state machine engine, this function will automatically initialize the given thread context, following the information implicitly:
SmeSetExtEventOprProc()
to setup external event handling functions through the Windows APIs GetMessage()
, PostThreadMessage()
. SmeSetMemOprProc()
to setup dynamic memory management functions through the new
and delete
operators. SmeSetTlsProc()
to setup thread local storage procedure functions through Windows APIs TlsGetValue()
, TlsSetValue()
.
and then hooks the dialog messages. Activate the Player application in the application thread. If an external event triggers, it calls the MfcPostExtIntEventToWnd()
function to post an external event to dialog. This function will post the WM_EXT_EVNET_ID
Windows message as below to the dialog:
#define WM_EXT_EVENT_ID (0xBFFF)
When the state machine engine receives this message, it translates this message to an external event, and dispatches it to the destination application port if it is not null
; otherwise, it dispatches it to all active applications which run in the same application thread context.
SME_THREAD_CONTEXT_T g_AppThreadContext;
SME_DEC_EXT_APP_VAR(Player);
BOOL CSamplePlayerMfcDlg::OnInitDialog()
{
CDialog::OnInitDialog();
....
g_AppThreadContext.nAppThreadID = 0;
SmeInitEngine(&g_AppThreadContext);
MfcHookWnd(GetSafeHwnd());
SmeActivateApp(&SME_GET_APP_VAR(Player),NULL);
}
void CSamplePlayerMfcDlg::OnButtonPower()
{
MfcSendExtIntEventToWnd(EXT_EVENT_ID_POWER,
0, 0, NULL, GetSafeHwnd());
}
void CSamplePlayerMfcDlg::OnButtonPause()
{
MfcSendExtIntEventToWnd(EXT_EVENT_ID_PAUSE_RESUME,
0, 0, NULL, GetSafeHwnd());
}
When the hooked window is destroyed, the window message hook is removed automatically.
Interested in the Subject?
You may download more information at the official site the UML StateWizard open source project.[4]
Notes and References
- Computer Networks, Andrew S.Tanenbaum
- Wikipedia, the leading user-contributed encyclopedia
- Microsoft Systems Journal, March 1997
- The UML StateWizard project is hosted by Sourceforge.net
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.