維尼的蜂巢

RealTime??!! It’s amazing!!!!

MFC的訊息映射 四月 20, 2006

Filed under: VC++/C++/C — kevinlin @ 9:06 下午

原來MFC的主要架構是繼承自CWinApp

他的上層是CWinThread 在上去是CCmdTarget在上去是 CObject

而且它裡面實作了 createwindow跟 訊息回圈(放在Run中 而且可以覆寫)

MFC比較重要的六個要注意的東西 

MFC 程式的初始化過程
RTTI(Runtime Type Information)
Dynamic Creation 動態生成
Persistence 永續留存
Message Mapping 訊息映射
Message Routing 訊息繞行

還蠻特別的是MFC的訊息映射
訊息的視窗有很多種實作的方法,下面就列出常見的三種。
沒有哪一種是最好的,但或許你可以找到一種適合你的。接下去,我們來結合一個基於對話框的應用程式來講解。

第一種方法:使用訊息映射。這是一種最直接、最簡單的方法。
a)    依賴主視窗
最簡單的方法,我們可以在主視窗中定義訊息映射。但有時候我們的主視窗的類別中已經寫了很多程式碼了,這樣會顯的很雜亂,看起來會很不想看因為太亂了;我們希望把這個特定訊息的反應獨立出來。這時,我們需要另外建立一個視窗。我們從CWnd類別中衍生了自己的一個類CMsgWnd。
接著定義訊息映射(假設WM_USER_MSG就是那個特定的消息):
在CMsgWnd.cpp中,加入
BEGIN_MESSAGE_MAP(CMsgWnd, CWnd)
    //{{AFX_MSG_MAP(CMsgWnd)
        // NOTE – the ClassWizard will add and remove mapping macros here.
    //}}AFX_MSG_MAP
    ON_MESSAGE(WM_USER_MSG, OnUserMsg)
