无法让MFC工作线程将hWnd或MDI窗口指针传递给线程

bri*_*maa 3 mfc multithreading

由于某种原因,传递hWnd或新创建的MDI窗口的指针不能在工作线程中重新编写为其原始值.我试过从App类,Document类和View类创建线程.都具有相同的效果.我正在为工作线程使用全局函数.有趣的是,我在1998年使用与MFC MDI中相同的代码相同的代码,当时效果很好,但现在似乎不起作用.

也许我只是没有看到问题.这里发生了什么?我想要做的是简单,创建一个新的View窗口,捕获它的hWnd并将该hWnd传递给工作线程,以便工作线程可以向窗口发送消息以打印字符串等等.我想从Document类启动线程.编译器是VS2010,它在Debug中运行.

看完之后:http: //msdn.microsoft.com/en-us/library/h14y172e%28v=VS.100%29.aspx,我意识到你可能无法将指针传递给工人的视图类线程.所以我专注于hWnds.在OnTestConnect块中,返回有效的hWnd以及指向新View窗口的有效指针.

这是代码(来自App类):

struct THREADPARMS
{
    HWND hWndView;
    int test;   
};
Run Code Online (Sandbox Code Playgroud)

(注意,我已经尝试将结构定义为typedef并使用变量名称,所有结果都相同)

UINT Starter( LPVOID pParms )
{
    THREADPARMS* pThreadParms = (THREADPARMS* )pParms;

    //This step shouldn't be necesarry but I tried it anyway.  Should
    //be able to use pThreadParms->hWndView without casting.
    //The hWnd value does not come across as valid.  It is valid before sending.
    HWND hWnd = (HWND)pThreadParms->hWndView;

    //The int comes across fine
    int iNum = pThreadParms->test;

     CHFTAppBView* pView = (CHFTAppBView*) CHFTAppBView::FromHandle(hWnd);

     //This bombs with a debug error becuase pView ptr is invalid (though it was
     //valid before sending over
     pView->SendMessage( ID_FILE_PRINT, 0, 0 );

     return 0;

}

void CHFTAppBApp::OnTestConnect()
{
    THREADPARMS* pThreadParms = new THREADPARMS;

     //Create the window
     AfxGetApp()->OnCmdMsg( ID_FILE_NEW, 0, NULL, NULL );

     CMDIFrameWnd* pFrame = (CMDIFrameWnd*)AfxGetApp()->m_pMainWnd;
     CMDIChildWnd* pChild = (CMDIChildWnd*)pFrame->GetActiveFrame();
     CHFTAppBView* pView = (CHFTAppBView*)pChild->GetActiveView();

     pThreadParms->hWndView = pView->m_hWnd;
     pThreadParms->test = 10;

     AfxBeginThread( Starter, pThreadParms );
}
Run Code Online (Sandbox Code Playgroud)

第2部分

//Create the window
AfxGetApp()->OnCmdMsg( ID_FILE_NEW, 0, NULL, NULL );

CMDIFrameWnd* pFrame = (CMDIFrameWnd*)AfxGetApp()->m_pMainWnd;
CMDIChildWnd* pChild = (CMDIChildWnd*)pFrame->GetActiveFrame();
CHFTAppBView* pView = (CHFTAppBView*)pChild->GetActiveView();

HWND h = pView->m_hWnd;
SendMessage( h, ID_FILE_PRINT, 0, 0 );
Run Code Online (Sandbox Code Playgroud)

我正在尝试确定hWnd是否有效.pView有效.单步执行代码并查看调试器监视器中的指针统计信息会显示一个引用CView类的helathy指针.但它并没有带来健康的回报.然后Debugger Watch说'无法评估'hWnd内存地址并说'unused = 0'.但是,通过IsWindow运行它会返回true.去搞清楚.尝试使用该句柄将CView消息发送到CView窗口时会被忽略.为什么GetAvtiveView会返回一个有效的指针,但该类中的hWnd会返回垃圾?

第3部分

经过多次挖掘后,虽然hwnd变量显示'unused = ???',但HWND有效 在Watch窗口中.我认为它是有效的,因为线程代码中收到的hWnd与主代码中附加到pView指针的hWnd相匹配.hWnd取自的pView指针也是有效的,因为Watch通过返回它所代表的CView类的名称将其识别为有效的CView类指针.但是仍有两个问题.一个是即使系统将有效的hWnd发送回CView窗口(pView-> m_hWnd),SendMessage(pView-> m_hWnd,ID_FILE_PRINT_PREVIEW,0,0)也拒绝工作.系统忽略它.我期望在新创建的视图窗口中运行FilePrintPreview命令.其次,FromHandle返回一个CWnd对象并使用以下所有方法将其转换为CView失败:

