由于某些原因,传递一个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部分问题解决了(目前)。教训:
- 不能传递CView窗口指针给工作线程。参见上面的链接。
使用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)
将hwnd传递给工作线程以识别CView窗口
- 不能从CWnd强制转换到较低的派生类(除非它能正常工作)。
- 在视图的消息映射中使用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
,它附属于同一个HWND
。NOT 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
的指针,该指针连接到CHFTAppBView
的HWND
上。
//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
-第二个CWnd
是NOT CHFTAppBView
, HWND
对你的MFC类,视图,文档或其他任何东西都一无所知。
你正试图采取CWnd::FromHandle
返回的CWnd *
并将其锤入CHFTAppBView *
。不管你怎么努力,这都行不通。你能得到的只有一个CWnd *
,别的什么都没有。
作为旁注,您也不能将MFC对象从一个线程传递到另一个线程,因此传递原始CHFTAppBView *
将导致奇怪的问题突然出现,并可能导致难以跟踪错误。
你问"为什么不能将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
为您提供了各种"图片"。它很适合到处展示,但不适合做其他事情。你在问"为什么我不能把这张照片变成我的妻子?"答案是,这不是图片的工作方式。