利用CWinThread實現跨線程父子MFC窗口

   原則上,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嚮導生成.

子窗口所在的線程.

  1. class CUIThread : public CWinThread 
  2.     DECLARE_DYNCREATE(CUIThread) 
  3.  
  4. protected
  5.     CUIThread();           // protected constructor used by dynamic creation 
  6.     virtual ~CUIThread(); 
  7.  
  8. public
  9.     virtual BOOL InitInstance(); 
  10.     virtual int ExitInstance(); 
  11.  
  12. protected
  13.     DECLARE_MESSAGE_MAP() 
  14.  
  15. public
  16.     HWND                m_hParentWnd; //注意,它是父窗口句柄,不能是CWnd*對象.
  17. }; 

在線程中創建對話框窗口.

  1. BOOL CUIWinThread::InitInstance() 
  2.     // TODO:  perform and per-thread initialization here 
  3.  
  4. ASSERT(::IsWindow(m_ hParentWnd)); 
  5.     CWnd* pParent = CWnd::FromHandle(m_hParentWnd);//注意這行 
  6.     CUIChildDlg* pDlg = new CUIChildDlg(pParent); 
  7.     pDlg->Create(CUIChildDlg::IDD, pParent); 
  8.     pDlg->ShowWindow(SW_SHOW); 
  9.  
  10.     return TRUE; 

在這個函數中,創建了一個對話框,作爲主窗口的子窗口.

注意這個FromHandle的調用,他返回一個CWnd對象.

這樣創建的窗口不會報錯.如果直接將主窗口對象傳遞過來,而不是通過調用FromHandle獲取,則調用pDlg->Create會報錯,在調試版本中會彈出一個窗口,指出錯誤位置.

CUIChildDlg是利用嚮導隨便寫的一個對話框.注意要重載下面這個函數.

  1. void CUIChildDlg::OnNcDestroy() 
  2.     CDialog::OnNcDestroy(); 
  3.  
  4.     // TODO: Add your message handler code here 
  5.     ::PostQuitMessage(0);//爲了使線程自動退出. 

下面代碼是CWinApp派生類,主窗口在這個類中創建. 

  1. BOOL CMFCSingleDocTestApp::InitInstance() 
  2.     ... … 
  3.     // The one and only window has been initialized, so show and update it 
  4.     m_pMainWnd->ShowWindow(SW_SHOW);//主窗口 
  5.     m_pMainWnd->UpdateWindow(); 
  6.     // call DragAcceptFiles only if there's a suffix 
  7.     //  In an SDI app, this should occur after ProcessShellCommand 
  8.  
  9. m_pUIThread = (CUIWinThread*)AfxBeginThread(RUNTIME_CLASS(CUIWinThread), 
  10. THREAD_PRIORITY_NORMAL,    0, CREATE_SUSPENDED);//創建後先不要啓動. 
  11.     m_pUIThread->m_hParentWnd = m_pMainWnd->m_hWnd;//主窗口句柄. 
  12.     m_pUIThread->ResumeThread(); 
  13.  
  14.     return TRUE; 

退出函數實現. 

  1. int CMFCSingleDocTestApp::ExitInstance() 
  2.     //TODO: handle additional resources you may have added 
  3.     AfxOleTerm(FALSE); 
  4.  
  5.     ASSERT(NULL != m_pUIThread); 
  6.     ::WaitForSingleObject(m_pUIThread->m_hThread, INFINITE); 
  7.  
  8.     return CWinAppEx::ExitInstance(); 

 

等待函數是必須的,這是爲了等待子窗口線程退出後父窗口線程再退出.

在測試中發現,關閉主窗口之前只能保證先關閉子窗口,但退出主線程之前並不能保證子窗口線程一定會退出,這可能會導致某些資源不能正確釋放,所以這裏要調用等待函數,從而保證子窗口線程能夠正常退出.

 

跨線程父子窗口的好處是創建子窗口阻塞時不會影響父窗口的運行.例如啓動程序時,創建子窗口過程中由於加載太多內容而阻塞,導致父窗口無法操作,分屬不同線程後,父窗口運行不受影響,它仍然可以正常啓動,最大最小化,移動,響應鼠標消息等等.

但也不完全是這樣,程序運行起來之後,子窗口也會使用父窗口所在的線程消息循環,如果子窗口阻塞,同樣會導致父窗口阻塞.

 

 

對於跨線程MFC對象使用問題,MSDN已經介紹過來,可以參考Multithreading with C++ and MFC.

有一位網友介紹的更詳細,參考如何在工作線程中創建窗口?.只不過他使用的是工作線程,而不是MFC提供的UI線程.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章