UINT ThreadTest1( LPVOID pParms )
{
    THREADPARMS* pThreadParms = (THREADPARMS* )pParms;

    //Returns a CWnd even though the handle is to a valid CView 
    CHFTAppBView* pView2 = (CHFTAppBView*) CHFTAppBView::FromHandle(hWnd);

    //Doesn't seem to do anything.  Still returns a hWnd that the system recognizes as 
    //a CWnd.  That's what reports in the Watch window.
    CHFTAppBView* pView = reinterpret_cast<CHFTAppBView*>(CHFTAppBView::FromHandle(hWnd));

    //Doesn't appear to do anything
    DYNAMIC_DOWNCAST( CHFTAppBView, pView2 );

    //Confirms what watch window says -- it's a CWnd not a CView.
    if ( !pView->IsKindOf( RUNTIME_CLASS(CHFTAppBView) ) )
          AfxMessageBox( "Not CView" );

    ::SendMessage( hWnd, ID_FILE_PRINT, 0, 0 );

    return 0;

}

void CHFTAppBDoc::Main()
{
    AfxGetApp()->OnCmdMsg( ID_FILE_NEW, 0, NULL, NULL );

    THREADPARMS* pThreadParms = new THREADPARMS;

    CMDIFrameWnd* pFrame = (CMDIFrameWnd*)AfxGetApp()->m_pMainWnd;
    CMDIChildWnd* pChild = (CMDIChildWnd*)pFrame->GetActiveFrame();
    CHFTAppBView* pView = (CHFTAppBView*)pChild->GetActiveView();

    pThreadParms->hWndView = pView->m_hWnd;

     AfxBeginThread( ThreadTest1, pThreadParms );

    SendMessage( pView->m_hWnd, ID_FILE_PRINT, 0, 0 );
    //Or
    ::SendMessage( pView->m_hWnd, ID_FILE_PRINT, 0, 0 );

 }
Run Code Online (Sandbox Code Playgroud)

那么问题是如何将hWnd转换为线程代码中正确的窗口指针?为什么系统无法将其转换为CView?

第4部分

Probem解决了(现在).教训:

  1. 无法将CView窗口指针传递给工作线程.请参阅上面的链接.
  2. 使用SendMessage在theads和windows之间进行通信

    //现在可以从工作线程开始工作

    THREADPARMS*pThreadParms =(THREADPARMS*)pParms;

    HWND hWnd = static_cast(pThreadParms-> hWndView);

    LRESULT lRst = :: SendMessage(pThreadParms-> hWnd,WM_GGG,0,0);

    //要不就

    LRESULT lRst = :: SendMessage(pThreadParms-> hWnd,WM_GGG,0,0);

    //以及创建线程的CDoc方法中的这个:

    CHFTAppBView*pView =(CHFTAppBView*)pChild-> GetActiveView();

    LRESULT lRst = :: SendMessage(pView-> m_hWnd,WM_GGG,0,0);

    CView的消息地图......

    ON_MESSAGE(WM_GGG,kk)

  3. 将HWND传递给工作线程以识别CView窗口

  4. 无法从CWnd转变为较低级别的(从而使其正常工作).
  5. 在视图的消息映射中使用ON_MESSAGE来捕获使用SendMessage发送的命令(确保在View的.h文件中包含方法声明为afx_msg)

一切都很直接,但对于那些寻找答案的人(面对无数可能的原因),这个总结可能会有所帮助......

我仍然没有完全理解为什么将FromHandle转换为CView在我的旧MFC应用程序中工作而不是现在.也许它与代码所在的位置有关.在旧的MFC应用程序中,它位于CView窗口中,并位于CDoc类的此代码中.CDocument不是来自CWnd,而是CView.

无论如何,这包装了这个问题.非常感谢所有提出建议的人 - Nik,Scott,Ulrich和Mark.你的圣人建议非常有帮助.

Nik*_*lis 5

你可以打电话,CHFTAppBView::FromHandle(hWnd)但你得到的不是指向a的指针CHFTAppBView; 它是指向a的指针CWnd.通过转换它,你告诉编译器"相信我,这实际上是指向a的指针CHFTAppBView.除非它确实不是,你不应该这样对待它或假装它是.

毕竟,如果食谱需要橙汁,那么你不需要柠檬,将其涂成橙色,将其榨汁并称之为橙汁.你呢?

那么该怎么办?好吧,如果您只想发送ID_FILE_PRINT消息,那么您甚至不需要这样做CWnd.你可以这样做:

