Passing closures into a Win32 FFI

I'm trying to use the Win32 API go get a list of windows that match a certain window title. For that I have written a method that uses the windows-rs crate to call the required system functions

fn enumerate_windows(enum_func: impl FnMut(&Process, &Window) -> bool) -> bool {
    let lparam = type_to_isize(&enum_func);
    unsafe { EnumWindows(Some(enum_windows_proc), LPARAM(lparam)) }.into()
}

The enum_windows_proc function passed to the EnumWindows function has the signature unsafe extern "system" fn enum_windows_proc(hwnd: HWND, param: LPARAM) -> BOOL. So far so good.

The enumerate_windows function is called from another function:

pub(crate) fn get_results<'a> (players: &'a Vec<MediaPlayer>, media_proc: &MediaProcT, results: &mut Vec<Result<'a>>) -> bool {
    let window_proc = |process:&'static Process, window:&'static Window| {
        for player in players {
            // Test if the give player is recognized media player
            if is_media_player_window(process, window, player) {
                let result = Result {
                    player: player,
                    process: process,
                    window: window,
                    media: Vec::new(),
                };
                results.push(result);
                return true;
            }
        }
        false
    };

    if !enumerate_windows(window_proc) {
        false
    } else {
        true
    }
}

However, when I try to compile, rustc spits out these errors:

