For some reason passing an hWnd or a pointer of a newly created MDI window cannot be recasted into its original value in the worker thread. I've tried creating the thread from the App class, Document class and View class. All have the same effect. I'm using a global function for the worker thread. Funny thing is I'm using the same code I used to do the same thing in an MFC MDI back in 1998 and it worked great back then but doesn't seem to work now.
Perhaps I'm just not seeing the problem. What is going on here? What I want to do is simple, create a new View window, capture it's hWnd and pass that hWnd to the worker thread so the worker thread can send messages to the window to print strings and so forth. I'd like to launch the thread from the Document class. The compiler is VS2010 and it's being run in Debug.
After reading this: http://msdn.microsoft.com/en-us/library/h14y172e%28v=VS.100%29.aspx, I realize that you probably can't pass a pointer to the view class around to worker threads. So I'm focusing on the hWnds. In the OnTestConnect block, a valid hWnd is returned as is a valid pointer to the new View window.
Here's the code (from the App class):
struct THREADPARMS
{
HWND hWndView;
int test;
};
(note, I've tried defining the struct as typedef and with a variable name, all have same result)
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 );
}
Part 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 );
I'm trying to determine if the hWnd is valid. The pView is valid. Stepping through the code and looking at the pointer stats in the Debugger Watch shows a helathy pointer that references the CView class. But it does not return a healthy hWnd. Then Debugger Watch says 'can't evaluate' hWnd memory address and says 'unused=0'. Running it past IsWindow returns true, however. Go figure. Trying to send a CView message to the CView window with that handle just gets ignored. Why would the GetAvtiveView return a valid pointer but the hWnd in that class return garbage?
Part 3
After some more digging, turns out the HWND is valid though the hwnd variable displays 'unused=???' in the Watch window. I assume it's valid because the hWnd received in the thread code matches the hWnd attached to the pView pointer in the main code. The pView pointer the hWnd is taken from is valid as well because Watch recognizes it as a valid CView class pointer by returning the name of the CView class it represents. There are still two problems however. One is that even though the system sends a valid hWnd back to a CView window (pView->m_hWnd), a SendMessage(pView->m_hWnd, ID_FILE_PRINT_PREVIEW, 0, 0,) refuses to work. The system ignores it. I expected the FilePrintPreview command in the newly created view window to run. Second is that FromHandle returns a CWnd object and casting it to a CView fails using all of these methods:
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 );
}
So the questions is how can the hWnd be converted to a the correct window pointer in the thread code? Why can't the system cast it to a CView?
Part 4
Probem solved (for now). Lessons:
- Cannot pass a CView window pointer to a worker thread. See link above about this.
Use SendMessage to communicate between theads and windows
//This works from the worker thread now
THREADPARMS* pThreadParms = (THREADPARMS* )pParms;
HWND hWnd = static_cast(pThreadParms->hWndView);
LRESULT lRst = ::SendMessage( pThreadParms->hWnd, WM_GGG, 0, 0 );
//Or just
LRESULT lRst = ::SendMessage( pThreadParms->hWnd, WM_GGG, 0, 0 );
//As well as this from the CDoc method that creates the thread:
CHFTAppBView* pView = (CHFTAppBView*)pChild->GetActiveView();
LRESULT lRst = ::SendMessage( pView->m_hWnd, WM_GGG, 0, 0 );
CView's message map...
ON_MESSAGE( WM_GGG, kk )
Pass HWNDs to worker threads to identify CView windows
- Cannot cast from CWnd to a lower derrived class (and excpet it to work right).
- Use ON_MESSAGE in view's message map to capture commands sent with SendMessage (be sure to include the method declaraion as afx_msg in the View's .h file)
All pretty straight forward but for those looking for answers (when faced with a myriad of possible causes) this summary may help...
I still don't completely undertstand why casting FromHandle to a CView worked in my old MFC app and not now. Perhaps it has something to do with where the code is located. In the old MFC app it was located in the CView window and in this code in the CDoc class. CDocument is not derrived from CWnd but CView is.
Anyway, that wraps this problem up. A BIG thank you to all who offered advice -- Nik, Scott, Ulrich and Mark. Your sage advice was very helpful.