::SendMessage(hWnd, ID_FILE_PRINT, 0, 0);
Run Code Online (Sandbox Code Playgroud)

当然,这可能是错的.你大概的意思做的是这样的:

::SendMessage(hWnd, WM_COMMAND, ID_FILE_PRINT, 0);
Run Code Online (Sandbox Code Playgroud)

更新:

同样,你不能做你想做的事.让我们一步一步走吧,好吗?

//Returns a CWnd even though the handle is to a valid CView 
CHFTAppBView* pView2 = (CHFTAppBView*) CHFTAppBView::FromHandle(hWnd);
Run Code Online (Sandbox Code Playgroud)

对.CWnd::FromHandle返回指向a的指针CWnd,而不是其他任何指针.它不会给你一个指向原始的指针CHFTAppBView.它给你一个新的 CWnd,附在同一个HWND.将它强制转换为a 是无效的,CHFTAppBView因为这CWnd不是新的CHFTAppBView.

//Doesn't seem to do anything.  Still returns a hWnd that the system recognizes as 
//a CWnd.  That's what reports in the Watch window.
CHFTAppBView* pView = reinterpret_cast<CHFTAppBView*>(CHFTAppBView::FromHandle(hWnd));
Run Code Online (Sandbox Code Playgroud)

再次,CWnd::FromHandle返回一个指向 的指针,该指针CWnd一无所知CHFTAppBView.你告诉编译器"相信我,这个指针是一个CHFTAppBView对象!" 除非它不是.它是一个指向CWnd连接到HWND这是一个CHFTAppBView.

//Doesn't appear to do anything
DYNAMIC_DOWNCAST( CHFTAppBView, pView );
Run Code Online (Sandbox Code Playgroud)

STILL不会做任何事情.首先,DYNAMIC_DOWNCAST返回一个指向CHFTAppBView那里的指针,你正在调用MFC RTTI函数但是没有对结果做任何事情.但即使你确实保存了结果,也无济于事.您将尝试将泛型CWnd转换为不是.

我将尝试再解释一次:当您创建视图时,MFC会创建一个CHFTAppBViewHWND视图关联的对象.当您在视图中调用CWnd::FromHandle传递时HWND,MFC会创建一个新的且不同的CWnd实例,该实例指向相同的HWND- 第二个CWndNOT, CHFTAppBView并且对HWNDMFC类,视图,文档或其他任何内容一无所知.

你正试图把CWnd *CWnd::FromHandle归还并把它锤成一个CHFTAppBView *.无论你怎么努力,这都行不通.所有你能得到的将是一个,CWnd *而不是别的.

作为旁注,您也无法将MFC对象从一个线程传递到另一个线程,因此传递原始文件CHFTAppBView *会导致出现奇怪的问题,并可能导致难以跟踪错误.

更新2:

你问" 为什么CWnd对象不能被强制转换为从CWnd中获取的窗口类? "

让我们从CWnd::FromHandle我们开始吧?

CWnd* PASCAL CWnd::FromHandle(HWND hWnd)
{
    CHandleMap* pMap = afxMapHWND(TRUE); //create map if not exist
    ASSERT(pMap != NULL);
    CWnd* pWnd = (CWnd*)pMap->FromHandle(hWnd);

#ifndef _AFX_NO_OCC_SUPPORT
    pWnd->AttachControlSite(pMap);
#endif

    ASSERT(pWnd == NULL || pWnd->m_hWnd == hWnd);
    return pWnd;
}
Run Code Online (Sandbox Code Playgroud)

这导致我们CHandleMap::FromHandle.这个代码有点复杂,在这里发布也无济于事,所以不要让事情变得混乱.但概念上,这就是函数的作用:

如果在地图中找到了一个句柄,它会返回它; 否则,它会创建一个新的CWnd并将该CWnd点指向HWND您传入的内容.然后它会返回指向CWnd您的指针.但注意,这是唯一的一个CWnd.还有其他人.所以你看,你不能只将其转换成CWnd其他东西 - 即使其他东西是从某个东西派生出来的CWnd- 因为你拥有的只是一个CWnd而已.

假设你有一个妻子.她非常漂亮,你非常爱她.你在钱包里随身携带她的照片.现在,当你遇到某人并且他们问你是否结婚时,你拿出钱包并自豪地向他们展示她的照片.你说话的人不认为你和照片结婚了.而且你不认为你可以将图片神奇地"转换"成你的妻子.

这里的情况有点类似.CWnd::FromHandle给你一个"图片"的种类.这对周围的展示很有好处,但对其他很多东西都不好.你在问"为什么我不能把照片转换成我的妻子?" 答案是因为这不是图片的工作方式.