简体   繁体   中英

Cast to inferred function type in Rust

Let's say we have a type FARPROC that we receive as result of certain Win32 operations, such as GetProcAddress . It's definition is as follows:

pub type FARPROC = unsafe extern "system" fn() -> isize;

This type then needs to be converted to a valid function type so we can call it. As developers, we know the function signature (from documentation), but we need to pass that knowledge onto the compiler. A straightforward approach is to use transmute with explicit function type as following:

let address: FARPROC = unsafe { GetProcAddress(module, proc).unwrap() };
let function: extern "system" fn(i32) = unsafe { transmute (&address) };

But what if we wanted to infer the function type from one of the existing functions that we defined in our rust code? Let's say we have some function with lengthy definition:

pub fn foo(arg1: i32, arg2: c_void, arg3: *const c_char) {
   // snip
}

In order to keep our code DRY, is it possible to use that function type with transmute ?

My attempted solution looks like the following snippet:


pub fn cast_to_function<F>(address: FARPROC, _fn: &F) -> F {
    unsafe { transmute_copy(&address) }
}

/// Usage

let address: FARPROC = unsafe { GetProcAddress(module, proc).unwrap() };
let function = cast_to_function(address, &foo);
function(1, ...);

This attempted solution is based on the similar C++ code that is working:

template<typename T>
T FnCast(void* fnToCast, T pFnCastTo) {
    return (T)fnToCast;
}

/// Usage

void bar(int arg1, void* arg2, const char* arg3){
   // snip
}

auto address = (void*) GetProcAddress(...); 
auto function = FnCast(address, &bar);
function(1, address, "bar");

The problem with my attempted solution in rust is that cast_to_function always returns the function with address that points to the referenced function, rather than an address that points to the one which we provided. Hence, given what was laid out so far, is it possible to intelligently infer the function type from its definition and cast arbitrary type to it?

To understand why your attempted solution doesn't work, we must know about the different kinds of function types in Rust; see this answer . When you make this call:

let function = cast_to_function(address, &foo);

the argument &foo is a reference to the specific function item foo ; it is not a function pointer like address . Due to the type signature of cast_to_function() , this means the return type T is that same function item. Function items are zero-sized types, so the transmute_copy() inside cast_to_function() doesn't do anything.

The simplest solution is to cast foo to a function pointer before calling cast_to_function() :

let function = cast_to_function(address, foo as fn(_, _));

(you'll also need to change cast_to_function() 's signature to take _fn by value: _fn: F ).

This is still a bit of boilerplate with the manual casting, though as demonstrated you can use placeholders ( _ ) instead of writing out each argument type. If you get the number of arguments wrong you just get a compile error (you can't accidentally cast foo to a wrong signature).


Getting rid of the required casting in the caller code is more complicated. I came up with this solution, which uses a trait to do the casting, and uses a macro to write the impls for functions of varying arity:

/// Casts a function pointer to a new function pointer, with the new type derived from a
/// template function.
///
/// SAFETY:
/// - `f` must be a function pointer (e.g. `fn(i32) -> i32`)
/// - `template`'s type must be a valid function signature for `f`
unsafe fn fn_ptr_cast<T, U, V>(fn_ptr: T, _template_fn: U) -> V
where
    T: Copy + 'static,
    U: FnPtrCast<V>,
{
    debug_assert_eq!(mem::size_of::<T>(), mem::size_of::<usize>());

    U::fn_ptr_cast(fn_ptr)
}

unsafe trait FnPtrCast<U> {
    unsafe fn fn_ptr_cast<T>(fn_ptr: T) -> U;
}

macro_rules! impl_fn_cast {
    ( $($arg:ident),* ) => {
        unsafe impl<Fun, Out, $($arg),*> FnPtrCast<fn($($arg),*) -> Out> for Fun
        where
            Fun: Fn($($arg),*) -> Out,
        {
            unsafe fn fn_ptr_cast<T>(fn_ptr: T) -> fn($($arg),*) -> Out {
                ::std::mem::transmute(::std::ptr::read(&fn_ptr as *const T as *const *const T))
            }
        }
    }
}

impl_fn_cast!();
impl_fn_cast!(A);
impl_fn_cast!(A, B);
impl_fn_cast!(A, B, C);
impl_fn_cast!(A, B, C, D);
impl_fn_cast!(A, B, C, D, E);
impl_fn_cast!(A, B, C, D, E, F);
impl_fn_cast!(A, B, C, D, E, F, G);
impl_fn_cast!(A, B, C, D, E, F, G, H);

This lets you do

let function = fn_ptr_cast(address, foo);

without any manual casting.

Note: This last solution assumes function pointers and data pointers are the same size, which may not always be the case on some platforms.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM