Skip to content

Instantly share code, notes, and snippets.

@Osspial
Created October 11, 2019 20:18
Show Gist options
  • Save Osspial/e5d95874a864478796c3a450a5ba0d8d to your computer and use it in GitHub Desktop.
Save Osspial/e5d95874a864478796c3a450a5ba0d8d to your computer and use it in GitHub Desktop.

The cef_callback_impl macro isn't all that complex - most of the heavy lifting here is getting done by the trait system. However, it is the jumping-off point into the trait system, so it's a good place to start looking at that.

For reference, here's the macro definition itself:

macro_rules! cef_callback_impl {
    (impl $RefCounted:ty: $CType:ty {
        $(fn $fn_name:ident(&mut $self:ident, $($field_name:ident: $field_ty:ty: $c_ty:ty),+ $(,)?) $(-> $ret:ty)? $body:block)+
    }) => {
        impl $RefCounted {
            $(
                extern "C" fn $fn_name(self_: *mut $CType, $($field_name: $c_ty),+) $(-> $ret)? {
                    trait Impl {
                        fn inner(&mut $self, $($field_name: $field_ty),+) $(-> $ret)?;
                    }
                    impl Impl for $RefCounted {
                        #[inline(always)]
                        fn inner(&mut $self, $($field_name: $field_ty),+) $(-> $ret)? $body
                    }
                    let mut this = unsafe { RefCounted::<$CType>::make_temp(self_) };
                    $(
                        let mut $field_name: $field_ty = unsafe{ <$field_ty as crate::extern_callback_helpers::CToRustType>::from_c_type($field_name) };
                    )+
                    this.inner($($field_name),+)
                }
            )+
        }
    };
}

Let's take a look at an example from ResourceRequestHandlerWrapper. This invocation:

cef_callback_impl! {
    impl ResourceRequestHandlerWrapper: cef_resource_request_handler_t {
        fn protocol_execution(
            &mut self,
            browser: Option<Browser>: *mut cef_browser_t,
            frame: Option<Frame>: *mut cef_frame_t,
            request: Request: *mut cef_request_t,
            allow_os_execution: Option<&mut c_int>: *mut c_int,
        ) {
            if self.delegate.on_protocol_execution(
                browser.as_ref(),
                frame.as_ref(),
                &request,
            ) {
                if let Some(allow_os_execution) = allow_os_execution {
                    *allow_os_execution = 1;
                }
            }
        }
    }
}

Expands into this (I've cleaned up the type signatures somewhat, but it's still fundamentally the same code):

impl ResourceRequestHandlerWrapper {
    extern "C" fn protocol_execution(
        self_: *mut cef_resource_request_handler_t,
        browser: *mut cef_browser_t,
        frame: *mut cef_frame_t,
        request: *mut cef_request_t,
        allow_os_execution: *mut c_int,
    ) {
        trait Impl {
            fn inner(
                &mut self,
                browser: Option<Browser>,
                frame: Option<Frame>,
                request: Request,
                allow_os_execution: Option<&mut c_int>,
            );
        }
        impl Impl for ResourceRequestHandlerWrapper {
            #[inline(always)]
            fn inner(
                &mut self,
                browser: Option<Browser>,
                frame: Option<Frame>,
                request: Request,
                allow_os_execution: Option<&mut c_int>,
            ) {
                if self.delegate.on_protocol_execution(browser.as_ref(), frame.as_ref(), &request) {
                    if let Some(allow_os_execution) = allow_os_execution {
                        *allow_os_execution = 1;
                    }
                }
            }
        }
        let mut this = unsafe { RefCounted::<cef_resource_request_handler_t>::make_temp(self_) };
        let browser: Option<&Browser> = unsafe {
            <Option<&Browser> as CToRustType>::from_c_type(browser)
        };
        let frame: Option<&Frame> = unsafe {
            <Option<&Frame> as CToRustType>::from_c_type(frame)
        };
        let request: &Request =
            unsafe { <&Request as CToRustType>::from_c_type(request) };
        let allow_os_execution: Option<&mut c_int> =
            unsafe { <Option<&mut c_int> as CToRustType>::from_c_type(allow_os_execution) };

        this.inner(browser, frame, request, allow_os_execution)
    }
}

