【问题标题】:Inconsistent error returning from 2 winapi calls从 2 个 winapi 调用返回的错误不一致
【发布时间】:2016-04-06 23:30:58
【问题描述】:

这是我的源代码:

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
}

当我在 main 的第 1 行初始化 message 时,一切都很好,但是当我开始复制文本时,事情开始变得有点奇怪。

get_data() 中的OpenClipboard 函数,我从GetLastError 得到的错误可能是6 (ERROR_INVALID_HANDLE) 或5 (ERROR_ACCESS_DENIED),原因不明。

GetClipboardData 函数中,我始终会从GetLastError 得到错误代码1418 (ERROR_CLIPBOARD_NOT_OPEN)。你会认为我会从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!

【问题讨论】:

  • 作为user32kernel32 crates 的作者,我可以自信地说没有互操作。它们是直接链接到系统库的原始直接调用。零包装,相当于在 C 中使用它们。
  • @retep998 这是一个奇怪的问题。我什至让代码进入无限循环,这取决于我是否有println!。某处有什么东西被弄脏了……
  • 呃,别管我的最后一件事了。正如@DavidHeffernan 所指出的,println! 最终必须调用改变 GetLastError 的东西。在 WinAPI 函数和对 GetLastError 的调用之间使用 println! 是个坏主意。
  • @Shep 我希望在调用函数之前准备好参数
  • @DavidHeffernan 我有类似WinApiCall(); println!(); GetLastError(); 的东西,事后看来这是一件非常愚蠢的事情^_^。这当然让我感到困惑。根据GetLastError 的说法,在纠正了我自己的错误后,我仍然看到“打开”剪贴板未打开的问题。

标签: winapi rust


【解决方案1】:

这是我的猜测:系统正在强制关闭您打开的剪贴板句柄。

据我所知,剪贴板是一个真正全局共享资源,即使跨进程也是如此。我无法追踪OpenClipboard 函数的存在时间,但根据 MSDN,它至少可以在 Windows 2000 上运行。如果它实际上起源于 Windows 98 / 95 甚至在此之前,我不会感到惊讶。剪贴板是一个古老的概念!

然而,一个概念上的问题出现了:如果一个程序打开剪贴板却忘记关闭它怎么办?然后你的整个系统将被阻止使用剪贴板,这将是非常糟糕的。我相信(没有证据)操作系统会跟踪您打开剪贴板的次数,并决定在您占用剪贴板时将其取回。

当您运行您的程序时,您可以看到它被固定在 100% 的 CPU 上,因为您已经构建了一个繁忙的循环,大部分时间剪贴板都处于打开状态。这足以触发一个简单的滥用启发式。将 (::std::thread::sleep_ms(100)) 添加到循环的末尾似乎会使一切表现得更好。

但是,您仍然偶尔会收到错误代码 5 (ERROR_ACCESS_DENIED)。同样,我认为这是由剪贴板的全局共享性质引起的。某些 other 进程由于某种原因打开了剪贴板,而您无法拥有它。正确的做法是处理无法始终立即访问资源的问题。

进行这些更改似乎可以让您的程序正常运行,但还有更多工作要做:

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 可以在剪贴板更改时通知消息循环。这可能是正确的解决方案,但确实需要您创建一个窗口(即使它仅用于消息)。

【讨论】:

  • 我确实对 PhantomData 有疑问,为什么需要它?
  • @Vikaton 我在PhantomData的每次使用中添加了基本解释cmets;其中哪些应该更明确?
  • struct Clipboard&lt;PhantomData&lt;()&gt;)的声明
  • @Vikaton 答案有点长,所以我posted it separately
  • gist.github.com/Vikaton/a02845839d23af2d5286 我一直收到错误 1418 :(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-03
  • 2021-01-27
相关资源
最近更新 更多