SetClipboardViewer将窗口加入剪贴板查看器链,剪贴板变化时系统发送WM_DRAWCLIPBOARD消息;需正确处理该消息并转发给下一查看器,窗口销毁时须调用ChangeClipboardChain注销,否则导致链断裂。

Windows平台用 SetClipboardViewer 监听剪贴板变化
Windows原生支持剪贴板变更通知,但不是靠轮询,而是通过消息机制。核心是调用 SetClipboardViewer 将当前窗口句柄注册为剪贴板查看器,之后系统会向该窗口发送 WM_DRAWCLIPBOARD 消息——只要剪贴板内容被修改(包括文本、图片、文件列表等),这个消息就会触发。
注意:必须确保窗口过程(WndProc)里正确处理该消息,并把消息转发给链表中下一个查看器(用 NextClipboardViewer 全局变量保存),否则其他程序的监听会失效。
常见错误:
- 忘记调用 ChangeClipboardChain 在窗口销毁时注销自己,导致后续剪贴板消息链断裂
- 在非UI线程(如 std::thread)中调用 SetClipboardViewer,会失败且无提示
- 没有检查 OpenClipboard(NULL) 是否成功就直接读取,容易因权限或占用导致读取为空
读取剪贴板内容前必须先打开并检查格式
收到 WM_DRAWCLIPBOARD 后不能直接读,得先调用 OpenClipboard,再用 EnumClipboardFormats 或 IsClipboardFormatAvailable 判断可用格式,最后按需调用 GetClipboardData。不同格式对应不同内存结构:
-
CF_TEXT/CF_UNICODETEXT→ 用GlobalLock得到char*或wchar_t*,记得GlobalUnlock -
CF_HDROP(拖放文件路径)→ 用DragQueryFile获取路径数量和单个路径 -
CF_BITMAP→ 返回HBITMAP,需用 GDI 函数进一步处理
容易踩的坑:
- 把 CF_UNICODETEXT 当作 CF_TEXT 处理,导致乱码
- 忘记 CloseClipboard,后续所有剪贴板操作都会阻塞
- 在多字节项目中直接用 CF_UNICODETEXT 而不转码,std::string 构造失败
为什么 GetClipboardSequenceNumber 不适合做变化判断
有人想用 GetClipboardSequenceNumber 轮询检测变化,这在技术上可行但实际不可取。该函数返回单调递增整数,每次剪贴板更新加一,但它不保证跨进程可见性——某些 UWP 应用或沙盒环境更新剪贴板时不会触发序列号变更;更严重的是,它无法告诉你“变了什么”,仍需打开剪贴板读内容,反而增加竞争风险。
立即学习“C++免费学习笔记(深入)”;
真正可靠的做法仍是消息驱动:
- 注册查看器后,只在 WM_DRAWCLIPBOARD 触发时读取
- 若需过滤特定格式(比如只关心文本),在消息处理中先调用 IsClipboardFormatAvailable(CF_UNICODETEXT) 再操作
- 避免在消息处理中做耗时操作(如写文件、网络请求),可 post 到工作线程处理
跨平台方案不存在,Linux/macOS 需换思路
C++ 标准库不提供剪贴板抽象,Qt 或 wxWidgets 等框架封装了跨平台接口,但底层仍是各自系统的适配。Linux 上通常依赖 X11 的 XConvertSelection + PropertyNotify 事件,或现代 Wayland 下通过 clipboard-manager D-Bus 接口;macOS 则用 NSPasteboard 的 addItemsToPasteboard: 通知或 KVO 监听。
如果你坚持纯 C++ 且不引入 GUI 框架:
- Windows:只能走 Win32 API(即前述方案)
- Linux:基本要绑定 X11 或 systemd user session(DBus)
- macOS:必须用 Objective-C++ 混合编译,调用 Cocoa API
没有零依赖、无GUI、全平台统一的 C++ 剪贴板监控方案


















