3
votes

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:

  1. Cannot pass a CView window pointer to a worker thread. See link above about this.
  2. 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 )

  3. Pass HWNDs to worker threads to identify CView windows

  4. Cannot cast from CWnd to a lower derrived class (and excpet it to work right).
  5. 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.

1
Can you just avoid using a temporary CHFTAppBView and call ::SendMessage(hWnd...) instead? You're probably running afoul of per-thread handle map issues.Scott Jones
You would do yourself a favour if you stopped using those C-style casts entirely. Instead, use dynamic_cast when going from baseclass to derived class and static_cast when going from void* to sometype*. Then, you make claims like "does not come across as valid", but fail to tell us about the observations you made that lead you to this conclusion.Ulrich Eckhardt
Thank you both for your suggestions. Scott, unfortunately, I am trying to determine if the hWnd is at all valid. I need the hWnd to get a pointer in the thread and I need the worker thread so the GUI doesn't hang when printing and processing data.brimaa
I'd suggest avoiding mixing MFC with the new C++ style casts. MFC implements it's own runtime type information system (it uses C++ features for virtual dispatch but not RTTI) and has a set of casts that take advantage of it and which you should probably use.Nik Bougalis
OK, I'll look into that. That must be new MFC stuff because back in 98 this exact sort of approach worked. I'd love for that to explain this problem. I'll keep you guys posted and provide and answer if I can uncover one.brimaa

1 Answers

5
votes

You can call CHFTAppBView::FromHandle(hWnd) but what you get back isn't a pointer to a CHFTAppBView; it's a pointer to a CWnd. By casting it, you're telling the compiler "trust me, this is really a pointer to a CHFTAppBView. Except it really isn't and you shouldn't treat it as such or pretend that it is.

After all, if a recipe calls for orange juice, then you don't take a lemon, paint it orange, juice it and call it orange juice. Do you?

So what to do? Well, if all you want to do is send the ID_FILE_PRINT message, you don't even need a CWnd as such. You could just just do:

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

Of course, that's also likely to be wrong. What you probably meant to do was this:

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

Update:

Again, you CANNOT do what you are trying to do. Let's go step by step, shall we?

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

Right. CWnd::FromHandle returns a pointer to a CWnd, not to anything else. It is NOT giving you back a pointer to your original CHFTAppBView. It's giving you a NEW CWnd which is attached to that same HWND. It is NOT VALID to cast that to a CHFTAppBView because this new CWnd is not a 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));

Again, CWnd::FromHandle returns a pointer to a new CWnd which knows nothing about CHFTAppBView. You are telling the compiler "trust me, this pointer is to a CHFTAppBView object!" Except it is NOT. It is a pointer to a CWnd attached to a HWND that is a CHFTAppBView.

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

This STILL wouldn't do anything. First of all, DYNAMIC_DOWNCAST returns a pointer to CHFTAppBView so right there, you're calling the MFC RTTI functions but not doing anything with the result. But even if you did save the result, it wouldn't help. You would be trying to convert a generic CWnd into something it is not.

I'll try to explain this one more time: When you create your view, MFC creates a CHFTAppBView object which is associated with the HWND of the view. When you call CWnd::FromHandle passing in the HWND of the view, MFC creates a NEW and distinct CWnd instance which points to that same HWND - that second CWnd is NOT CHFTAppBView and the HWND knows nothing about your MFC classes, views, documents or anything else.

You are trying to take the CWnd * that CWnd::FromHandle returns and hammer it into a CHFTAppBView *. No matter how hard you try, this will not work. All you can ever get will be a CWnd * and nothing else.

As a sidenote, you also cannot pass MFC objects from one thread to another, so passing the original CHFTAppBView * will cause weird issues to crop up, and may lead to hard to track errors.

Update 2:

You ask "Why can't a CWnd object be cast to a window class that derrives from CWnd?"

Let's start from CWnd::FromHandle shall we?

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

This leads us to CHandleMap::FromHandle. The code for that is a bit complicated and posting it here won't help, so let's not clutter things up. But here is what the function does, conceptually:

If a handle is found in the map, it returns it; otherwise, it creates a new CWnd and makes that CWnd point to the HWND you passed in. It then returns a pointer to that CWnd to you. But notice, this is ONLY a CWnd. Nothiong else. So you see, you cannot just convert that returned CWnd to something else - even if the something else is derived from a CWnd - because what you have is just a CWnd and nothing more.

Let's say you have a wife. She is very beautiful and you love her very much. You carry a picture of her in your wallet. Now, when you meet someone and they ask if you are married, you take out your wallet and proudly show them her picture. The person to who you're talking do doesn't think that you are married to the picture. And you don't think that you can magically "convert" the picture into your wife.

The situation here is somewhat similar. CWnd::FromHandle gives you a "picture" of sorts. It's good for showing around, but not good for much else. You are asking "why can't I transform the picture to my wife?" The answer is because that's not how pictures work.