线程与消息.docx
- 文档编号:9824620
- 上传时间:2023-02-06
- 格式:DOCX
- 页数:17
- 大小:22.05KB
线程与消息.docx
《线程与消息.docx》由会员分享,可在线阅读,更多相关《线程与消息.docx(17页珍藏版)》请在冰豆网上搜索。
线程与消息
在前面我们研究了使用AFX_MANAGE_STATE(AfxGetStaticModuleState())进行DLL间的资源切换,以及工作线程中创建Windows消息循环的原理,以为就可以搞定一切类似问题了…但是请看以下代码
DWORDCTestMFCDlg:
:
ThreadFunc(PVOIDyy)
{
CAboutDlgdlg;
dlg.DoModal();
return0;
}
voidCTestMFCDlg:
:
OnOK()
{
:
:
CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)ThreadFunc,NULL,NULL,NULL);
}
在VC++6.0上编译运行出现以下ASSERT。
voidCWnd:
:
AssertValid()const
{
……
CHandleMap*pMap=afxMapHWND();
……
CObject*p;
ASSERT((p=pMap->LookupPermanent(m_hWnd))!
=NULL||
(p=pMap->LookupTemporary(m_hWnd))!
=NULL);
ASSERT((CWnd*)p==this); //mustbeus
MFC有一个全局的Hash表(通过afxMapHWND()获得),用于把HWND句柄与MFC的封装对象CWnd进行关联,这样就可以通过CWnd:
:
FromHandle()等函数把CWnd对象Attach到一个已有的HWND句柄上,利用MFC的封装函数可以简化对HWND的直接操作。
很显然,这里的Assert是因为CWnd对象根据自身的窗口句柄(m_hWnd)从Hash表里找到CWnd对象指针与对象的本身(this)并不相同!
这说明,CWnd对象创建时注册到的Hash表与目前检索的Hash表并不是同一个。
为什么会是这样的呢?
CHandleMap*PASCALafxMapHWND(BOOLbCreate)
{
AFX_MODULE_THREAD_STATE*pState=AfxGetModuleThreadState();
if(pState->m_pmapHWND==NULL&&bCreate)
{
…….
pState->m_pmapHWND=newCHandleMap(RUNTIME_CLASS(CTempWnd),offsetof(CWnd,m_hWnd));
……
}
returnpState->m_pmapHWND;
看来这个Hash表跟AfxGetModuleThreadState()有关,继续
AFX_MODULE_THREAD_STATE*AFXAPIAfxGetModuleThreadState()
{
returnAfxGetModuleState()->m_thread.GetData();
}
AFX_MODULE_STATE*AFXAPIAfxGetModuleState()
{
_AFX_THREAD_STATE*pState=_afxThreadState;
AFX_MODULE_STATE*pResult;
if(pState->m_pModuleState!
=NULL)
{
//threadstate'smodulestateservesasoverride
pResult=pState->m_pModuleState;
}
else
{
//otherwise,useglobalappstate
pResult=_afxBaseModuleState.GetData();
}
ASSERT(pResult!
=NULL);
returnpResult;
}
那么_afxThreadState是什么呢?
EXTERN_THREAD_LOCAL(_AFX_THREAD_STATE,_afxThreadState)
class_AFX_THREAD_STATE:
publicCNoTrackObject
{
public:
_AFX_THREAD_STATE();
virtual~_AFX_THREAD_STATE();
//overrideform_pModuleStatein_AFX_APP_STATE
AFX_MODULE_STATE*m_pModuleState;
AFX_MODULE_STATE*m_pPrevModuleState;
#defineTHREAD_LOCAL(class_name,ident_name)\
AFX_DATADEFCThreadLocal
#defineEXTERN_THREAD_LOCAL(class_name,ident_name)\
externAFX_DATATHREAD_LOCAL(class_name,ident_name)
分析的结果是externCThreadLocal<_AFX_THREAD_STATE>_afxThreadState。
template
classCThreadLocal:
publicCThreadLocalObject
{
AFX_INLINETYPE*GetData()
{
TYPE*pData=(TYPE*)CThreadLocalObject:
:
GetData(&CreateObject);
ASSERT(pData!
=NULL);
returnpData;
}
AFX_INLINEoperatorTYPE*()
{returnGetData();}
AFX_INLINETYPE*operator->()
{returnGetData();}
staticCNoTrackObject*AFXAPICreateObject()
{returnnewTYPE;}
可以看出来了,_afxThreadState是一个全局的对象。
通过该对象可以获得_AFX_THREAD_STATE对象,后者是线程相关的。
CThreadLocalObject的代码不再分析,大概就是检查当前的线程私有数据,如果有则返回,否则创建新的对象(即_AFX_THREAD_STATE)。
继续看AfxGetModuleState(),大致的意思是获取与当前线程相关联的AFX_MODULE_STATE对象,如果没有则获取该进程的缺省AFX_MODULE_STATE对象。
PROCESS_LOCAL(_AFX_BASE_MODULE_STATE,_afxBaseModuleState)
class_AFX_BASE_MODULE_STATE:
publicAFX_MODULE_STATE
#definePROCESS_LOCAL(class_name,ident_name)\
AFX_DATADEFCProcessLocal
#defineEXTERN_PROCESS_LOCAL(class_name,ident_name)\
externAFX_DATAPROCESS_LOCAL(class_name,ident_name)
继续看AfxGetModuleThreadState(),在获得了AFX_MODULE_STATE对象之后,访问其m_thread成员。
这又是一个线程相关的数据,可以获得AFX_MODULE_STATE在不同线程中的私有数据。
//AFX_MODULE_STATE(globaldataforamodule)
classAFX_MODULE_STATE:
publicCNoTrackObject
{
//definethreadlocalportionsofmodulestate
THREAD_LOCAL(AFX_MODULE_THREAD_STATE,m_thread)
现在简单总结一下,AfxGetModuleState()可以获得与执行线程关联的AFX_MODULE_STATE,而AfxGetModuleThreadState()可以获得与执行线程关联的AFX_MODULE_STATE与当前执行线程关联的AFX_MODULE_THREAD_STATE。
看起来有点绕,再啰嗦几句。
我们可以这样理解,每一个线程在执行的时候,需要以一个Module作为缺省上下文,比如查找资源的时候,默认从该Module里查找。
这就是为什么我们需要在DLL的输出函数入口处进行资源切换,其实是把调用者线程的上下文设为当前代码所在的Module,从而保证资源的正确装载。
另外,Module里代码需要根据不同的执行线程保存不同的全局数据(是不是为了避免访问的冲突?
),于是Module还可以有自己独有的线程相关的数据。
所以,AFX_MODULE_STATE可以在不同线程中使用,而AFX_MODULE_THREAD_STATE只能在特定线程中使用。
现在再来看afxMapHWND(),其返回的与工作线程关联的Module在工作线程下的Hash表。
我们现在可以想象,如果工作线程关联Module的不同,或者相同关联Module下,而不是工作线程下,Hash表是完全不同的。
现在继续检查堆栈,发现是Assert来自CWnd的消息循环
intCWnd:
:
RunModalLoop(DWORDdwFlags)
{
……
if(!
AfxGetThread()->PumpMessage())
CWinThread*AFXAPIAfxGetThread()
{
//checkforcurrentthreadinmodulethreadstate
AFX_MODULE_THREAD_STATE*pState=AfxGetModuleThreadState();
CWinThread*pThread=pState->m_pCurrentWinThread;
//ifnoCWinThreadforthemodule,thenusetheglobalapp
if(pThread==NULL)
pThread=AfxGetApp();
returnpThread;
_AFXWIN_INLINECWinApp*AFXAPIAfxGetApp()
{returnafxCurrentWinApp;}
#defineafxCurrentWinApp
AfxGetModuleState()->m_pCurrentWinApp
原来是获得当前执行线程相关的Module,并获得其包含/对应的CWinThread对象,并执行其消息循环函数。
现在终于明白,为什么每个支持MFC的模块都要有一个CWinApp(从CWinThread派生)的全局对象了,原来是用于消息循环的!
请注意,如果当前线程没有相关联的Module,则返回当前进程的缺省AFX_MODULE_STATE对象,具体请参见AfxGetModuleState()。
好了,让我们来看一下,在消息循环里发生了什么。
BOOLCWinThread:
:
PumpMessage()
{
……
if(m_msgCur.message!
=WM_KICKIDLE&&!
PreTranslateMessage(&m_msgCur))
{
:
:
TranslateMessage(&m_msgCur);
:
:
DispatchMessage(&m_msgCur);
}
returnTRUE;
BOOLCWinThread:
:
PreTranslateMessage(MSG*pMsg)
{
……
//walkfromtargettomainwindow
CWnd*pMainWnd=AfxGetMainWnd();
if(CWnd:
:
WalkPreTranslateTree(pMainWnd->GetSafeHwnd(),pMsg))
returnTRUE;
//incaseofmodelessdialogs,lastchanceroutethroughmain
// window'sacceleratortable
if(pMainWnd!
=NULL)
{
CWnd*pWnd=CWnd:
:
FromHandle(pMsg->hwnd);
if(pWnd->GetTopLevelParent()!
=pMainWnd)
returnpMainWnd->PreTranslateMessage(pMsg);
原来产生Assert的CWnd对象是主窗口对象,也就是CTestMFCDlg,而不是我们后面在工作线程里创建的CAboutDlg。
这个问题让我们很疑惑,为什么在CAboutDlg的消息循环里会调用CTestMFCDlg对象呢?
仔细看注释,原来消息循环里试图去查找所谓的主窗口(AfxGetMainWnd),并在找到后调用它的消息预处理函数(PreTranslateMessage)。
我们看看主窗口在哪里?
_AFXWIN_INLINECWnd*AFXAPIAfxGetMainWnd()
{CWinThread*pThread=AfxGetThread();
returnpThread!
=NULL?
pThread->GetMainWnd():
NULL;}
CWnd*CWinThread:
:
GetMainWnd()
{
if(m_pActiveWnd!
=NULL)
returnm_pActiveWnd;
//probablyin-placeactive
//whennotinplaceactive,justreturnmainwindow
if(m_pMainWnd!
=NULL)
returnm_pMainWnd;
returnCWnd:
:
GetActiveWindow();
}
很显然是想获取当前线程关联的Module对应的CWinThread对应的主窗口/激活窗口。
因为主线程和工作线程都没有做Module的切换工作,_afxThreadState->m_pModuleState都为空,所以两个线程执行AfxGetModuleState()的结果总是返回进程缺省的AFX_MODULE_STATE对象,即_afxBaseModuleState。
所以AfxGetThread()函数返回的必然是theApp对象(CTestMFCApp),后者的m_pMainWnd成员指向的就是CTestMFCDlg对象。
在调用CTestMFCDlg的PreTranslateMessage函数的时候,其内部进行了如下检查
CFrameWnd*CWnd:
:
GetTopLevelFrame()const
{
if(GetSafeHwnd()==NULL)//noWindowattached
returnNULL;
ASSERT_VALID(this);
然后进入了前面提到的CWnd:
:
AssertValid()函数,终于导致Assert的发生。
为什么呢?
因为afxMapHWND()是在工作线程下运行的,而m_pMainWnd对应的CTestMFCDlg是在主线程下创建的,两者使用的Hash表是不同的。
因此工作线程根据HWND查找CWnd对象时自然是找不到,于是自动创建一个新的CWnd,当然与m_pMainWnd不同啦,虽然HWND是相同的。
现在我们来想想,为什么MFC要做前面的ASSERT检查呢?
为什么要求MFC对象只能在创建线程里使用而不允许跨线程使用呢?
就像在ASSERT的位置的注释所言:
//Note:
ifeitheroftheaboveassertsfireandyouare
//writingamultithreadedapplication,itislikelythat
//youhavepassedaC++objectfromonethreadtoanother
//andhaveusedthatobjectinawaythatwasnotintended.
//(onlysimpleinlinewrapperfunctionsshouldbeused)
//
//Ingeneral,CWndobjectsshouldbepassedbyHWNDfrom
//onethreadtoanother. Thereceivingthreadcanwrap
//theHWNDwithaCWndobjectbyusingCWnd:
:
FromHandle.
//
//ItisdangeroustopassC++objectsfromonethreadto
//another,unlesstheobjectsaredesignedtobeusedin
//suchamanner.
是不是为了避免资源访问冲突?
比如同时操作一个对象等等,单线程总是比多线程简单很多。
又或者MFC本身就使用了太多线程相关的数据,跨线程操作会导致混乱?
到目前为止也还没有想清楚……
到目前为止,我们已经大概知道问题是如何出现的了,那么如何解决呢?
这或许才是大多数人想要了解的。
1.使用VS2005编译运行,一切正常。
2.使用AfxBeginThread()创建线程,而不是CreateThread()。
voidCTestMFCDlg:
:
OnOK()
{
AfxBeginThread((AFX_THREADPROC)ThreadFunc,NULL);
}
因为AfxBeginThread内部会自动做一些处理,所以也没有问题。
3.另外我们还发现,如果只有一个窗口,即没有CTestMFCDlg窗口存在的情况下,CAboutDlg是可以正常显示的,因为不涉及跨线程访问MFC对象的情况。
这就是为什么我们经常在非MFC的COM组件里可以多线程使用单个MFC窗口的原因。
MFC并不是只能在主线程里使用,只是不能跨线程传递对象而以。
4.有没有可能使用CreateThread()创建线程,而还能在线程里显示窗口的?
特别是有些代码是第三方开发的,你无法修改的时候。
当然了,AfxBeginThread内部可能也是调用CreateThread这个Windows的API,既然它能,我们肯定也能,只是代码的多少而已。
有没有可能像前面的文章对DLL输出函数的资源切换那么简单,就用AFX_MANAGE_STATE(AfxGetStaticModuleState())?
我们试着加上AFX_MANAGE_STATE(AfxGetStaticModuleState()),结果是
_AFXWIN_INLINEHINSTANCEAFXAPIAfxGetResourceHandle()
{ASSERT(afxCurrentResourceHandle!
=NULL);
#define afxCurrentResourceHandle AfxGetModuleState()->m_hCurrentResourceHandle
原因是_afxThreadState->m_pModuleState变成了
AFX_MODULE_STATE*AFXAPIAfxGetStaticModuleState()
{
AFX_MODULE_STATE*pModuleState=&afxModuleState;
returnpModuleState;
}
static_AFX_DLL_MODULE_STATEafxModuleState;
而原本AfxGetModuleState()返回的是_afxBaseModuleState,两者是不同的。
现在连资源都装载不了,更别说下面的消息循环了!
而前面我们已经能够装载资源了,所以添加的这句话简直是火上浇油,毫无益处!
现在我们就纳闷了,现在的情况跟以前我们遇到的显示DLL里的窗口有什么不同么?
为什么之前可以这么做,而现在不可以?
看起来两者完全一样嘛!
仔细想一想,原来以前的情况是只有一个线程,跨越两个DLL,所以需要切换的只是ModuleState。
现在是在同一个DLL/EXE里,需要跨越的是两个线程!
哦,这样看来,应该要切换的是ModuleThreadState,让两个线程使用同一个ModuleThreadState。
因此,需要线程的创建者把自己的ModuleThreadState传给被创建线程,并进行相关的替换--你可以查看AfxBeginThread/CWinThread的实现代码,而以下是我的简单实现。
DWORDCTestMFCDlg:
:
ThreadFunc(PVOIDref_mts)
{
AFX_MODULE_THREAD_STATE*mts=AfxGetModuleThreadState();
AFX_MODULE_THREAD_STATEbackup_mts;
memcpy(&backup_mts,mts,sizeof(AFX_MODULE_THREAD_STATE));
memcpy(mts,(AFX_MODULE_THREAD_STATE*)ref_mts,sizeof(AFX_MODULE_THREAD_STATE));
CAboutDlgdlg;
dlg.DoModal();
memcpy(mts,&backup_mts,sizeof(AFX_MODULE_THREAD_STATE));
return0;
}
voidCTestMFCDlg:
:
OnOK()
{
//AfxBeginThread((AFX_THREADPROC)ThreadFunc,NULL);
:
:
CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)ThreadFunc,AfxGetModuleThreadState(),NULL,NULL);
}
因为线程结束后,线程的私有数据会被删除,所以需要首先保存原有的地址,在最后恢复再删除,否则删除的将是引用的数据。
今天跟同事联调一段代码,被一个问题郁闷了很久。
调用过程其实并不复杂,就是他提供一个Dll,并输出一个函数(
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 线程 消息