...which doesn't look the best. I wouldn't write that if I were writing the function out by hand, but due to limitations of the macro system I'm unaware of any way to implement this particular macro that would generate the same code structure I'd write manually. Still, each section has a purpose:

trait Impl {
    fn inner(
        &mut self,
        browser: Option<&Browser>,
        frame: Option<&Frame>,
        request: &Request,
        allow_os_execution: Option<&mut c_int>,
    );
}
impl Impl for ResourceRequestHandlerWrapper {
    #[inline(always)]
    fn inner(
        &mut self,
        browser: Option<&Browser>,
        frame: Option<&Frame>,
        request: &Request,
        allow_os_execution: Option<&mut c_int>,
    ) {
        if self.delegate.on_protocol_execution(browser, frame, request) {
            if let Some(allow_os_execution) = allow_os_execution {
                *allow_os_execution = 1;
            }
        }
    }
}

It's nice and Rust-y for the method body the user writes to be able to use self, and this section accomplishes that. We need to declare an associated function on ResourceRequestHandlerWrapper inside the function body if we'd like to do that. We use a trait instead of a vanilla associated function for scoping reasons: associated functions are always visible on the module level, so if we just did impl ResourceRequestHandlerWrapper fn inner..., it would conflict with other inner functions in the other C callbacks. Traits, on the other hand, are only visible inside the function body, so each function can have its own separate Impl trait without running into naming conflicts.

As a side note, notice how the macro definition takes &mut self as &mut $self:ident. This is because self has some weird interactions with the macro hygiene system. The hygiene system recognizes it as an identifier, so makes self as declared in the macro body and self as declared in the macro invocation different values. However, the user can only put self into the $self matcher, since the language expects that the first parameter of associated methods is named self.

let mut this = unsafe { RefCounted::<cef_resource_request_handler_t>::make_temp(self_) };

let browser: Option<&Browser> = unsafe {
    <Option<&Browser> as CToRustType>::from_c_type(browser)
};
let frame: Option<&Frame> = unsafe {
    <Option<&Frame> as CToRustType>::from_c_type(frame)
};
let request: &Request =
    unsafe { <&Request as CToRustType>::from_c_type(request) };
let allow_os_execution: Option<&mut c_int> =
    unsafe { <Option<&mut c_int> as CToRustType>::from_c_type(allow_os_execution) };

This section has more type annotations than strictly necessary, though the extra specificity certainly doesn't hurt. However, we don't call the functions converting from C types to Rust types directly, since different operations are necessary for different types and the macro system can't distinguish between different types in more than the most primitive, token-level ways. Instead, we shell out to the CToRustType helper trait:

pub trait CToRustType {
    type CType;
    unsafe fn from_c_type(c_type: Self::CType) -> Self;
}

This trait performs the conversion between C types (raw pointers, raw enums, etc.) into the corresponding Rust types. For the most part, the implementations are fairly simple; here's the implementation for Browser types:

impl CToRustType for Browser {
    type CType = *mut cef_sys::cef_browser_t;
    unsafe fn from_c_type(c_type: Self::CType) -> Self {
        <Browser>::from_ptr_unchecked(c_type)
    }
}

We automatically implement CToRustType for any Option<T> when the CType is a pointer (the Pointer trait is defined elsewhere in the module):

impl<T> CToRustType for Option<T>
where
    T: CToRustType,
    <T as CToRustType>::CType: Pointer,
{
    type CType = <T as CToRustType>::CType;

    unsafe fn from_c_type(c_type: Self::CType) -> Self {
        if c_type.is_null() {
            None
        } else {
            Some(<T as CToRustType>::from_c_type(c_type))
        }
    }
}

We then pass those arguments onto the inner function, which process the types and returns the expected value. We don't do any processing on the return type since the trait functions' return types don't consistently map onto CEF types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment