前言
本文主要涉及如何使用 Windows API 实现剪贴板监听器,以及如何在 Rust 中使用指针和内存管理来操作Windows API。
监听 Windows 剪贴板
Windows 剪贴板监听基本原理
Windows 有三种方法可以监视剪贴板的更改:最早的方法是创建剪贴板查看器窗口,Windows 2000 添加了查询剪贴板序列号的功能,Windows Vista 添加了剪贴板格式侦听器。对于新程序,建议使用剪贴板格式侦听器或剪贴板序列号,尤其是侦听器。
剪贴板格式侦听器是一个注册的窗口,它会在剪贴板内容发生更改时收到通知。窗口通过调用 AddClipboardFormatListener
函数注册为剪贴板格式侦听器,当剪贴板的内容发生更改时,会收到一条 WM_CLIPBOARDUPDATE
通知。
通过 CreateWindowEx
方法可以创建一个不可见的消息窗口,它只能收发消息,可以用该窗口注册一个剪贴板侦听器。
代码实现
Rust 最小代码
use std::{
ffi::OsStr,
os::windows::prelude::OsStrExt,
ptr::{null, null_mut},
};
use winapi::um::winuser::{
AddClipboardFormatListener, CreateWindowExW, GetMessageW, HWND_MESSAGE, WM_CLIPBOARDUPDATE,
};
fn main() {
// 创建消息窗口
let hwnd = unsafe {
CreateWindowExW(
0,
str_to_lpcwstr("STATIC").as_ptr(),
null(),
0,
0,
0,
0,
0,
HWND_MESSAGE,
null_mut(),
// wnd_class.hInstance,
null_mut(),
null_mut(),
)
};
if hwnd == null_mut() {
panic!("CreateWindowEx failed");
}
// 添加剪贴板监听器
unsafe { AddClipboardFormatListener(hwnd) };
// 监听窗口消息
let mut msg = unsafe { std::mem::zeroed() };
loop {
let ret = unsafe { GetMessageW(&mut msg, hwnd, 0, 0) };
if ret != 1 {
break;
}
match msg.message {
WM_CLIPBOARDUPDATE => {
println!("WM_CLIPBOARDUPDATE ");
}
_ => (),
}
}
}
fn str_to_lpcwstr(s: &str) -> Vec<u16> {
OsStr::new(s)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
复制代码
Rust 封装代码
use std::{
ffi::OsStr,
os::windows::prelude::OsStrExt,
ptr::{null, null_mut},
};
use winapi::{
shared::windef::HWND,
um::winuser::{
AddClipboardFormatListener, CreateWindowExW, GetMessageW, HWND_MESSAGE, MSG,
WM_CLIPBOARDUPDATE,
},
};
fn main() {
ClipboardListen::run(move || {
println!("clipboard updated.");
//TODO::剪贴板内容获取
});
}
pub struct ClipboardListen {}
impl ClipboardListen {
pub fn run<F: Fn() + Send + 'static>(callback: F) {
std::thread::spawn(move || {
for msg in Message::new() {
match msg.message {
WM_CLIPBOARDUPDATE => callback(),
_ => (),
}
}
});
}
}
pub struct Message {
hwnd: HWND,
}
impl Message {
pub fn new() -> Self {
// 创建消息窗口
let hwnd = unsafe {
CreateWindowExW(
0,
str_to_lpcwstr("STATIC").as_ptr(),
null(),
0,
0,
0,
0,
0,
HWND_MESSAGE,
null_mut(),
// wnd_class.hInstance,
null_mut(),
null_mut(),
)
};
if hwnd == null_mut() {
panic!("CreateWindowEx failed");
}
unsafe { AddClipboardFormatListener(hwnd) };
Self { hwnd }
}
fn get(&self) -> Option<MSG> {
let mut msg = unsafe { std::mem::zeroed() };
let ret = unsafe { GetMessageW(&mut msg, self.hwnd, 0, 0) };
if ret == 1 {
Some(msg)
} else {
None
}
}
}
impl Iterator for Message {
type Item = MSG;
fn next(&mut self) -> Option<Self::Item> {
self.get()
}
}
fn str_to_lpcwstr(s: &str) -> Vec<u16> {
OsStr::new(s)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
复制代码
总结
尽管最终代码只有短短不到百行,但实现过程异常曲折。
首先,相关资料相对匮乏,而且各种实现方式五花八门,甚至有使用定时器定期检查剪贴板内容差异的方法。
通过查看各种开源库的代码,最终选择了添加剪贴板监听器的方案。但由于个人不熟悉Windows API开发,对于为什么需要创建一个窗口来监听剪贴板,进行了大量的资料查阅和理解,但结果仍是一知半解(现在的理解是大概 Windows 万物皆窗口吧x)。