You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MFC单文档项目使用CImage在OnDraw绘制PNG移动对象时崩溃求助

崩溃原因排查
  • 第一处致命错误:OnDraw方法内调用Invalidate()会触发窗口强制重绘,导致OnDraw被无限递归调用,最终栈溢出崩溃,这是你程序崩溃的核心原因。
  • 第二处错误:CImage加载资源未做结果校验,构造函数中加载background.png如果出现路径不匹配、文件损坏、资源不存在的情况,Load调用失败后BackgdoundImage处于无效状态,调用Draw方法会直接触发访问异常。小球对应的CImage也存在相同的未校验问题。
  • 第三处潜在问题:相对路径加载资源不可靠,MFC单文档项目调试阶段的默认工作目录是项目工程目录(和.vcxproj同目录),发布后运行的工作目录是exe所在目录,路径不匹配会直接导致图片加载失败。
修复步骤
  1. 直接删除OnDraw中的Invalidate()调用,重绘触发逻辑移到定时器中实现。
  2. 所有CImage的Load调用增加返回值校验,调试阶段可以用断言快速定位加载失败问题,发布阶段增加降级逻辑避免崩溃。
  3. 调试阶段把所有图片放到工程根目录,发布时把图片放到exe同目录,或者直接把图片嵌入到程序资源中,用资源ID加载彻底避免路径问题。
可移动对象的正确绘制实现方案

游戏类场景的绘制逻辑遵循「逻辑更新→触发重绘→画面渲染」的分层逻辑,具体实现如下:

1. 增加定时器控制刷新频率

在View的初始化方法中启动定时器,设置16ms触发一次(对应约60帧的刷新率,满足流畅动画需求):

void CcpPongView::OnInitialUpdate()
{
    CView::OnInitialUpdate();
    SetTimer(1, 16, nullptr);
}

2. 定时器中执行游戏逻辑+触发重绘

重写OnTimer方法,先执行所有游戏逻辑(小球移动、挡板移动、碰撞检测、分数计算),再调用Invalidate(FALSE)触发重绘,第二个参数传FALSE跳过系统默认背景擦除,减少画面闪烁:

void CcpPongView::OnTimer(UINT_PTR nIDEvent)
{
    if (nIDEvent == 1) {
        // 执行游戏核心逻辑
        ball.UpdatePosition();
        leftPaddle.UpdatePosition();
        rightPaddle.UpdatePosition();
        CheckCollision();
        // 触发画面重绘
        Invalidate(FALSE);
    }
    CView::OnTimer(nIDEvent);
}

3. OnDraw中只做画面渲染

OnDraw中只负责按Z序绘制所有元素,不要加任何逻辑更新、重绘触发相关的代码:

CcpPongView::CcpPongView() noexcept
{
    // 加载背景资源并校验
    HRESULT hr = BackgdoundImage.Load(_T("background.png"));
    ASSERT(SUCCEEDED(hr));
    // 加载小球资源并校验
    hr = ball.getBallImage().Load(_T("ball.png"));
    ASSERT(SUCCEEDED(hr));
    // 加载挡板资源并校验
    hr = leftPaddle.GetImage().Load(_T("paddle.png"));
    ASSERT(SUCCEEDED(hr));
    hr = rightPaddle.GetImage().Load(_T("paddle.png"));
    ASSERT(SUCCEEDED(hr));
}

void CcpPongView::OnDraw(CDC* dc)
{
    FlickerFreeDC pDC(dc);
    CcpPongDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;
    
    CRect clientRect;
    GetClientRect(&clientRect);
    // 绘制背景
    if (BackgdoundImage) {
        BackgdoundImage.Draw(pDC->GetSafeHdc(), 0, 0, clientRect.Width(), clientRect.Height());
    } else {
        // 加载失败降级:黑色填充背景
        pDC.FillSolidRect(&clientRect, RGB(0,0,0));
    }
    // 绘制左右挡板
    if (leftPaddle.GetImage()) {
        leftPaddle.GetImage().Draw(pDC->GetSafeHdc(), leftPaddle.position.x, leftPaddle.position.y, 15, 100);
    }
    if (rightPaddle.GetImage()) {
        rightPaddle.GetImage().Draw(pDC->GetSafeHdc(), rightPaddle.position.x, rightPaddle.position.y, 15, 100);
    }
    // 绘制小球
    if (ball.getBallImage()) {
        ball.getBallImage().Draw(pDC->GetSafeHdc(), ball.position.x, ball.position.y, 25, 25);
    }
}

内容的提问来源于stack exchange,提问作者Cc4Dayz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 03:54:03