retep998 / winapi-rs

Rust bindings to Windows API
https://crates.io/crates/winapi
Apache License 2.0
1.85k stars 393 forks source link

Dyamically load functions available only on newer Windows versions #518

Open kryptan opened 7 years ago

kryptan commented 7 years ago

Microsoft constantly adds new functions to WinAPI in newer Windows versions (including in Windows 10 updates). I want to check at runtime whether these functions are available to use them if possible but still be able to execute my application on older Windows versions if they are not. As far as I can see winapi crate does not have any support for this. The only solution for me is to manually write all function definitions in my own code and use shared_library crate to load them.

winapi could provide something like this:

type GetDpiForWindow = unsafe extern "system" fn (hwnd: winapi::HWND) -> winapi::UINT;

lazy_static! {
    static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> = ...
}

Or just a function instead of lazy_static!:

type GetDpiForWindow = unsafe extern "system" fn (hwnd: winapi::HWND) -> winapi::UINT;

fn get_function_GetDpiForWindow() -> Option<GetDpiForWindow> { ... }
retep998 commented 7 years ago

If I was going to do something like this, I would want it handled via a macro, so that I can simply wrap extern functions in that macro and it'll generate dynamic versions too. Unfortunately the lack of usable ident concatenation in macros is a major roadblock.

kryptan commented 7 years ago

Not an ideal solution but something like this might work:

extern "system" {
    fn GetDpiForWindow (hwnd: HWND) -> UINT;
}

mod GetDpiForWindow {
    pub type Type = unsafe extern "system" fn (hwnd: HWND) -> UINT;

    fn get() -> Option<Type> { ... }
}

The trick is that module and function can have the same name as they are in different namespaces. No concatenation is necessary.

Another option is redefining a macro:

// macro to produce extern functions
macro_rules! declare_function { ... }

include!("function_definitions.rs")

// macro to produce definitions for dynamic loading
macro_rules! declare_function { ... }

mod dynamic {
     include!("function_definitions.rs")
}

Edit: added mod dynamic in the second example to clarify how identifier conflicts are avoided.

retep998 commented 7 years ago

Having a module for each function is probably not a good idea. Rust would likely choke on the sheer number of modules and the documentation would be super confusing.

Having macros that redefine macros might work (in the way that I am thinking of, not the way that you are thinking of) but that stuff is so corner case and has probably had enough changes that I doubt I'd be able to get something sane that works across all supported Rust versions.

kryptan commented 7 years ago

Yeah, I also don't like those solutions.

Instead of declaring a module we can declare a type instead:

extern "system" {
    fn GetDpiForWindow (hwnd: HWND) -> UINT;
}

pub type GetDpiForWindow = unsafe extern "system" fn (hwnd: HWND) -> UINT;

Once we have a type for each function we can probably make some generic function or macro which would be able to load any function.

With the macro proposal it is not actually necessary to redefine macro. Instead they can be declared in different modules and wouldn't conflict with each other. For example:

mod1.rs:

// macro to produce extern functions
macro_rules! declare_function { ... }

include!("function_definitions.rs")

mod2.rs:

// macro to produce definitions for dynamic loading
macro_rules! declare_function { ... }

include!("function_definitions.rs")

Having macros that redefine macros might work (in the way that I am thinking of, not the way that you are thinking of)

Not sure what you meant here.

retep998 commented 7 years ago

I'd rather not stuff the functions into a separate file so anything involving include!("function_definitions.rs") is out of the running.

Not sure what you meant here.

I was thinking of a system where each time I call the macro to define the functions, it would redefine another macro to act as a variable to store all those definitions and then at the end of the module I would call a macro which would regurgitate all those functions into a submodule but as dynamically loaded versions.

l0calh05t commented 5 years ago

Any progress on this issue in the past two years?

retep998 commented 5 years ago

Enough of procedural macros have been stabilized recently that winapi 0.4 could use them to create a macro for this. If someone wants to help write such a procedural macro it would be greatly appreciated.

cmsd2 commented 5 years ago

ident manipulation seems straight forward enough. https://gist.github.com/cmsd2/cc2013a01a9318ca3e9a53ef56cf9b5a

is that useful?

l0calh05t commented 5 years ago

Is ident concatenation (via procedural macros) really the correct solution? I think the variant with a "dynamic" submodule would be the cleanest from the API users PoV, but I don't see any variant except the include!("function_definitions.rs") being possible here.

l0calh05t commented 5 years ago

I decided to try it (using proc_macros and a dynamic module, no identifier concatenation): https://github.com/l0calh05t/winapi-rs-dynamic-experiment

Seems to work fine. I used libloading to ensure libraries are unloaded properly in cases such as the functions being used in a DLL and lazy_static to ensure they are loaded exactly once. A usage example is included as well.

It's also my first attempt at a procedural macro, so it may be improvable.

kankri commented 5 years ago

My need for function pointer definitions in "winapi" is that there are some DLLs you only want to load when needed so you want to use dynamic loading, or that for some DLLs the one in the system doesn't implement the full API (like "%windir%\system32\dbghelp.dll") and must be loaded dynamically from a specific directory (e.g. DLL bundled with the application).

In C/C++ many SDKs declare both each function and then a pointer to it. Then it's easy with some macros to declare a struct for the function pointers and the code to initialize those pointers.

So far I have done something similar with "winapi". First I declare each function pointer:

use paste;

macro_rules! FN {
    ($func_name:ident ( $($name:ident : $type:ty),* ) -> $ret:ty) => (
        #[allow(dead_code)]
        extern "system" { pub fn $func_name ( $( $name : $type ),* ) -> $ret; }
        paste::item! {
            #[allow(dead_code, non_camel_case_types)]
            type [< p $func_name >] = unsafe extern "system" fn (
                $( $name : $type ),* ) -> $ret;
        }
    )
}

FN!{SymInitializeW(
    hProcess: HANDLE,
    UserSearchPath: PCWSTR,
    fInvadeProcess: BOOL
) -> BOOL}

The FN macro call above declares both the external function SymInitializeW and a function pointer pSymInitializeW.

Then I define a structure containing all the pointers I need:

struct Api {
    SymInitializeW: OsSymbol<pSymInitializeW>,
}

And then I load the functions when I need them:

    let lib = Library::new(dll_path)?;
    Api {
        SymInitializeW: load_sym(&lib, b"SymInitializeW")?,
    }

It would be nice if the function pointer definitions were provided by "winapi". I don't mind and prefer doing the other steps manually so I have full control of how to manage the library (when to load, unload and reload the DLL) and the function pointers (which ones to resolve) and can decide whether to use libloading or some other helper crate.

The FN macro could of course do something more beautiful than prepend the function name with a "p", maybe it could put the definitions in a ptr module (winapi::um::dbghelp::ptr::SymInitializeW), but that doesn't seem to increase the ease of use for me. In any case, I feel adding a dozen-line macro like FN and wrapping the existing function declarations with it would be straight forward.