#![allow(clippy::cargo_common_metadata)] use std::vec::Vec; use libffi::{ low, middle::{Cif, Type}, raw, }; use mlua::prelude::*; use crate::ctype::{libffi_types_from_table, type_userdata_stringify, CType}; use crate::FFI_STATUS_NAMES; use crate::{ association::{get_association, set_association}, ctype::type_name_from_userdata, }; use crate::{carr::CArr, cptr::CPtr}; pub struct CStruct { libffi_cif: Cif, libffi_type: Type, fields: Vec, offsets: Vec, size: usize, } const CSTRUCT_INNER: &str = "__cstruct_inner"; impl CStruct { pub fn new(fields: Vec) -> LuaResult { let libffi_type = Type::structure(fields.clone()); let libffi_cfi = Cif::new(vec![libffi_type.clone()], Type::void()); // Get field offsets with ffi_get_struct_offsets let mut offsets = Vec::::with_capacity(fields.len()); unsafe { let offset_result: raw::ffi_status = raw::ffi_get_struct_offsets( low::ffi_abi_FFI_DEFAULT_ABI, libffi_type.as_raw_ptr(), offsets.as_mut_ptr(), ); if offset_result != raw::ffi_status_FFI_OK { return Err(LuaError::external(format!( "ffi_get_struct_offsets failed. expected result {}, got {}", FFI_STATUS_NAMES[0], FFI_STATUS_NAMES[offset_result as usize] ))); } offsets.set_len(offsets.capacity()); } // Get tailing padded size of struct // See http://www.chiark.greenend.org.uk/doc/libffi-dev/html/Size-and-Alignment.html let size = unsafe { (*libffi_type.as_raw_ptr()).size }; Ok(Self { libffi_cif: libffi_cfi, libffi_type, fields, offsets, size, }) } // Create new CStruct UserData with LuaTable. // Lock and hold table for .inner ref pub fn from_lua_table<'lua>( lua: &'lua Lua, table: LuaTable<'lua>, ) -> LuaResult> { let fields = libffi_types_from_table(&table)?; let cstruct = lua.create_userdata(Self::new(fields)?)?; table.set_readonly(true); set_association(lua, CSTRUCT_INNER, cstruct.clone(), table)?; Ok(cstruct) } // Stringify cstruct for pretty printing something like: // pub fn stringify(userdata: &LuaAnyUserData) -> LuaResult { let field: LuaValue = userdata.get("inner")?; if field.is_table() { let table = field .as_table() .ok_or(LuaError::external("failed to get inner type table."))?; // iterate for field let mut result = String::from(" "); for i in 0..table.raw_len() { let child: LuaAnyUserData = table.raw_get(i + 1)?; if child.is::() { result.push_str(format!("{}, ", type_userdata_stringify(&child)?).as_str()); } else { result.push_str( format!( "<{}({})>, ", type_name_from_userdata(&child), type_userdata_stringify(&child)? ) .as_str(), ); } } // size of result.push_str(format!("size = {} ", userdata.borrow::()?.size).as_str()); Ok(result) } else { Err(LuaError::external("failed to get inner type table.")) } } // Get byte offset of nth field pub fn offset(&self, index: usize) -> LuaResult { let offset = self .offsets .get(index) .ok_or(LuaError::external("Out of index"))? .to_owned(); Ok(offset) } pub fn get_type(&self) -> Type { self.libffi_type.clone() } } impl LuaUserData for CStruct { fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { fields.add_field_method_get("size", |_, this| Ok(this.size)); // Simply pass in the locked table used when first creating this object. // By strongly referencing the table, the types inside do not disappear // and the user can read the contents as needed. (good recycling!) fields.add_field_function_get("inner", |lua, this: LuaAnyUserData| { let table: LuaValue = get_association(lua, CSTRUCT_INNER, this)? // It shouldn't happen. .ok_or(LuaError::external("inner field not found"))?; Ok(table) }); } fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_method("offset", |_, this, index: usize| { let offset = this.offset(index)?; Ok(offset) }); methods.add_function("ptr", |lua, this: LuaAnyUserData| { let pointer = CPtr::from_lua_userdata(lua, &this)?; Ok(pointer) }); methods.add_function("arr", |lua, (this, length): (LuaAnyUserData, usize)| { let carr = CArr::from_lua_userdata(lua, &this, length)?; Ok(carr) }); methods.add_meta_function(LuaMetaMethod::ToString, |_, this: LuaAnyUserData| { let result = CStruct::stringify(&this)?; Ok(result) }); } }