使用 windows-sys crate 调用您的第一个 API
所以你想尝试调用一个简单的 Windows API。从哪里开始呢?让我们看看一个相对简单的 API,用于向线程池提交回调。您可以在这里阅读更多关于此 API 的信息。
第一步是添加对 windows-sys crate 的依赖,并指出您希望访问哪些功能
[dependencies.windows-sys]
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_sys::{Win32::Foundation::*, 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() { unsafe { } }
线程池 API 被建模为一组通过传统 C 风格 API 公开的“对象”。我们首先需要做的是创建一个工作对象
#![allow(unused)] fn main() { let work = CreateThreadpoolWork(Some(callback), std::ptr::null_mut(), std::ptr::null()); }
第一个参数是指向回调函数的指针。其余参数是可选的,您可以在我的 MSDN 线程池系列中阅读更多关于它们的信息。
由于此函数会分配内存,因此它可能会失败,这通过返回空指针而不是有效的 work 对象句柄来表示。我们将检查此条件并调用 GetLastError
函数以显示任何相关的错误代码
#![allow(unused)] fn main() { if work == 0 { println!("{:?}", GetLastError()); return; } }
回调本身必须是有效的 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, 0); }
第二个参数指示我们是否希望取消任何尚未开始执行的挂起回调。此处传入 0
,表示 false,因此表示我们希望等待函数阻塞,直到所有提交的工作都已完成。届时,我们可以安全地关闭 work 对象以释放其内存
#![allow(unused)] fn main() { CloseThreadpoolWork(work); }
为了证明它可靠地工作,我们可以打印出计数器的值
#![allow(unused)] fn main() { let counter = COUNTER.read().unwrap(); println!("counter: {}", *counter); }
运行示例应该打印出如下内容
counter: 10
这是完整的示例以供参考。