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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
//! A "compatibility layer" for spanning XP and Windows 7 //! //! The standard library currently binds many functions that are not available //! on Windows XP, but we would also like to support building executables that //! run on XP. To do this we specify all non-XP APIs as having a fallback //! implementation to do something reasonable. //! //! This dynamic runtime detection of whether a function is available is //! implemented with `GetModuleHandle` and `GetProcAddress` paired with a //! static-per-function which caches the result of the first check. In this //! manner we pay a semi-large one-time cost up front for detecting whether a //! function is available but afterwards it's just a load and a jump. use crate::ffi::CString; use crate::sys::c; pub fn lookup(module: &str, symbol: &str) -> Option<usize> { let mut module: Vec<u16> = module.encode_utf16().collect(); module.push(0); let symbol = CString::new(symbol).unwrap(); unsafe { let handle = c::GetModuleHandleW(module.as_ptr()); match c::GetProcAddress(handle, symbol.as_ptr()) as usize { 0 => None, n => Some(n), } } } macro_rules! compat_fn { ($module:literal: $( $(#[$meta:meta])* pub fn $symbol:ident($($argname:ident: $argtype:ty),*) -> $rettype:ty $body:block )*) => ($( $(#[$meta])* pub mod $symbol { use super::*; use crate::sync::atomic::{AtomicUsize, Ordering}; use crate::mem; type F = unsafe extern "system" fn($($argtype),*) -> $rettype; static PTR: AtomicUsize = AtomicUsize::new(0); #[allow(unused_variables)] unsafe extern "system" fn fallback($($argname: $argtype),*) -> $rettype $body /// This address is stored in `PTR` to incidate an unavailable API. /// /// This way, call() will end up calling fallback() if it is unavailable. /// /// This is a `static` to avoid rustc duplicating `fn fallback()` /// into both load() and is_available(), which would break /// is_available()'s comparison. By using the same static variable /// in both places, they'll refer to the same (copy of the) /// function. /// /// LLVM merging the address of fallback with other functions /// (because of unnamed_addr) is fine, since it's only compared to /// an address from GetProcAddress from an external dll. static FALLBACK: F = fallback; #[cold] fn load() -> usize { // There is no locking here. It's okay if this is executed by multiple threads in // parallel. `lookup` will result in the same value, and it's okay if they overwrite // eachothers result as long as they do so atomically. We don't need any guarantees // about memory ordering, as this involves just a single atomic variable which is // not used to protect or order anything else. let addr = crate::sys::compat::lookup($module, stringify!($symbol)) .unwrap_or(FALLBACK as usize); PTR.store(addr, Ordering::Relaxed); addr } fn addr() -> usize { match PTR.load(Ordering::Relaxed) { 0 => load(), addr => addr, } } #[allow(dead_code)] pub fn is_available() -> bool { addr() != FALLBACK as usize } pub unsafe fn call($($argname: $argtype),*) -> $rettype { mem::transmute::<usize, F>(addr())($($argname),*) } } pub use $symbol::call as $symbol; )*) }