END_MESSAGE_MAP()
以及定義訊息反映函數(只是展示接收到了訊息):
LRESULT CMsgWnd::OnUserMsg(WPARAM wParam, LPARAM lParam)
{
    CString  strMsg = “Receive message using message map(CommonWnd): “;
    CString  strParam;
    strParam.Format(“Method %d!", wParam);
    ::AfxMessageBox(strMsg + strParam);
    return 0;
}
在CMsgWnd.h中,加入函數宣告:
afx_msg LRESULT OnUserMsg(WPARAM wParam, LPARAM lParam);
演示時,我們在主視窗類別(CMsgWindowDlg)中定義一個該類別的一個物件,然後如下建立視窗:mMsgWnd.Create(NULL, “Msg", WS_CHILD, CRect(0,0,1,1), this, 0);

b)    不依賴其它視窗
上述的方法,我們建立的視窗作為主視窗的子視窗。但是,如果我們不想牽扯到其他視窗,我們應該怎麼做呢?很簡單,修改基礎類別,從CFrameWnd類衍生。因此呢,我們又生成了一個類CMsgFrameWnd。接下去,定義訊息映射的過程跟上述方法是相似的。
值得注意的是:我們在寫的時後,使用new操作在系統的Heap上建立這個視窗,如下:
mMsgFrameWnd = new CMsgFrameWnd(); // creat window in heap
mMsgFrameWnd->Create(0, “Msg", WS_POPUPWINDOW, CRect(0,0,1,1), 0, 0);
在銷毀這個窗口時,千萬不要使用delete,而要使用DestroyWindow,如下:
if (mMsgFrameWnd)
{
    ::DestroyWindow(mMsgFrameWnd->GetSafeHwnd());
    mMsgFrameWnd = NULL;
}
因為CFrameWnd這個基礎類別實現中會自動地去調用delete this來釋放視窗物件的記憶體。

 

第二種方法:使用SetWindowLong替換視窗處理函數。這也是一種比較方便的方法。
如果我們不想自己定義類別,或者不想使用MFC的視窗類,別更或者我們在DLL中完成我們的工作,根本不關心也很難利用其它的視窗,那麼,就使用Windows API來建立一個訊息視窗吧。參考如下:
// create an overlapped window with an MFC window class
LPCTSTR lpszClass = AfxRegisterWndClass(NULL);
HWND hWnd=::CreateWindow(lpszClass,             // windows class name
            “Msg Hub",                  // window caption
                    WS_OVERLAPPEDWINDOW,            // window style
                    CW_USEDEFAULT, CW_USEDEFAULT,   // position and dimensions
                    CW_USEDEFAULT, CW_USEDEFAULT,
                    NULL,                // owner window handle–NULL is Desktop
    NULL,                // for popup and overlapped windows: window menu handle
    AfxGetInstanceHandle(),            // handle to application instance
    NULL                            // pointer to window-creation data
    );
gWindowMethod2.Attach(hWnd);
// change the window procdure address
gOriginalProc = (WNDPROC) SetWindowLong(hWnd, GWL_WNDPROC,    (LONG)NewWndProc);
窗口創建成功後,使用SetWindowLong給這個窗口指定一個新的消息處理函數,定義為:
LRESULT WINAPI NewWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if (uMsg == WM_USER_MSG)
    {
        CString  strMsg = “Receive message using SetWindowLong: “;
        CString  strParam;
        strParam.Format(“Method %d!", wParam);
        ::AfxMessageBox(strMsg + strParam);
        return 1;
    }
    return CallWindowProc(gOriginalProc,hWnd,uMsg,wParam,lParam);
}
注意在不再使用該訊息視窗時銷毀它:
if (gWindowMethod2.GetSafeHwnd())
{
    HWND  hWnd = gWindowMethod2.Detach();
    ::DestroyWindow(hWnd);
}

第三種方法:使用Hook替換來攔截訊息。這是一種小題大做、「牛刀殺雞」的方法。
與第二種方法相似的是,我們同樣建立一個視窗,如下:
// create an overlapped window with an MFC window class
LPCTSTR lpszClass = AfxRegisterWndClass(NULL);
HWND hWnd=::CreateWindow(lpszClass,             // windows class name
            “Msg Hub",                  // window caption
                    WS_OVERLAPPEDWINDOW,            // window style
                    CW_USEDEFAULT, CW_USEDEFAULT,   // position and dimensions
                    CW_USEDEFAULT, CW_USEDEFAULT,
                    NULL,                // owner window handle–NULL is Desktop
    NULL,                // for popup and overlapped windows: window menu handle
    AfxGetInstanceHandle(),            // handle to application instance
    NULL                            // pointer to window-creation data
    );
gWindowMethod2.Attach(hWnd);
// Install hook for the current thread
glhHook = SetWindowsHookEx(WH_CALLWNDPROC,CallWndProc,AfxGetInstanceHandle(),GetCurrentThreadId());
視窗建立成功後,我們就使用SetWindowsHookEx安裝一個WH_CALLWNDPROC類型的鉤子(Hook)。鉤子函數定義如下:
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode == HC_ACTION)
    {
        CWPSTRUCT * pMsg = (CWPSTRUCT*) lParam;
        if (pMsg->hwnd == gWindowMethod3.GetSafeHwnd() &&
            pMsg->message == WM_USER_MSG)
        {
            CString  strMsg = “Receive message using Hook: “;
            CString  strParam;
            strParam.Format(“Method %d!", pMsg->wParam);
            ::AfxMessageBox(strMsg + strParam);
            return TRUE;
        }    
    }
    return CallNextHookEx(glhHook, nCode, wParam, lParam);
}
注意在不再使用該視窗時銷毀它,並卸載訊息的鉤子:
UnhookWindowsHookEx(glhHook);
if (gWindowMethod3.GetSafeHwnd())
{
    HWND  hWnd = gWindowMethod3.Detach();
    ::DestroyWindow(hWnd);
}

 

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 變更 )

Twitter picture

You are commenting using your Twitter account. Log Out / 變更 )

Facebook照片

You are commenting using your Facebook account. Log Out / 變更 )

Google+ photo

You are commenting using your Google+ account. Log Out / 變更 )

連結到 %s