这是我的源代码:
extern crate user32;
extern crate kernel32;
use std::io::prelude::*;
use std::net::TcpStream;
use std::ptr;
fn main() {
let mut message = get_data();
loop {
if message != get_data() {
let mut client = TcpStream::connect("127.0.0.1:8080").unwrap();
message = get_data();
println!("{}", message);
let _ = client.write(message.as_bytes());
println!("Sent!");
}
}
}
fn get_data() -> String {
let value: String;
unsafe {
let open = user32::OpenClipboard(ptr::null_mut());
if open == 0 {
println!("{}", kernel32::GetLastError());
user32::CloseClipboard();
return "NULL".to_string()
}
let var = user32::GetClipboardData(13 as u32);
if var.is_null() {
println!("{}", kernel32::GetLastError());
user32::CloseClipboard();
return "NULL".to_string()
}
let data = kernel32::GlobalLock(var) as *mut u16;
let len = rust_strlen16(data);
let raws = std::slice::from_raw_parts(data, len);
value = String::from_utf16_lossy(raws);
kernel32::GlobalUnlock(var);
user32::CloseClipboard();
}
value
}
#[inline(always)] // used from clipboard-win, not mine
unsafe fn rust_strlen16(buff_p: *mut u16) -> usize {
let mut i: isize = 0;
while *buff_p.offset(i) != 0 {
i += 1;
}
return i as usize
}
当我在message
的第1行初始化main
时,一切都很好,但是当我开始复制文本时,事情开始变得有些奇怪。
来自OpenClipboard
中的get_data()
函数,我将从GetLastError
获得的错误为6(ERROR_INVALID_HANDLE
)或5(ERROR_ACCESS_DENIED
)一个未知的原因。
从GetClipboardData
函数我会始终从ERROR_CLIPBOARD_NOT_OPEN
获得错误代码1418(GetLastError
)。您会认为我从OpenClipboard
获取错误代码6或5会收到此错误,但是它不能打印1418因为它无法在函数中走得那么远,对吧?我可能错了,但这就是我的看法。
你应该注意的是,在循环中比较message
时会出现这些错误,但是if
语句仍在继续,并且消息被分配了实际复制的数据,这里是一些示例输出
1418 // this is when message and get_data() are being checked
println!("{}", result); // this is the actual copied data which is message
Sent! // sent!
5
NULL
Sent!
println!("Got Connection");
Sent!
答案 0 :(得分:3)
以下是关于发生了什么的猜测:系统强行关闭打开的剪贴板句柄。
据我所知,剪贴板是真正的全局共享资源,即使是跨进程也是如此。我无法追踪OpenClipboard
函数的存在时间,但根据MSDN,它至少可以在Windows 2000上运行。如果它真的起源于Windows 98/95甚至在此之前,我不会感到惊讶。剪贴板是一个古老的概念!
然而,出现了一个概念性问题:如果一个程序打开剪贴板并忘记关闭它会怎么样?然后你的整个系统将无法使用剪贴板,这将是非常可怕的。我相信(通过没有证明)操作系统会跟踪您打开剪贴板的数量,并决定在您使用剪贴板时从中取回剪贴板。
运行程序时,您可以看到它与100%CPU挂钩,因为您构建了一个繁忙的循环,大部分时间都是剪贴板打开的。这足以触发一个简单的滥用启发式方法。将(::std::thread::sleep_ms(100)
)添加到循环的末尾似乎可以使一切行为更好。
但是,您仍会偶尔收到错误代码5(ERROR_ACCESS_DENIED
)。我再次相信这是由剪贴板的全局共享性质引起的。某些其他进程由于某种原因打开了剪贴板而您无法拥有它。正确的做法是无法始终立即访问资源。
进行这些更改似乎可以使您的计划正常运行,但还有更多工作要做:
extern crate user32;
extern crate kernel32;
use std::ptr;
use std::marker::PhantomData;
fn main() {
let mut sequence_val = None;
loop {
let clip = match Clipboard::open() {
Err(WinApiError(ERROR_ACCESS_DENIED)) |
Err(WinApiError(ERROR_CLIPBOARD_NOT_OPEN)) => {
println!("Someone else is using the clipboard; continuing");
continue;
},
Err(e) => panic!("Unknown error while opening the clipboard: {:?}", e),
Ok(c) => c,
};
let next_sequence_val = Some(clip.sequence_number());
if sequence_val != next_sequence_val {
println!("Clipboard advanced from {:?} to {:?}", sequence_val, next_sequence_val);
sequence_val = next_sequence_val;
let all_formats: Result<Vec<_>, _> = clip.formats().collect();
println!("Available formats: {:?}", all_formats);
let message = clip.get_text().expect("Cannot read from clipboard");
match message {
Some(message) => println!("Got clipboard text: {}", message),
None => println!("Clipboard did not contain textual data"),
}
}
::std::thread::sleep_ms(250);
}
}
#[derive(Debug, Copy, Clone)]
struct WinApiError(u32);
const ERROR_ACCESS_DENIED: u32 = 0x5;
const ERROR_CLIPBOARD_NOT_OPEN: u32 = 0x58A;
impl WinApiError {
fn from_global() -> Result<(), WinApiError> {
let val = unsafe { kernel32::GetLastError() };
if val != 0 {
Err(WinApiError(val))
} else {
Ok(())
}
}
}
// PhantomData is used here to prevent creating the struct with
// `Clipboard`.
struct Clipboard {
marker: PhantomData<()>,
}
const CF_UNICODETEXT: u32 = 13;
impl Clipboard {
fn open() -> Result<Clipboard, WinApiError> {
unsafe {
user32::OpenClipboard(ptr::null_mut());
try!(WinApiError::from_global());
Ok(Clipboard { marker: PhantomData })
}
}
fn formats(&self) -> ClipboardFormats {
ClipboardFormats { clip: PhantomData, fmt: 0 }
}
fn sequence_number(&self) -> u32 {
unsafe { user32::GetClipboardSequenceNumber() }
}
fn get_text(&self) -> Result<Option<String>, WinApiError> {
for fmt in self.formats() {
let fmt = try!(fmt);
if fmt == CF_UNICODETEXT {
unsafe {
let var = user32::GetClipboardData(CF_UNICODETEXT);
try!(WinApiError::from_global());
// I don't believe this lock is actually
// needed. In searching around, it only seems to
// be used when you call `GlobalAlloc` and then
// set the clipboard with that data.
// If it's needed, consider making another RAII
// type to handle automatically unlocking.
let data = kernel32::GlobalLock(var) as *mut u16;
try!(WinApiError::from_global());
let len = rust_strlen16(data);
let raws = std::slice::from_raw_parts(data, len);
let value = String::from_utf16_lossy(raws);
kernel32::GlobalUnlock(var);
try!(WinApiError::from_global());
return Ok(Some(value));
}
}
}
Ok(None)
}
}
impl Drop for Clipboard {
fn drop(&mut self) {
unsafe {
// Ignore failure to close as we can't really do anything
// about it
user32::CloseClipboard();
let _ = WinApiError::from_global();
}
}
}
unsafe fn rust_strlen16(buff_p: *mut u16) -> usize {
let mut i = 0;
while *buff_p.offset(i) != 0 { i += 1 }
i as usize
}
struct ClipboardFormats<'a>{
// PhantomData is used here to prevent outliving the opened
// clipboard
clip: PhantomData<&'a Clipboard>,
fmt: u32,
}
impl<'a> Iterator for ClipboardFormats<'a> {
type Item = Result<u32, WinApiError>;
fn next(&mut self) -> Option<Self::Item> {
let next_fmt = unsafe { user32::EnumClipboardFormats(self.fmt) };
if next_fmt == 0 {
match WinApiError::from_global() {
Ok(_) => None,
Err(e) => Some(Err(e)),
}
} else {
self.fmt = next_fmt;
Some(Ok(next_fmt))
}
}
}
即我创建了一个便捷类型WinApiError
,它自动检查GetLastError
并将其绑定到Rust标准Result
类型。这与try!
一起普遍使用。
我介绍了一个Clipboard
结构,以便有一个实现Drop
的类型。这使我们确信在完成剪贴板后总是关闭剪贴板。你可能想要添加一个close
方法,如果你想早点关闭它,它会非常明显和简单:
fn close(self) {
// Automatically dropped as it goes out of scope.
}
为了略微提高性能,我使用GetClipboardSequenceNumber
返回剪贴板的整数时间戳。这允许进行更轻量级的检查以了解剪贴板是否已更改,而不是每次都创建String
。它还会阻止您使用"NULL"
的魔术字符串。您可能仍希望跟踪最后一个字符串,以了解用户是否再次复制了相同的文本。
从剪贴板中检索数据时,您应该遍历所有可用格式并对您关心的第一个采取操作。虽然您只关心UCS-2格式,但我在尝试调试时添加了这个格式,所以我想我就离开了。
有一个更好的解决方案,而不仅仅是我没有实施的睡眠。剪贴板更改时,AddClipboardFormatListener
可以通知消息循环。这可能是正确的解决方案,但确实需要您创建一个窗口(即使它仅用于消息)。