返回> 网站首页 

[转载]MFC DLL 中的窗口响应PreTranslateMessage

yoours2014-09-22 11:18:04 阅读 1629

简介一边听听音乐,一边写写文章。

PreTranslateMessage是CWnd的虚函数,在这条函数里处理一些按键消息非常方便。但最近参与一个项目,这个项目由主程序和多个插件DLL组成,其中的一个插件DLL是带有界面的,并且以主程序的窗口为父窗口,在这个插件DLL窗口中怎么也响应不了PreTranslateMessage函数。
当按下键盘时,首先主程序的CWinApp对象的PreTranslateMessage会被调用。在这条函数中最重要的函数是WalkPreTranslateTree,其实现如下:
BOOL PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg)  
{  
    ASSERT(hWndStop == NULL || ::IsWindow(hWndStop));  
    ASSERT(pMsg != NULL);  
  
    // walk from the target window up to the hWndStop window checking  
    //  if any window wants to translate this message  
  
    for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))  
    {  
        CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);  
        if (pWnd != NULL)  
        {  
            // target window is a C++ window  
            if (pWnd->PreTranslateMessage(pMsg))  
                return TRUE; // trapped by target window (eg: accelerators)  
        }  
  
        // got to hWndStop window without interest  
        if (hWnd == hWndStop)  
            break;  
    }  
    return FALSE;       // no special processing  
}  

可以看到MFC会从当前窗口一直向最顶层窗口查找并调用CWnd的PreTranslateMessage,直到PreTranslateMessage返回True,或找到最顶层窗口。跟踪发现当在MFC DLL的窗口上按下键盘时,CWnd::FromHandlePermanent 的返回总是NULL,继续跟踪发现问题出在FromHandlePermanent这条函数里。MFC实现如下:
CWnd* PASCAL CWnd::FromHandlePermanent(HWND hWnd)  
{  
    CHandleMap* pMap = afxMapHWND();  
    CWnd* pWnd = NULL;  
    if (pMap != NULL)  
    {  
        // only look in the permanent map - does no allocations  
        pWnd = (CWnd*)pMap->LookupPermanent(hWnd);  
        ASSERT(pWnd == NULL || pWnd->m_hWnd == hWnd);  
    }  
    return pWnd;  
}  
  
CHandleMap* PASCAL afxMapHWND(BOOL bCreate)  
{  
    AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();  
    if (pState->m_pmapHWND == NULL && bCreate)  
    {  
        BOOL bEnable = AfxEnableMemoryTracking(FALSE);  
#ifndef _AFX_PORTABLE  
        _PNH pnhOldHandler = AfxSetNewHandler(&AfxCriticalNewHandler);  
#endif  
        pState->m_pmapHWND = new CHandleMap(RUNTIME_CLASS(CWnd),  
            ConstructDestruct<CWnd>::Construct, ConstructDestruct<CWnd>::Destruct,   
            offsetof(CWnd, m_hWnd));  
  
#ifndef _AFX_PORTABLE  
        AfxSetNewHandler(pnhOldHandler);  
#endif  
        AfxEnableMemoryTracking(bEnable);  
    }  
    return pState->m_pmapHWND;  
}  

原来CWnd::FromHandlePermanent 中调用了afxMapHWND,而该函数返回的是当前模块的模块线程状态中HandleMap,该Map记录了此模块中的HWND对应的CWnd对象的指针。而在调用CWnd的Create函数时会调用MFC会调用CWnd的Attach,该函数会把当前的CWnd指针记录到当前模块的模块线程状态中,代码如下:
BOOL CWnd::Attach(HWND hWndNew)  
{  
    ASSERT(m_hWnd == NULL);     // only attach once, detach on destroy  
    ASSERT(FromHandlePermanent(hWndNew) == NULL);  
        // must not already be in permanent map  
  
    if (hWndNew == NULL)  
        return FALSE;  
  
    CHandleMap* pMap = afxMapHWND(TRUE); // create map if not exist  
    ASSERT(pMap != NULL);  
  
    pMap->SetPermanent(m_hWnd = hWndNew, this);  
  
#ifndef _AFX_NO_OCC_SUPPORT  
    AttachControlSite(pMap);  
#endif  
  
    return TRUE;  

因为DLL中窗口的创建是在一个导出函数中,并在调用CWnd::Create这前调用了AFX_MANAGE_STATE(AfxGetStaticModuleState())来切换模块线程状态,导致该窗口所在的模块线程状态和MFC调用CWinApp::PreTranslateMessage时的不同,所以DLL中的窗口就无法响应PreTranslateMessage函数了。

解决方案:
1、dll导出一条函数 DllPreTranslateMe
BOOL PASCAL DllPreTranslateMessage(MSG *pMsg)  
{  
    AFX_MANAGE_STATE(AfxGetStaticModuleState());  
    return theApp.PreTranslateMessage(pMsg);  
}  

2、在主程序的CWinApp的PreTranslateMessage中直接调用DLL的DllPreTranslateMessage函数。但记住要先调用DLL中的函数。
BOOL CMyApp::PreTranslateMessage(MSG* pMsg)  
{  
    // TODO: Add your specialized code here and/or call the base class  
    if(DllPreTranslateMessage(pMsg))  
        return TRUE;  
    return CWinApp::PreTranslateMessage(pMsg);  
}  
经过以上两步,DLL中的窗口就可以响应PreTranslateMessage了。
微信小程序扫码登陆

文章评论

1629人参与,0条评论