当前位置: 首页 > article >正文

MFC中的窗口线程安全性与CWnd类

MFC中的窗口线程安全性与CWnd类

  • CWnd类
  • 线程限制的核心原因
    • 常见问题场景
    • 为什么不能简单地在其他线程操作CWnd
    • 处理方案
  • 正确的跨线程通信方法
    • 自定义消息
    • 补充
      • 参数传递的详细机制(WPARAM 和 LPARAM )
      • SendMessage对比PostMessage

CWnd类

CWnd(Class Window)是MFC中所有窗口类的基类,它的功能如下:
(1)封装了Windows窗口句柄(HWND)
(2)提供了窗口管理的基本功能
(3)实现了消息处理机制
(4)作为对话框、视图、控件等所有窗口元素的父类

线程限制的核心原因

MFC要求窗口对象必须在创建它们的线程中操作,原因:

  1. Windows系统的消息泵机制:
    Windows本身要求窗口消息必须在创建窗口的线程中处理
    每个线程有自己的消息队列
    跨线程发送消息虽然技术上可行,但直接操作窗口对象是危险的
  2. MFC的内部状态管理:
    MFC维护了许多线程特定的数据结构
    CWnd对象与线程特定的消息映射表相关联
    跨线程访问可能导致状态不一致
  3. 资源管理安全性:
    防止在多线程环境下对窗口资源的竞争访问
    确保窗口句柄的有效性在单线程上下文中维护

关键词:消息循环、线程同步

常见问题场景

开发者常遇到的问题是:
(1)在工作线程(如下载线程)中尝试直接更新UI
(2)在非创建线程中调用CWnd成员函数(如SetWindowText、EnableWindow等)
(3)试图跨线程创建或销毁窗口对象

为什么不能简单地在其他线程操作CWnd

// 在工作线程中直接更新UI - 错误示例!
void CWorkerThread::UpdateProgress(int nProgress)
{
    m_pProgressCtrl->SetPos(nProgress); // 可能导致崩溃
}

这种操作可能不会立即崩溃,但会导致:
(1)难以调试的随机崩溃
(2)UI响应迟缓
(3)资源泄漏
(4)消息处理混乱

处理方案

对于需要频繁更新UI的后台任务,可以考虑:
(1)CWinThread:MFC的线程类,可以与主线程更好地协调
(2)定时器(SetTimer):主线程定期检查状态
(3)异步消息处理:结合消息和事件对象

正确的跨线程通信方法

自定义消息

(1)定义消息ID

#define WM_SEND_XXX (WM_USER+1)// WM_USER 是系统预留的自定义消息起点

作用:定义一个唯一的消息标识符,避免与系统消息冲突。
为什么用 WM_USER: 0x0400(WM_USER)
Windows 保留0x0000 - 0x03FF ,0x0400 (WM_USER) - 0x7FFF是可以自定义使用的范围

(2)定义自定义消息响应函数

class CMyClass: public CDialog{
private:
afx_msg LRESULT OnSend(WPARAM wParam, LPARAM lParam);// afx_msg 是 MFC 宏,无实际作用
}

关键点:
函数签名必须为 LRESULT (WPARAM, LPARAM)。
afx_msg 是 MFC 的标记宏,编译时会被移除,仅提示这是消息处理函数。

如何通过 WPARAM 和 LPARAM 传递参数,请看补充部分;

(3)注册消息映射(在消息映射表中)

BEGIN_MESSAGE_MAP(CMyClass, CDialogEx)
	...
	ON_MESSAGE(WM_SEND_XXX , &CMyClass::OnSend)
END_MESSAGE_MAP()

MFC 的消息映射机制: 在程序启动时,MFC 会生成一个消息映射表,将 WM_SEND_XXX 动态关联到 OnSend 函数。

(4)实现消息响应函数

LRESULT CMyClass::OnSend(WPARAM wParam, LPARAM lParam)
{
	//更新UI
	//调用CWnd成员函数
	//跨线程创建或销毁窗口对象
	//如:UpdateData() ;// 安全!此时在主线程上下文执行
	return 0;
}

UpdateData() 是 MFC 提供的一个函数
主要用于:
UpdateData(TRUE):从对话框控件读取数据到成员变量(如 m_strName)。
UpdateData(FALSE):将成员变量的值更新到对话框控件(如 CEdit 显示新文本)。
它是如何工作的?
UpdateData() 内部会遍历对话框的所有控件,并调用 CWnd 相关方法(如GetWindowText、SetWindowText),因此它 本质上是操作 UI 控件。

(5)发送消息

// 在工作线程中发送消息
SendMessage(WM_SEND_XXX, 0, 0);  // 同步阻塞
// 或
PostMessage(WM_SEND_XXX, 0, 0);  // 异步非阻塞
方法线程行为适用场景
SendMessage发送线程阻塞,等待处理需要即时响应的操作(如获取结果)
PostMessage发送线程立即返回UI 更新(推荐)

参考补充部分,有更详细的对比。

(6) 整个流程

