Audiences
This
article was written for UML users or developers of distributed, concurrent and
real-time embedded systems, and computer network programmers. If you have UML
dynamic modeling experiences, it will be better.
Introduction
Many concurrent, distributed or real-time applications have
to tightly co-work with other objects. These 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 window message hooking technology.
Why run state machines based Win32/WinCE applications?
It is natural to use state machines in modeling such
applications. An application that must sequence a series of actions or handle
inputs (responses and indications) differently depending on what state it is in
is often best implemented as a state machine.
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 debug can commence long before a hardware prototype is available. In a simulation
environment, a developer can design the simulator using Windows program as
service providers. These simulators have the identical interface with target
service providers' interface. On target environment, the developer may require
little effort to integrate state machine applications with these service
providers to the real environment.
However, for Windows applications, in particular with the
emerging of WinCE operation system for smart phone (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 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. New hook
registers its own address as 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 execution to the previous handler, eventually arriving to the default one.
Otherwise, the chain is broken. Un-registering the hook means setting the
original procedure as the event handler.
Hooking can be used for many purposes, including debugging
and extending original functionality. However, it is also 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 input. Instead,
they wait for the system to pass input to them. The system passes all input
for an application to the various windows in the application. Each window has
a function, called a window procedure, which 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 type of specific Windows message)
to this hooked window, the state machine engine will get the event data and
then dispatch them to the active state machine applications.
In UML StateWizard (an UML dynamic model tool for
concurrent, distributed real time application development) the procedure is
defined as the following.
1. Hooking window messages
The following function MfcHookWnd() hooks a HWND object by
sub-classing 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:
// External event is triggered. App go to running state.
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, state machine engine allows applications to be divided into
several groups and the applications in each group run on its separate thread at
one time. The following Figure Running State Machined Based Applications
illustrates several groups of applications run at each groups’ respective
thread. At each thread the state machine engine hooks a window which runs at
this thread.
Figure: Running State Machined Based Applications
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.
/********************************************************************
DESCRIPTION: Just like SmeRun(), this function dispatches external event to
applications.
* INPUT:
* OUTPUT: None.
* NOTE:
*******************************************************************/
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;
/* Call hook function on an external event coming. */
if (pThreadContext->fnOnEventComeHook)
(*pThreadContext->fnOnEventComeHook)(SME_EVENT_ORIGIN_EXTERNAL, pEvent);
/* Dispatch it to active applications.*/
DispatchEventToApps(pThreadContext, pEvent);
DispatchInternalEvents(pThreadContext);
/* Free external event if necessary. */
if (pThreadContext->fnDelExtEvent && pEvent)
{
(*pThreadContext->fnDelExtEvent)(pEvent);
// Engine should delete this event, because
// translation of external event will create an internal event.
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 looks like following.
Figure: Player State Machine Application
The following sample shows the way to hook dialog messages
and dispatch external events to 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 state machine engine, this function will automatically initialize
the given thread context with the following information implicitly:
1) SmeSetExtEventOprProc() to setup external event handling
functions through Windows API GetMessage(), PostThreadMessage().
2) SmeSetMemOprProc() to setup dynamic memory management
functions through new, delete operators.
3) SmeSetTlsProc() to setup thread local storage procedure
functions through Windows API TlsGetValue(), TlsSetValue().
It then hooks the dialog messages. Activate the Player
application in the application thread. If an external event triggers, call the
MfcPostExtIntEventToWnd() function to post an external event to dialog. This
function will post WM_EXT_EVNET_ID Windows message to the dialog.
#define WM_EXT_EVENT_ID (0xBFFF)
When the state machine engine receives this message,
translate this message to an external event and dispatch it to the destination
application port if it is not null. Otherwise, dispatch it to all active
applications which run in the same application thread context.
// The application thread context.
SME_THREAD_CONTEXT_T g_AppThreadContext;
// Declare the Player state machine application variable.
SME_DEC_EXT_APP_VAR(Player);
BOOL CSamplePlayerMfcDlg::OnInitDialog()
{
CDialog::OnInitDialog();
....
// Initialize engine.
g_AppThreadContext.nAppThreadID = 0;
SmeInitEngine(&g_AppThreadContext);
// Hook dialog message.
MfcHookWnd(GetSafeHwnd());
SmeActivateApp(&SME_GET_APP_VAR(Player),NULL);
}
void CSamplePlayerMfcDlg::OnButtonPower()
{
MfcSendExtIntEventToWnd(EXT_EVENT_ID_POWER, 0, 0, NULL, GetSafeHwnd());
}
void CSamplePlayerMfcDlg::OnButtonPause()
{
// TODO: Add your control notification handler code here
MfcSendExtIntEventToWnd(EXT_EVENT_ID_PAUSE_RESUME, 0, 0, NULL, GetSafeHwnd());
}
Download Source
Interested by the subject
You may download more information at http://www.intelliwizard.com/ the
official site -the UML IntelliWizard open source project (4). You can get the source
code for this article at /download/869/StateWizard.zip
References
[1] [Computer Networks, Andrew S.Tanenbaum].
[2] This
entry is from Wikipedia, the leading user-contributed encyclopedia.
[3] Microsoft Systems Journal March 1997
[4]
The UML StateWizard project is hosted by
Sourceforge.net