键盘快捷键

在章节间导航

S/ 在书中搜索

? 显示此帮助

Esc 隐藏此帮助

使用 Windows crate 调用你的第一个 API

所以你想尝试调用一个简单的 Windows API。从哪里开始呢?让我们看看一个相对简单的 API,用于向线程池提交回调。您可以在这里阅读更多关于此 API 的信息

第一步是添加对 windows crate 的依赖,并指出你希望访问哪些功能

[dependencies.windows]
version = "0.52"
features = [
    "Win32_Foundation",
    "Win32_System_Threading",
]

为什么要这两个功能?线程池 API 定义在 Win32::System::Threading 模块中,我们还将使用 Win32::Foundation 模块中的一些定义。如果你不确定,任何给定 API 的文档都会提供有用的注释,指示需要哪些功能。例如,这是 WaitForThreadpoolWorkCallbacks 的文档,你可以看到它依赖于这两个功能,因为它定义在 Win32::System::Threading 模块中,并且依赖于 Win32::Foundation 模块中定义的 BOOL

Cargo 现在将处理繁重的工作,跟踪依赖项并确保导入库存在,这样我们就可以在 Rust 中简单地调用这些 API,而无需任何进一步的配置。我们可以使用 use 声明使这些 API 更易于访问

#![allow(unused)]
fn main() {
use windows::{core::Result, Win32::System::Threading::*};
}

为了“证明”代码有效但又保持简单,我们只使用线程池将计数器递增若干次。这里我们可以使用读写锁来安全地多线程访问计数器变量

#![allow(unused)]
fn main() {
static COUNTER: std::sync::RwLock<i32> = std::sync::RwLock::new(0);
}

对于这个例子,我将只使用一个简单的 main 函数和一个大的 unsafe 块,因为这里几乎所有东西都是 unsafe。为什么会这样?因为 windows crate 允许你调用外部函数,而这些函数通常被认为是 unsafe 的。

fn main() -> Result<()> {
    unsafe {
        
    }

    Ok(())
}

线程池 API 被建模为一组通过传统 C 风格 API 公开的“对象”。我们首先需要做的是创建一个工作对象

#![allow(unused)]
fn main() {
let work = CreateThreadpoolWork(Some(callback), None, None)?;
}

第一个参数是指向回调函数的指针。其余参数是可选的,您可以在我的 MSDN 线程池系列中阅读更多关于它们的信息。

回调本身必须是有效的 C 风格回调,符合线程池 API 预期的签名。这是一个简单的回调,它将增加计数

#![allow(unused)]
fn main() {
extern "system" fn callback(_: PTP_CALLBACK_INSTANCE, _: *mut std::ffi::c_void, _: PTP_WORK) {
    let mut counter = COUNTER.write().unwrap();
    *counter += 1;
}
}

参数可以安全地忽略,但有时确实很有用。此时,我们有一个有效的工作对象,但什么都没有发生。为了启动一些“工作”,我们需要将工作对象提交给线程池。您可以根据需要多次提交,所以让我们继续做十次

#![allow(unused)]
fn main() {
for _ in 0..10 {
    SubmitThreadpoolWork(work);
}
}

现在您可以预期回调会并发运行,因此上面使用了 RwLock。当然,有了所有的并发性,我们需要某种方式来判断工作何时完成。这就是 WaitForThreadpoolWorkCallbacks 函数的工作

#![allow(unused)]
fn main() {
WaitForThreadpoolWorkCallbacks(work, false);
}

第二个参数表示我们是否希望取消任何尚未开始执行的待处理回调。因此,在此处传递 false 表示我们希望等待函数阻塞,直到所有提交的工作都已完成。届时,我们可以安全地关闭工作对象以释放其内存

#![allow(unused)]
fn main() {
CloseThreadpoolWork(work);
}

为了证明它可靠地工作,我们可以打印出计数器的值

#![allow(unused)]
fn main() {
let counter = COUNTER.read().unwrap();
println!("counter: {}", *counter);
}

运行示例应该打印出如下内容

counter: 10

这是完整示例供参考