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 96 97 98 99 100 101 102 103 104
/// Creates an trait that emulates virtual table behavior from C++.
/// You can use `'this` lifetime for arguments that requires `self` lifetime.
/// ```
/// # use faithe::interface;
/// struct CPlayer;
/// interface! {
/// trait IEntity(CPlayer) {
/// // 1 - is an index of this function in the table.
/// extern "C" fn print() = 1;
/// // Lifetimes works too
/// extern "C" fn other<'a>(num: &'a i32) -> &'a i32 = 2;
/// }
/// }
/// // You can use `IInterface::virt_by_address(obj, "func_name")` to get the address of the function by its name or
/// // `IInterface::virt_by_index(obj, <function_index>)` to get the address by the function's index.
/// ```
#[macro_export]
macro_rules! interface {
(
$(
$vs:vis trait $name:ident$(($($target:ident$(<$($tlf:tt),*>)?),*))? {
$(
$(extern $($cc:literal)?)? fn $fn_id:ident$(<$($gen:tt),*>)?($($arg_id:ident: $arg_ty:ty),*) $(-> $ret_ty:ty)? = $idx:expr;
)*
}
)*
) => {
$(
$vs unsafe trait $name: ::core::marker::Sized {
$(
#[inline(always)]
#[allow(non_snake_case)]
$(extern $($cc)?)? fn $fn_id<'this$(,$($gen),*)?>(&'this self, $($arg_id: $arg_ty),*) $(-> $ret_ty)? {
unsafe {
let slot = *(self as *const Self as *const usize) + $idx * core::mem::size_of::<usize>();
(*core::mem::transmute::<_, *const $(extern $($cc)?)? fn(&Self, $($arg_ty),*) $(-> $ret_ty)?>(slot))
(self, $($arg_id),*)
}
}
)*
/// Returns the address of the virtual function by its name.
#[inline]
fn virt_by_name(&self, name: &'static str) -> usize {
unsafe {
match name {
$(
stringify!($fn_id) => {
(*(self as *const Self as *const &'static [usize; $idx + 1]))[$idx]
}
)*
_ => panic!("Unknown function")
}
}
}
/// Returns the address of the virtual function by its index.
#[inline]
fn virt_by_index(&self, idx: usize) -> usize {
unsafe {
*(*(self as *const Self as *const *const usize)).add(idx)
}
}
/// Dumps the virtual function table by walking vmt pointer until reaches zero slot.
#[inline]
fn walk_vmt(&self, mut callback: impl core::ops::FnMut(usize, *const ())) {
unsafe {
let mut i = 0;
let mut slot = *(self as *const Self as *const *const usize);
while *slot.add(i) != 0 {
callback(i, *slot.add(i) as _);
i += 1;
}
}
}
/// Same as `walk_vmt` but calls `callback` only on defined methods.
#[inline]
fn dump_vmt(&self, mut callback: impl core::ops::FnMut(usize, &'static str, *const ())) {
unsafe {
let mut i = 0;
let mut slot = *(self as *const Self as *const *const usize);
while *slot.add(i) != 0 {
match i {
$(
$idx => { callback(i, stringify!($fn_id), *slot.add(i) as _) }
)*
_ => {}
}
i += 1;
}
}
}
}
$(
$(
unsafe impl$(<$($tlf),*>)? $name for $target$(<$($tlf),*>)? { }
)*
)?
)*
};
}