error[E0308]: mismatched types
--> src\platform.rs:56:9
|
56 | if !enumerate_windows(window_proc) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
|
= note: expected trait for<'a, 'b> FnMut<(&'a Process, &'b Window)>
found trait FnMut<(&Process, &Window)>
note: this closure does not fulfill the lifetime requirements
--> src\platform.rs:39:23
|
39 | let window_proc = |process:&'static Process, window:&'static Window| {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: the lifetime requirement is introduced here
--> src\windows.rs:30:49
|
30 | pub(crate) fn enumerate_windows(enum_func: impl FnMut(&Process, &Window) -> bool) -> bool {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: implementation of FnOnce is not general enough
--> src\platform.rs:56:9
|
56 | if !enumerate_windows(window_proc) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of FnOnce is not general enough
|
= note: closure with signature fn(&'2 Process, &Window) -> bool must implement FnOnce<(&'1 Process, &Window)>, for any lifetime '1...
= note: ...but it actually implements FnOnce<(&'2 Process, &Window)>, for some specific lifetime '2

error: implementation of FnOnce is not general enough
--> src\platform.rs:56:9
|
56 | if !enumerate_windows(window_proc) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of FnOnce is not general enough
|
= note: closure with signature fn(&Process, &'2 Window) -> bool must implement FnOnce<(&Process, &'1 Window)>, for any lifetime '1...
= note: ...but it actually implements FnOnce<(&Process, &'2 Window)>, for some specific lifetime '2

I have seen rust - Why does passing a closure to function which accepts a function pointer not work? - Stack Overflow on SO and according to that advice set the type of the input parameter for enumerate_windows accordingly.
What did I miss? Is it just a lifetime annotation?

enum_func: impl FnMut(&Process, &Window) -> bool means that enum_func must be able to accept any combination of &'_ Process and &'_ Window lifetimes. However, window_proc is only defined for a &'static Process and &'static Window, resulting in the compiler error. Do you get any other errors if you remove the explicit 'static lifetime from the window_proc parameters? If so, do you still get those errors if you place the window_proc closure directly into the enumerate_windows call (i.e., enumerate_windows(|...| { ... }))?

When I remove the explicit 'static lifetime I get this:

error[E0308]: mismatched types
--> src\platform.rs:56:9
|
56 | if !enumerate_windows(window_proc) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
|
= note: expected trait for<'a, 'b> FnMut<(&'a Process, &'b Window)>
found trait FnMut<(&Process, &Window)>
note: this closure does not fulfill the lifetime requirements
--> src\platform.rs:39:23
|
39 | let window_proc = |process, window| {
| ^^^^^^^^^^^^^^^^^
note: the lifetime requirement is introduced here
--> src\windows.rs:30:49
|
30 | pub(crate) fn enumerate_windows(enum_func: impl FnMut(&Process, &Window) -> bool) -> bool {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: implementation of FnOnce is not general enough
--> src\platform.rs:56:9
|
56 | if !enumerate_windows(window_proc) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of FnOnce is not general enough
|
= note: closure with signature fn(&'2 Process, &Window) -> bool must implement FnOnce<(&'1 Process, &Window)>, for any lifetime '1...
= note: ...but it actually implements FnOnce<(&'2 Process, &Window)>, for some specific lifetime '2

error: implementation of FnOnce is not general enough
--> src\platform.rs:56:9
|
56 | if !enumerate_windows(window_proc) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of FnOnce is not general enough
|
= note: closure with signature fn(&Process, &'2 Window) -> bool must implement FnOnce<(&Process, &'1 Window)>, for any lifetime '1...
= note: ...but it actually implements FnOnce<(&Process, &'2 Window)>, for some specific lifetime '2

For more information about this error, try rustc --explain E0308.
error: could not compile anisthesia due to 3 previous errors

If I place the colure directly into the enumerate_windows call, it insteda get this:

error[E0521]: borrowed data escapes outside of closure
--> src\platform.rs:51:17
|
37 | results: &mut Vec<Result<'a>>,
| ------- results declared here, outside of the closure body
...
41 | if !enumerate_windows(|process, window| {
| ------- process is a reference that is only valid in the closure body
...
51 | results.push(result);
| ^^^^^^^^^^^^^^^^^^^^ process escapes the closure body here

error[E0521]: borrowed data escapes outside of closure
--> src\platform.rs:51:17
|
37 | results: &mut Vec<Result<'a>>,
| ------- results declared here, outside of the closure body
...
41 | if !enumerate_windows(|process, window| {
| ------ window is a reference that is only valid in the closure body
...
51 | results.push(result);
| ^^^^^^^^^^^^^^^^^^^^ window escapes the closure body here

For more information about this error, try rustc --explain E0521.
error: could not compile anisthesia due to 2 previous errors

Regarding the placements of the closure, lifetime-generic closures are notoriously fiddly; unless you want to keep the window_proc closure inside the enumerate_windows call, you'd want to extract it into a local function that gets called as enumerate_windows(|process, window| window_proc(process, window, players, results)).

Regarding the next lifetime error, the problem is that each Result has a reference to a process and window, but those references only last until the end of window_proc, so you can't put them into results. You'll have to find some way to either copy the relevant data out of the references, or use owned values instead of references.

I tried what you suggested and wrapped the body of the closure into a local function that I then called with

if !enumerate_windows(|process, window| window_proc_wrapper(process, window, players, results)) {
    // ...
}

However that gives me the same E0521 error.

I solved the compile error by adding lifetimes to the function header:

fn enumerate_windows<'a, 'b>(enum_func: impl FnMut(&'a Process, &'b Window) -> bool) -> bool

That should work, right?

Whether it actually works depends on how enum_windows_proc is constructing the references. With your modified version, it looks like you may be putting dangling references into results. What is the body of enum_windows_proc?

This is it:


fn enum_windows_proc(handle: HWND, mut window_proc: impl FnMut(&Process, &Window) -> bool) -> bool {
    let is_visible: bool = unsafe { IsWindowVisible(handle).into() };
    if !is_visible {
        return true;
    }

    // Get text in the window's title bar
    let text: String = get_window_text(handle);
    let class_name: String = get_window_class_name(handle).unwrap();

    let window = Window {
        handle,
        text,
        class_name,
    };

    let process_id:u32 = get_window_process_id(handle);

    // Get the full path for for the given process
    let process_path: PathBuf = get_process_path(process_id).unwrap();

    let process_name: String = process_path
        .file_stem()
        .unwrap()
        .to_string_lossy()
        .to_string();
    if !verify_process_file_name(&process_name) {
        return true;
    }

    let process = Process {
        id: process_id,
        name: process_name,
    };

    if !window_proc(&process, &window) {
        false
    } else {
        true
    }
}

1 Like

In this case, I'd recommend to just pass the Process and Window into window_proc directly instead of behind a reference. The main thing I'd be worried about is the HWND: at any point, the window can be destroyed, and its HWND handle can be reused for another window behind the program's back. I don't know of any way to prevent this, as long as you are enumerating windows you do not control.

I don't know of any way to prevent this, as long as you are enumerating windows you do not control.

That shouldn't be a problem. I don't need to do anything big with this handle, just use it to get a few properties about the window so I can check it against a list of known media players and then get the video they are playing.

Thanks for the help.

The last time I checked it takes about two hours on a busy machine before HWNDs (handles in general) are recycled.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.