Rust调用cocoa库读取MacOS剪贴板无法获取完整字符串问题
Rust读取macOS剪贴板内容问题
问题背景
学习Rust过程中需要实现命令行读取并打印macOS剪贴板内容的功能,此前用Swift实现过相同逻辑,但在Rust中调用Cocoa接口时遇到问题:
- 最初版本代码打印返回的
NSArray内容时,只能拿到NSPasteboardItem对象的内存地址,无法获取实际字符串 - 调整代码后只能拿到文本的第一个UTF-8字符,复制
World时循环5次每次都输出W,不知道如何遍历NSString的UTF8String()方法返回的*const i8指针指向的完整文本
参考可运行Swift代码
import Foundation import AppKit let pasteboard = NSPasteboard.general func reload() -> [String]{ var clipboardItems: [String] = [] for element in pasteboard.pasteboardItems! { if let str = element.string(forType: NSPasteboard.PasteboardType(rawValue: "public.utf8-plain-text")) { clipboardItems.append(str) } } return clipboardItems; } // 循环读取剪贴板内容 while true { let firstClipboardItem = reload() print(firstClipboardItem); sleep(1); }
初始错误Rust代码
use cocoa::appkit::{NSApp, NSPasteboard, NSPasteboardReading, NSPasteboardTypeString}; use cocoa::foundation::NSArray; fn main() { unsafe { let app = NSApp(); let pid = NSPasteboard::generalPasteboard(app); let changec = pid.changeCount(); let pid_item = pid.pasteboardItems(); if pid_item.count() != 0 { let items = &*pid_item.objectAtIndex(0); println!("{:?}", items); } println!("{:?}", *pid.stringForType(NSPasteboardTypeString)); } }
运行输出:*<NSPasteboardItem: 0x6000021a3de0>*
更新后存在bug的Rust代码
use cocoa::appkit::{NSApp, NSPasteboard, NSPasteboardTypeString}; use cocoa::foundation::{NSArray, NSString}; fn main() { unsafe { let app = NSApp(); let pid = NSPasteboard::generalPasteboard(app); let changec = pid.changeCount(); let nsarray_ptr = pid.pasteboardItems(); if nsarray_ptr.count() != 0 { for i in 0..NSArray::count(nsarray_ptr) { let raw_item_ptr = NSArray::objectAtIndex(nsarray_ptr, i); let itm = raw_item_ptr.stringForType(NSPasteboardTypeString); for u in 0..itm.len() { let stri = itm.UTF8String(); println!("TEXT {:?}", *stri as u8 as char); } } } } }
运行输出(复制文本World时):
TEXT 'W' TEXT 'W' TEXT 'W' TEXT 'W' TEXT 'W'
问题原因
- 初始版本直接打印
objectAtIndex(0)返回的指针,拿到的是NSPasteboardItem对象本身,不是对象里存储的字符串值;直接调用stringForType返回的是NSString对象指针,直接打印只会输出对象地址,不会输出字符串内容。 - 第二版代码循环里每次都重新调用
UTF8String(),拿到的永远是指向字符串起始位置的指针,没有做指针偏移,所以每次解引用都只能拿到第一个字符。另外NSString的len()方法返回的是UTF-16代码单元长度,和UTF-8编码的字节数不一定相等,直接用这个值做循环边界也存在隐患。
正确实现方案
运行前需要在Cargo.toml中添加对应依赖,且仅支持macOS平台编译运行:
[dependencies] cocoa = "0.24" core-foundation = "0.9"
不需要手动逐字节遍历C字符串指针,Rust标准库的std::ffi::CStr已经提供了现成的C字符串转换方法,可以直接把UTF8String()返回的*const i8指针转为Rust的&str类型,代码如下:
use cocoa::appkit::{NSApp, NSPasteboard, NSPasteboardTypeString}; use cocoa::foundation::{NSArray, NSString}; use std::ffi::CStr; fn main() { unsafe { // 初始化NSApp上下文,Cocoa接口调用必须先执行这一步 let _app = NSApp(); let pasteboard = NSPasteboard::generalPasteboard(_app); let items = pasteboard.pasteboardItems(); let item_count = NSArray::count(items); if item_count == 0 { println!("剪贴板为空"); return; } for i in 0..item_count { let item = NSArray::objectAtIndex(items, i); let ns_string = item.stringForType(NSPasteboardTypeString); // 空指针判断,避免无文本内容时崩溃 if ns_string.is_null() { continue; } // 把C风格UTF8字符串指针转为Rust原生字符串 let c_str = CStr::from_ptr(ns_string.UTF8String()); match c_str.to_str() { Ok(content) => println!("剪贴板内容:{}", content), Err(_) => println!("读取到非UTF-8编码内容,无法解析") } } } }
如果需要手动遍历指针实现字符读取,只需要在拿到初始指针后每次循环做对应偏移即可,注意要使用UTF-8实际字节长度作为循环边界:
let utf8_ptr = ns_string.UTF8String(); // 先获取C字符串的UTF-8字节长度 let c_str_len = CStr::from_ptr(utf8_ptr).to_bytes().len(); for u in 0..c_str_len { // 指针偏移u个字节位置后再解引用 let ch = *(utf8_ptr.add(u)) as u8 as char; print!("{}", ch); }
内容的提问来源于stack exchange,提问作者X01
相关产品推荐
相关产品推荐

