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



由于某些原因,传递一个hWnd或一个新创建的MDI窗口的指针不能在工作线程中重新转换为其原始值。我试过从应用程序类,文档类和视图类创建线程。都有相同的效果。我正在为工作线程使用全局函数。有趣的是,我使用相同的代码,我用来做同样的事情在MFC MDI早在1998年,它工作得很好,但现在似乎不工作。

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

读完这个:http://msdn.microsoft.com/en-us/library/h14y172e%28v=VS.100%29.aspx,我意识到你可能不能传递一个指针到视图类周围的工作线程。所以我关注的是风之家。在OnTestConnect块中,返回一个有效的hWnd作为指向新视图窗口的有效指针。

下面是代码(来自App类):
struct THREADPARMS
{
    HWND hWndView;
    int test;   
};

(注意,我已经尝试将结构定义为类型定义并带有变量名,所有结果都相同)

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 );
}

第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 );

我正在尝试确定hWnd是否有效。pView是有效的。逐步浏览代码并查看调试器监视中的指针状态,会显示一个引用CView类的健康指针。但它并没有返回一个健康的hWnd。然后调试器手表说'无法评估' hWnd内存地址,并说'未使用=0'。但是,运行它超过IsWindow返回true。图。试图发送一个CView消息到CView窗口与那个句柄只是被忽略。为什么GetAvtiveView返回一个有效的指针,但在该类返回垃圾hWnd ?

第3部分

进一步挖掘后,发现HWND是有效的,尽管HWND变量显示"未使用=??"在《守望》窗口。我认为它是有效的,因为在线程代码中收到的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 );
 }

所以问题是如何将hWnd转换为线程代码中正确的窗口指针?为什么系统不能将它转换为CView?

第4部分

问题解决了(目前)。教训:

  1. 不能传递CView窗口指针给工作线程。参见上面的链接。
  2. 使用SendMessage在头和窗口之间通信

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

    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的message map…

    ON_MESSAGE(WM_GGG, kk)

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

  4. 不能从CWnd强制转换到较低的派生类(除非它能正常工作)。
  5. 在视图的消息映射中使用ON_MESSAGE来捕获用SendMessage发送的命令(确保在视图的.h文件中包含方法声明作为afx_msg)

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

我仍然不完全理解为什么将FromHandle转换为CView在我的旧MFC应用程序中工作,而不是现在。也许这与代码的位置有关。在旧的MFC应用程序中,它位于CView窗口中,在CDoc类中的代码中。CDocument不是从CWnd派生的,但CView是。

不管怎样,这个问题就解决了。非常感谢所有提供建议的人——尼克、斯科特、乌尔里希和马克。

你可以调用CHFTAppBView::FromHandle(hWnd),但你得到的不是指向CHFTAppBView的指针;这是一个指向CWnd的指针。通过强制转换它,你告诉编译器"相信我,这真的是一个指向CHFTAppBView的指针。除非它真的不是,你不应该这样对待它或假装它是。

毕竟,如果一个食谱需要橙汁,那么你不会把柠檬涂成橙色,榨成汁,然后叫它橙汁。你呢?

那么该怎么办呢?好吧,如果您想要做的只是发送ID_FILE_PRINT消息,那么您甚至不需要CWnd。你可以直接输入:

::SendMessage(hWnd, ID_FILE_PRINT, 0, 0);
当然,这是也可能是错误的。的意思可能是要做的是:
::SendMessage(hWnd, WM_COMMAND, ID_FILE_PRINT, 0);

:

同样,不能做你想做的事情。我们一步一步来,好吗?
//Returns a CWnd even though the handle is to a valid CView 
CHFTAppBView* pView2 = (CHFTAppBView*) CHFTAppBView::FromHandle(hWnd);

CWnd::FromHandle返回一个指向CWnd的指针,而不是其他任何指针。它是而不是给你一个指向原始CHFTAppBView的指针。它给你一个NEW CWnd,它附属于同一个HWNDNOT VALID将其强制转换为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));
同样,CWnd::FromHandle返回一个指向new CWnd的指针,该指针对CHFTAppBView一无所知。你在告诉编译器"相信我,这个指针指向一个CHFTAppBView对象!"除了不是。它是一个指向CWnd的指针,该指针连接到CHFTAppBViewHWND上。
//Doesn't appear to do anything
DYNAMIC_DOWNCAST( CHFTAppBView, pView );

这个仍然不会做任何事情。首先,DYNAMIC_DOWNCAST返回一个指向CHFTAppBView的指针,所以就在那里,你调用了MFC RTTI函数,但没有对结果做任何事情。但即使你保存了结果,也无济于事。你会试图将一个通用的CWnd转换成它不是的东西。

我将尝试再解释一次:当您创建视图时,MFC创建与视图的HWND相关联的CHFTAppBView对象。当你在视图的HWND中调用CWnd::FromHandle时,MFC创建一个NEW和不同的CWnd实例,指向相同的HWND -第二个CWndNOT CHFTAppBView, HWND对你的MFC类,视图,文档或其他任何东西都一无所知。

你正试图采取CWnd::FromHandle返回的CWnd *并将其锤入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;
}

这将我们引向CHandleMap::FromHandle。这个代码有点复杂,在这里发布它也没有帮助,所以我们不要把事情弄乱。但从概念上讲,函数的作用是:

如果在映射中找到句柄,则返回该句柄;否则,它将创建一个新的CWnd,并使CWnd指向您传入的HWND。然后它返回一个指向CWnd的指针给你。但是请注意,这是ONLY一个CWnd。Nothiong。所以你看,你不能把返回的CWnd转换成其他的东西——即使其他的东西是从CWnd派生出来的——因为你得到的只是一个CWnd,仅此而已。

假设你有一个妻子。她很漂亮,你很爱她。你的钱包里放着她的照片。现在,当你遇到别人,他们问你是否结婚了,你拿出钱包,自豪地给他们看她的照片。和你说话的人不会认为你和这幅画结婚了。你不认为你可以神奇地把照片"变成"你的妻子。

这里的情况有点类似。CWnd::FromHandle为您提供了各种"图片"。它很适合到处展示,但不适合做其他事情。你在问"为什么我不能把这张照片变成我的妻子?"答案是,这不是图片的工作方式。

最新更新