3

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

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

阅读此内容后:http: //msdn.microsoft.com/en-us/library/h14y172e%28v=VS.100%29.aspx,我意识到您可能无法将指向视图类的指针传递给工作人员线程。所以我专注于hWnds。在 OnTestConnect 块中,返回一个有效的 hWnd 作为指向新视图窗口的有效指针。

这是代码(来自 App 类):

struct THREADPARMS
{
    HWND hWndView;
    int test;   
};

(注意,我尝试将结构定义为 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 );
}

第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 是有效的。单步执行代码并查看 Debugger Watch 中的指针统计信息会显示一个引用 CView 类的 helathy 指针。但它不会返回健康的 hWnd。然后 Debugger Watch 说“无法评估”hWnd 内存地址并说“未使用=0”。但是,通过 IsWindow 运行它会返回 true。去搞清楚。尝试使用该句柄向 CView 窗口发送 CView 消息会被忽略。为什么 GetAvtiveView 会返回一个有效指针,但该类中的 hWnd 会返回垃圾?

第 3 部分

经过更多的挖掘,结果表明 HWND 是有效的,尽管 hwnd 变量显示 'unused=???' 在监视窗口中。我认为它是有效的,因为线程代码中收到的 hWnd 与附加到主代码中 pView 指针的 hWnd 匹配。从中获取 hWnd 的 pView 指针也是有效的,因为 Watch 通过返回它所代表的 CView 类的名称将其识别为有效的 CView 类指针。然而,仍然存在两个问题。一个是即使系统将有效的 hWnd 发送回 CView 窗口 (pView->m_hWnd),SendMessage(pView->m_hWnd, ID_FILE_PRINT_PREVIEW, 0, 0,) 也拒绝工作。系统忽略它。我希望在新创建的视图窗口中运行 FilePrintPreview 命令。

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 的消息映射...

    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 是。

无论如何,这解决了这个问题。非常感谢所有提供建议的人——Nik、Scott、Ulrich 和 Mark。您的明智建议非常有帮助。

4

1 回答 1

5

你可以打电话CHFTAppBView::FromHandle(hWnd),但你得到的不是指向 a 的指针CHFTAppBView;它是指向 a 的指针CWnd。通过强制转换它,您是在告诉编译器“相信我,这实际上是一个指向 a 的指针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返回一个指向 a 的指针CWnd,而不是其他任何东西。它不会给你一个指向你原来的CHFTAppBView. 它给了你一个 CWnd的,它附加到同一个HWND. 将其转换为 a是无效CHFTAppBView的,因为这个 newCWnd不是 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));

同样,CWnd::FromHandle返回一个指向new CWnd的指针,该指针对CHFTAppBView. 你是在告诉编译器“相信我,这个指针指向一个CHFTAppBView对象!” 除非它不是。它是一个指向CWnd附加到 a的指针HWND,即 a CHFTAppBView

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

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

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

您正在尝试将返回的CWnd *返回CWnd::FromHandle并将其锤击成CHFTAppBView *. 不管你怎么努力,这都行不通。你所能得到的只是a 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. 代码有点复杂,在这里发布也无济于事,所以我们不要把事情弄得乱七八糟。但从概念上讲,这是该函数的作用:

如果在映射中找到句柄,则返回它;否则,它会创建一个新的CWndCWnd指向HWND你传入的那个。然后它返回一个指向CWnd你的指针。但请注意,这只是一个CWnd. 没有别的了。所以你看,你不能只是将返回CWnd的东西转换成其他东西——即使其他东西是从 a 派生的CWnd——因为你所拥有的只是 aCWnd而已。

假设你有一个妻子。她很漂亮,你很爱她。你的钱包里放着她的照片。现在,当你遇到某人并且他们问你是否结婚时,你拿出你的钱包并自豪地向他们展示她的照片。与您交谈的人并不认为您已与照片结婚。而且你不认为你可以神奇地将照片“转换”成你的妻子。

这里的情况有些相似。CWnd::FromHandle为您提供各种“图片”。这对四处展示很有好处,但对其他很多事情都没有好处。您在问“为什么我不能将图片转换为我的妻子?” 答案是因为图片不是这样工作的。

于 2013-05-21T02:22:55.580 回答