sequenceDiagram:
participant 工作线程
participant 主线程消息队列
participant 主线程

工作线程->>主线程消息队列: PostMessage(WM_SEND_XXX)
主线程消息队列-->>主线程: 分发消息
主线程->>主线程: 执行OnSend()
主线程->>主线程: UpdateData(FALSE) 安全更新UI

补充

参数传递的详细机制(WPARAM 和 LPARAM )

afx_msg LRESULT OnSend(WPARAM wParam, LPARAM lParam);

参数传递的详细机制

  1. WPARAM 和 LPARAM 的本质
    在 32 位系统中,二者均为 32 位整数(unsigned int)。
    在 64 位系统中,二者为 64 位整数(size_t)。
  2. 设计用途:
    携带附加数据,可以是:
    WPARAM :整数值(如状态码、标志位)。
    LPARAM :指针(需保证指针有效性)。

潜在问题;

  1. 生命周期问题
    指针传递的对象如果是局部变量,要注意生命周期
    解决方法:使用堆分配,自己管理生命周期或者使用共享指针shared_ptr;
  2. 类型安全
    博客园——https://www.cnblogs.com/lucky-bubble/p/18286130

SendMessage对比PostMessage

  1. SendMessage:同步阻塞

主线程处理时机 立即处理(若主线程处于消息循环中):
主线程会中断当前任务,直接调用消息处理函数(如 OnSendPacket),处理完成后才返回结果给发送线程。

若主线程忙(如卡在耗时操作):
发送线程会一直阻塞,直到主线程进入消息循环并处理完该消息。

子线程(发送线程)发完后,阻塞等待主线程处理完后,子线程再继续执行;

特性说明
处理时机主线程立即处理(类似直接函数调用)
线程阻塞发送线程暂停,等待主线程处理完成
返回值可通过 LRESULT 获取处理结果
适用场景需要即时响应的操作
  1. PostMessage:异步非阻塞

主线程处理时机 延迟处理:
消息被放入主线程的消息队列,主线程会在下一次消息循环(如 GetMessage/PeekMessage)时处理。

若主线程忙:
消息会积压在队列中,直到主线程空闲时处理(不会阻塞发送线程)。

特性说明
处理时机主线程稍后处理(取决于消息队列的调度)
线程阻塞发送线程继续执行,不等待
返回值仅返回是否成功投递(BOOL),无法直接获取处理结果
适用场景UI 更新、后台任务通知等无需即时响应的操作

特性对比表格

特性SendMessagePostMessage
处理时机立即(中断当前任务)延迟(下次消息循环)
线程阻塞发送线程等待发送线程不等待
返回值有(LRESULT)无(仅投递成功与否)
消息队列不经过队列,直接调用处理函数通过消息队列异步处理
典型用途同步操作(如获取文本框内容)异步通知(如更新进度条)

处理流程对比

SendMessage 流程

子线程->>主线程: SendMessage(WM_MSG)
主线程->>主线程: 立即执行OnMsg()
主线程-->>子线程: 返回LRESULT
子线程->>子线程: 继续执行后续代码

PostMessage 流程

子线程->>主线程消息队列: PostMessage(WM_MSG)
子线程->>子线程: 立即继续执行
主线程消息队列-->>主线程: 下次GetMessage时处理
主线程->>主线程: 执行OnMsg()

如何选择
用 SendMessage :
需要同步获取结果(如读取控件值)。
确保操作原子性(如防止数据竞争)。

用 PostMessage :
只需通知主线程更新 UI(如进度条)。
避免阻塞工作线程(如网络下载线程)。

原文地址:https://blog.csdn.net/weixin_44050362/article/details/146497270
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/613460.html

相关文章:

  • 从 YOLO11 模型格式导出到TF.js 模型格式 ,环境爬坑,依赖关系已经贴出来了
  • 智慧养老时代:老年人慢性病预防与生活方式优化
  • 【今日EDA行业分析】2025年3月28日
  • 基于扩散模型的光照编辑新突破:IC-Light方法解析与优化
  • DeepSeek大模型应用开发新模式
  • 智能舵机:AI融合下的自动化新纪元
  • ADZS-ICE-2000和AD-ICE2000仿真器在线升级固件
  • Error:Flash Download failed
  • AIGC-广告助手创作智能体完整指令(DeepSeek,豆包,千问,Kimi,GPT)
  • Ubuntu与CentOS操作指令的主要区别详解
  • 【力扣hot100题】(004)盛水最多的容器
  • 【go微服务】如何快速掌握grpc开发
  • 计算机二级WPS Office第十二套WPS演示
  • ETL中数据转换的三种处理方式
  • 职场新人面对不懂的问题应该如何寻求帮助?
  • 基于Dockerfile以docker运行java(可快速替换jar包实现工程更新)
  • 2007-2019年各省地方财政一般公共服务支出数据
  • Proxmox配置显卡直通
  • 算法基础_基础算法【快速排序 + 归并排序 + 二分查找】
  • centOS 7.9 65bit 修复Openssh漏洞