原則上,MFC對象只能由創建該對象的線程訪問,而不能由其它線程訪問.這是因爲MFC窗口中有一個Windows handle maps, 這個maps同線程相關,也就是說該線程一定會訪問該maps,而且該線程創建的MFC窗口對象一定會放到該maps中,如果沒有就會報錯.但是如果其它線程將一個窗口對象傳到該線程,因爲這個窗口沒有在該線程maps中,所以就會報錯.但是MFC也給出了跨線程訪問MFC窗口對象的辦法,一種辦法就是就是不傳遞窗口對象,而是傳遞窗口句柄;另外一種辦法就是在接收窗口句柄的線程中使用FromHandle構造一個新的窗口對象並加入maps中.詳細信息參考 Multithreading: Programming Tips.
下面是示例代碼.
從CWinThread派生一個CUIThread類,可以利用VS嚮導生成,再添加一個成員:HWND m_hParentWnd.
創建一個對話框類CUIChildDlg,同樣用VS嚮導生成.
子窗口所在的線程.
- class CUIThread : public CWinThread
- {
- DECLARE_DYNCREATE(CUIThread)
- protected:
- CUIThread(); // protected constructor used by dynamic creation
- virtual ~CUIThread();
- public:
- virtual BOOL InitInstance();
- virtual int ExitInstance();
- protected:
- DECLARE_MESSAGE_MAP()
- public:
- HWND m_hParentWnd; //注意,它是父窗口句柄,不能是CWnd*對象.
- };
在線程中創建對話框窗口.
- BOOL CUIWinThread::InitInstance()
- {
- // TODO: perform and per-thread initialization here
- ASSERT(::IsWindow(m_ hParentWnd));
- CWnd* pParent = CWnd::FromHandle(m_hParentWnd);//注意這行
- CUIChildDlg* pDlg = new CUIChildDlg(pParent);
- pDlg->Create(CUIChildDlg::IDD, pParent);
- pDlg->ShowWindow(SW_SHOW);
- return TRUE;
- }
在這個函數中,創建了一個對話框,作爲主窗口的子窗口.
注意這個FromHandle的調用,他返回一個CWnd對象.
這樣創建的窗口不會報錯.如果直接將主窗口對象傳遞過來,而不是通過調用FromHandle獲取,則調用pDlg->Create會報錯,在調試版本中會彈出一個窗口,指出錯誤位置.
CUIChildDlg是利用嚮導隨便寫的一個對話框.注意要重載下面這個函數.
- void CUIChildDlg::OnNcDestroy()
- {
- CDialog::OnNcDestroy();
- // TODO: Add your message handler code here
- ::PostQuitMessage(0);//爲了使線程自動退出.
- }
下面代碼是CWinApp派生類,主窗口在這個類中創建.
- BOOL CMFCSingleDocTestApp::InitInstance()
- {
- ... …
- // The one and only window has been initialized, so show and update it
- m_pMainWnd->ShowWindow(SW_SHOW);//主窗口
- m_pMainWnd->UpdateWindow();
- // call DragAcceptFiles only if there's a suffix
- // In an SDI app, this should occur after ProcessShellCommand
- m_pUIThread = (CUIWinThread*)AfxBeginThread(RUNTIME_CLASS(CUIWinThread),
- THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);//創建後先不要啓動.
- m_pUIThread->m_hParentWnd = m_pMainWnd->m_hWnd;//主窗口句柄.
- m_pUIThread->ResumeThread();
- return TRUE;
- }
退出函數實現.
- int CMFCSingleDocTestApp::ExitInstance()
- {
- //TODO: handle additional resources you may have added
- AfxOleTerm(FALSE);
- ASSERT(NULL != m_pUIThread);
- ::WaitForSingleObject(m_pUIThread->m_hThread, INFINITE);
- return CWinAppEx::ExitInstance();
- }
等待函數是必須的,這是爲了等待子窗口線程退出後父窗口線程再退出.
在測試中發現,關閉主窗口之前只能保證先關閉子窗口,但退出主線程之前並不能保證子窗口線程一定會退出,這可能會導致某些資源不能正確釋放,所以這裏要調用等待函數,從而保證子窗口線程能夠正常退出.
跨線程父子窗口的好處是創建子窗口阻塞時不會影響父窗口的運行.例如啓動程序時,創建子窗口過程中由於加載太多內容而阻塞,導致父窗口無法操作,分屬不同線程後,父窗口運行不受影響,它仍然可以正常啓動,最大最小化,移動,響應鼠標消息等等.
但也不完全是這樣,程序運行起來之後,子窗口也會使用父窗口所在的線程消息循環,如果子窗口阻塞,同樣會導致父窗口阻塞.
對於跨線程MFC對象使用問題,MSDN已經介紹過來,可以參考Multithreading with C++ and MFC.
有一位網友介紹的更詳細,參考如何在工作線程中創建窗口?.只不過他使用的是工作線程,而不是MFC提供的UI線程.