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

