void CDlgTwo::AddToListWhenFinish(CString strName, CString strVersion)
{
    int iCnt = m_list.GetItemCount();//Button Wrong会在此抛出异常,调用堆栈查看后发现变量m_list的hWnd=???
    CString strIndex = _T("");
    strIndex.Format(_T("%.3d"),iCnt + 1);
    m_list.InsertItem(iCnt,strIndex);
    m_list.SetItemText(iCnt,1,strName);
    m_list.SetItemText(iCnt,2,strVersion);
}int iCnt = m_list.GetItemCount();//Button Wrong会在此抛出异常,调用堆栈查看后发现变量m_list的hWnd=???,意思是控件不存在了?但是我有在OnInitDialog中初始化,并且另一个按钮都不会有这个问题。感觉就像是哪里没衔接好,重新来了一样

解决方案 »

  1.   

    UINT TestProc(LPVOID pParam)//出错按钮的线程
    {
        CDlgOne* pDlg = (CDlgOne*)pParam;
        pDlg->Func();
        return 0;
    }线程可能已经 return !
      

  2.   


    void CDlgTwo::AddToListWhenFinish(CString strName, CString strVersion)
    {
        int iCnt = m_list.GetItemCount();//Button Wrong会在此抛出异常,调用堆栈查看后发现变量m_list的hWnd=???
        CString strIndex = _T("");
        strIndex.Format(_T("%.3d"),iCnt + 1);
        m_list.InsertItem(iCnt,strIndex);
        m_list.SetItemText(iCnt,1,strName);
        m_list.SetItemText(iCnt,2,strVersion);
    }int iCnt = m_list.GetItemCount();//Button Wrong会在此抛出异常,调用堆栈查看后发现变量m_list的hWnd=???,意思是控件不存在了?但是我有在OnInitDialog中初始化,并且另一个按钮都不会有这个问题。感觉就像是哪里没衔接好,重新来了一样
    UI操作一般最好在UI线程
      

  3.   

    MFC本身不是线程安全的,从来都不要在非主线程的其它线程中操作界面,其实只要遵循这条规则,使用MFC开发从来都不会出现这种问题。当然,你遇到的这个问题并不是线程访问冲突带来的问题,而且和MFC内部的一些实现有关。在MFC中,MFC为每个线程创建了一个句柄表,里面保存着句柄hwnd和指针CWnd*的映射关系。当你在新创建的线程中调用GetParent()时,它里面调用了FromHandle根据父窗口的句柄去获取父窗口的指针,FromHandle内部获取当前线程的句柄表,并在表中查询有没有这个句柄对应的窗口指针,如果在主线程中查询,当然没有问题,主线程的句柄表中确实保存着父窗口句柄和父窗口指针的映射关系,因为父窗口就是在主线程中创建的,当时创建的时候就把他们的映射关系保存到了主线程句柄表里。但是你新开的线程的句柄表是空的,并没有保存这个窗口句柄和它对应的窗口指针的映射关系,它无法根据该窗口句柄查到对应的窗口指针,所以这时候它会根据句柄创建一个临时的CWnd对象,并返回这个临时窗口对象的指针。这个临时的窗口对象和你之前在主线程中创建的CTestListDlg对象,已经不是同一个对象了,自然,这个临时的CTestListDlg对象内部也没有CListCtrl m_list;这些东西,所以m_list为空。
    你可以在你的代码里这样试一下:void CDlgOne::OnBnClickedButton1()
    {
    // TODO: Add your control notification handler code here
    CTestListDlg *p = (CTestListDlg*)GetParent();
    ((CTestListDlg*)GetParent())->AddToInstall(_T("Name_New"),_T("Version_New"));
    }
    void CDlgOne::Func()
    {
    CTestListDlg *p = (CTestListDlg*)GetParent();
    ((CTestListDlg*)GetParent())->AddToInstall(_T("Name_New"),_T("Version_New"));
    }
    分别点下两个按钮,调试看看两次GetParent()获取到的父窗口指针CTestListDlg *p的指一不一样。
    我这里测试一次是0x004af538 一次是0x02b9fc40
    也就是说它根本不是同一个对象,在线程里面获取到的这个是临时创建的,这个临时创建的对象内部的“东西”不全,没有CListCtrl m_list;这些东西。如果还不理解,可以在CTestListDlg中增加一个公有成员变量int m_data;
    然后在主窗口上增加一个按钮“赋值”,这个“赋值”按钮点下后给m_data赋值为99。
    程序运行后,先点一下“赋值”按钮,然后点“Button OK”,调试可以发现
    CTestListDlg *p = (CTestListDlg*)GetParent();后,p->m_data的值是多少,是99,没有问题。
    然后点击“Button Wrong”,调试可以发现
    CTestListDlg *p = (CTestListDlg*)GetParent();后,p->m_data的值是多少,是0!有问题!
    因为前后两个p指向的根本不是同一个对象,在新开线程中获取到的这个p是个新创建的临时对象,内部根本没有那些其他的东西。这些东西在《MFC windows程序设计》和《深入浅出MFC》中都有讲到,其实完全不必去了解MFC内部的这些细节,只要按照《MFC windows程序设计》等书上所说的,只在主线程中操作界面,规规矩矩的来,肯定不会出这种问题。
      

  4.   

    这的确是个很奇怪的问题,我调试了半天,问题知道出在哪儿,却找不到原因,然后百度了一下,发现有人也遇见过这样的问题 http://www.cnblogs.com/over140/archive/2010/05/24/1742476.html我曾经在一个项目中采用过在线程里更新界面数据的方法,但是从来就没有出错过,所以上面的问题让人莫明其妙。附下我以前的部分代码,只不过我这里创建线程用的是CreateThread而不是AfxBeginThread 我觉得楼主可以用CreateThread创建线程再试试,我觉得7楼说的很对,这个肯定和MFC内部机制有关。 //创建所有的线程
    for(i = 0 ; i < nNumberOfThreads ; i++ )
    {  
    //初始化每个线程的上下文,用于传递给线程函数
    pThreadsContext = new THREAD_CONTEXT;
    pThreadsContext->pWorkQueue  = this;
    pThreadsContext->pThreadData = ThreadData == NULL? NULL : ThreadData[i];    
    //创建线程
    m_phThreads[i] = CreateThread(
    NULL,
    0,
    CWorkQueue::ThreadFunc,
    pThreadsContext,
    0,
    &dwThreadId); if(m_phThreads[i] == NULL)
    {  
    delete pThreadsContext;
    m_nNumberOfThreads = i;
    Destroy();
    return false;
    }
    }void CDlgEntryKM2::OnTimer(UINT_PTR nIDEvent)
    {
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    if( nIDEvent == 1 )
    {
    if( KillTimer( nIDEvent ) )
    {
    m_pLastWorkItem = new CWorkItemBaseInfo( this, RIGHT_NAME_KM2 );
    // 更新用户界面的线程就是在这里启动的
    theApp.m_WorkQueue.InsertWorkItem( m_pLastWorkItem );
    }
    } CDialog::OnTimer(nIDEvent);
    }void CDlgEntryKM2::PrepareSearch()
    {
    KillTimer( 1 );
    SetTimer( 1,200,NULL );
    }// 这就是更新用户界面的线程函数
    void CDlgEntryKM2::StartSearch( CWorkItemBaseInfo * pWorkItemBaseInfo )
    {
    _BeginTry; _STARTPROMPT; GetDlgItem(IDC_BTN_SAVE)->EnableWindow( FALSE );
    GetDlgItem(IDC_BTN_SAVE2)->EnableWindow( FALSE );
    GetDlgItem(IDC_BTN_SAVE3)->EnableWindow( FALSE );
    GetDlgItem(IDC_BTN_SAVE4)->EnableWindow( FALSE );
    SetDlgItemText( IDS_PROMPT,_T("") );
    //UpdateData();//多线程里不能用UpdateData
    if( !m_ListCtrl.DeleteAllItems() ) return; //CString strExamDateSearch;
    //CTime tm;
    //((CDateTimeCtrl*)GetDlgItem(IDC_DTP_EXAMDATE_SEARCH))->GetTime(tm);
    //strExamDateSearch = tm.Format( _T("%Y-%m-%d") );
    CString strArchiveID,strArchiveID2,strName,strIDCard;
    GetDlgItemText( IDE_ARCHIVE_ID_SEARCH,strArchiveID );
    GetDlgItemText( IDE_ARCHIVE_ID_SEARCH2,strArchiveID2 );
    GetDlgItemText( IDE_DRIVER_NAME_SEARCH,strName );
    GetDlgItemText( IDE_DRIVER_IDCARD_SEARCH,strIDCard );
    strArchiveID.Remove(' ');strArchiveID2.Remove(' ');strName.Remove(' ');strIDCard.Remove(' ');
    int nScSel = m_cmbSchoolSearch.GetCurSel();
    int nCtpClsSel = m_cmbCartypeClass.GetCurSel();
    //条件全部为空,返回
    if( nScSel < 0 && nCtpClsSel < 0 && strArchiveID.IsEmpty() && strName.IsEmpty() && strIDCard.IsEmpty() )
    return;