本文描述了使用钩子技术在基于Windows程序的MFC应用框架下直接运行状态机应用程序的方法。
一个典型的状态自动机线程如下运作:
SmeRun()
{
do {
等待被传递到该运行的线程的一个外部事件;
if (事件有效)
{
分发这个事件给活动的应用程序并触发状态转换。
} else break;
} while(1);
}
这种运行模式的缺点是我们必须为状态自动机创建一个单独的线程。为了直接用基于程序的MFC应用框架 运行 状态自动机应用程序,状态向导允许你使用 函数MfcHookWnd().将一个应用线程与一个窗口相连。MFC程序可以将一个外部事件传递 或发送到运行在一个线程上的被钩住的窗口,然后引擎 将它分发到活动的状态机应用程序。
MfcHookWnd()通过在Windows下把一个HWND对象作为子类钩住这个HWND对象,即通过在所有当前的程序前面插入它自己的窗口进程,通常是AfxWndProc。可以参考Microsoft Systems Journal (March 1997)获得更详细的信息。
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;
}
CEgnSubclassWnd EngSubclassWnd;
BOOL MfcHookWnd(HWND hWndHooked)
{
if (hWndHooked==NULL || !IsWindow(hWndHooked)) return FALSE;
CWnd *pWnd = CWnd::FromHandle(hWndHooked);
return EngSubclassWnd.HookWindow(pWnd);
}
通常,所有的状态机程序只运行在一个线程上。然而,状态自动机允许将程序分成一些组,每组的程序同时在单独的线程上运行。如下图:用MFC运行状态机说明一组程序可在每组各自的线程上运行。在每个线程上,状态自动机钩住在线程上运行的窗口。
图: 用 MFC 运行状态自动机
下面的例子说明了钩住对话框消息和分发外部事件给Player 状态机应用程序的方法。声明一个应用线程上下文。在对话框打开时,通过SmeInitEngine()初始化状态自动机的线程上下文。在Windows系统下,这个函数将自动的隐式的初始化所给线程上下文的以下信息:
1) SmeSetExtEventOprProc() 通过Windows API GetMessage(), PostThreadMessage() 来创建外部事件处理函数 。
2) SmeSetMemOprProc() 通过new, delete操作来创建动态内存管理函数。
3) SmeSetTlsProc() 通过Windows API TlsGetValue(), TlsSetValue()来创建线程局部存储器程序函数。
然后钩住对话框消息。激活Player应用程序的应用线程。如果有外部事件触发,调用MfcPostExtIntEventToWnd()函数发送一个外部事件给对话框。这个函数将发送如下的WM_EXT_EVNET_ID窗口消息给对话框。
#define WM_EXT_EVENT_ID (0xBFFF)
当状态自动机接收到这个消息,将信息转化为一个外部事件,如果它不是空的就 将它分发到目的应用程序端口;否则将它分发到所有在应用线程上下文上的活动应用程序。
图: Player 应用程序
// 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());
}
被钩住的窗口毁灭时,窗口消息钩子被自动 的移除。
下面的函数对Windows应用程序的发展起了很大作用。
· MfcHookWnd() : 这个 API 函数钩住特定的窗口。
· MfcPostExtPtrEventToWnd() : 这个 API 函数传递一个带有数据块的外部事件给特定的窗口,然后引擎分发它给在线程上下文的活动状态自动机应用程序。MfcPostExtIntEventToWnd() : 这个 API函数发送一个带有数据块的外部事件给特定的窗口,然后引擎分发它给在线程上下文的活动状态自动机应用程序。
· MfcSendExtPtrEventToWnd() :这个API函数传递一个带有两个整型参数的外部事件给特定的窗口,然后引擎分发它给在线程上下文的活动状态自动机应用 程序。
· MfcSendExtIntEventToWnd() : 这个API函数发送一个带有两个整型参数的外部 事件给特定的窗口,然后引擎分发它给在线程上下文的活动状态自动机应用程序。