如何使用Rust和winapi板条箱在Windows中为事件创建订阅?

How to create a subscription for events in Windows using Rust and the winapi crate?

我正在尝试使用Winapi板条箱中的EvtSubscribe订阅Windows事件,但是却获得了ERROR_INVALID_PARAMETER

我在Rust中找不到示例,但确实找到了C示例。

我的产生ERROR_INVALID_PARAMETER的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
fn main() {
    unsafe {
        let mut callback: winapi::um::winevt::EVT_SUBSCRIBE_CALLBACK = None;
        let mut session = std::ptr::null_mut();
        let mut signal_event = std::ptr::null_mut();

        let mut bookmark = std::ptr::null_mut();
        let mut context = std::ptr::null_mut();
        let channel_path ="Security";
        let channel_path: winnt::LPWSTR = to_wchar(channel_path);
        let query ="Event/System[EventID=4624]";
        let query: winnt::LPWSTR = to_wchar(query);

        let event_handle = winevt::EvtSubscribe(
            session,
            signal_event,
            channel_path,
            query,
            bookmark,
            context,
            callback,
            winevt::EvtSubscribeStartAtOldestRecord,
        );

        //println!("{:?}", &event_handle);
        println!("{:?}", &winapi::um::errhandlingapi::GetLastError());
    } //unsafe end
}

fn to_vec(str: &str) -> Vec<u16> {
    return OsStr::new(str)
        .encode_wide()
        .chain(Some(0).into_iter())
        .collect();
}

fn to_wchar(str: &str) -> *mut u16 {
    return to_vec(str).as_mut_ptr();
}


EvtSubscribe的文档说明:

SignalEvent

[...] This parameter must be NULL if the Callback parameter is not
NULL.

Callback

[...] This parameter must be NULL if the SignalEvent parameter is
not NULL.

这里未声明的含义是,必须提供这些参数之一。明确禁止同时传递这两个参数,但是两者都不传递是没有意义的,否则,您的代码将无法接收该事件。

传递这些值之一将导致代码开始工作。

从编辑上来说,这是一个很好的例子,说明Rust枚举本来可以更好地为API建模。这将清楚表明这两个选项是互斥的,并且是必需的:

1
2
3
4
enum Subscriber {
    EventObject(HANDLE),
    Callback(EVT_SUBSCRIBE_CALLBACK),
}

偶然地,您对to_wchar的实现是不正确的,并可能导致内存不安全。 to_vec分配内存,您获取一个指向它的指针,然后释放该内存,从而创建了一个悬空指针。错误指针由unsafe块内部的C代码读取-部分原因需要unsafe

您要么需要使用mem::forget,如如何将Rust`Vec < T >`暴露给FFI所示? (然后您需要防止以某种方式泄漏内存),或者您需要引用数据而不是使用原始指针。