added module builder, example project, documentation

This commit is contained in:
David Cole 2021-03-09 20:47:20 +13:00
parent 50d774ee31
commit 7587dccd0d
13 changed files with 290 additions and 93 deletions

View File

@ -34,23 +34,27 @@ fn main() {
panic!("Error running `php-config`: {}", stderr);
}
let includes = String::from_utf8(includes_cmd.stdout)
.expect("unable to parse `php-config` stdout");
let includes =
String::from_utf8(includes_cmd.stdout).expect("unable to parse `php-config` stdout");
let ignore_math_h_macros = IgnoreMacros(vec![
// math.h:914 - enum which uses #define for values
"FP_NAN".into(),
"FP_INFINITE".into(),
"FP_ZERO".into(),
"FP_SUBNORMAL".into(),
"FP_NORMAL".into(),
// math.h:237 - enum which uses #define for values
"FP_INT_UPWARD".into(),
"FP_INT_DOWNWARD".into(),
"FP_INT_TOWARDZERO".into(),
"FP_INT_TONEARESTFROMZERO".into(),
"FP_INT_TONEAREST".into(),
].into_iter().collect());
let ignore_math_h_macros = IgnoreMacros(
vec![
// math.h:914 - enum which uses #define for values
"FP_NAN".into(),
"FP_INFINITE".into(),
"FP_ZERO".into(),
"FP_SUBNORMAL".into(),
"FP_NORMAL".into(),
// math.h:237 - enum which uses #define for values
"FP_INT_UPWARD".into(),
"FP_INT_DOWNWARD".into(),
"FP_INT_TOWARDZERO".into(),
"FP_INT_TONEARESTFROMZERO".into(),
"FP_INT_TONEAREST".into(),
]
.into_iter()
.collect(),
);
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindgen::Builder::default()

4
example/skel/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
Cargo.lock
/.vscode
expand.rs

14
example/skel/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "example"
version = "0.1.0"
authors = ["David Cole <david.cole1340@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
php-rs = { path = "../../" }
[lib]
name = "skel"
crate-type = ["dylib"]

19
example/skel/src/lib.rs Normal file
View File

@ -0,0 +1,19 @@
use php_rs::{
info_table_end, info_table_row, info_table_start,
php::module::{ModuleBuilder, ModuleEntry},
};
#[no_mangle]
pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
info_table_start!();
info_table_row!("skeleton extension", "enabled");
info_table_end!();
}
#[no_mangle]
pub extern "C" fn get_module() -> *mut php_rs::php::module::ModuleEntry {
ModuleBuilder::new("ext-skel", "0.1.0")
.info_function(php_module_info)
.build()
.into_raw()
}

1
src/bindings.rs Normal file
View File

@ -0,0 +1 @@
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

View File

@ -1,9 +1,46 @@
use std::ffi::CString;
use crate::bindings::{ZEND_BUILD_TS, ZEND_MODULE_API_NO};
use std::ffi::{CStr, CString};
pub fn c_str<T>(s: T) -> *const i8
/// Takes a Rust string object, converts it into a C string
/// and then releases the string to the C world.
///
/// Note that strings produced by this function *will not* be freed by
/// Rust, and this can cause memory leaks.
///
/// # Examples
/// ```
/// use std::ffi::CString;
/// use php_rs::functions::c_str;
///
/// let mut ptr = c_str("Hello");
///
/// unsafe {
/// assert_eq!(b'H', *ptr as u8);
/// assert_eq!(b'e', *ptr.offset(1) as u8);
/// assert_eq!(b'l', *ptr.offset(2) as u8);
/// assert_eq!(b'l', *ptr.offset(3) as u8);
/// assert_eq!(b'o', *ptr.offset(4) as u8);
/// assert_eq!(b'\0', *ptr.offset(5) as u8);
///
/// // reclaim string and release memory
/// let _ = CString::from_raw(ptr);
/// }
/// ```
pub fn c_str<S>(s: S) -> *const i8
where
T: Into<Vec<u8>>
S: Into<String>,
{
let x = CString::new(s).unwrap();
x.as_ptr()
CString::into_raw(CString::new(s.into()).unwrap())
}
/// Fetches the `build_id` for a Zend extension module.
pub(crate) fn build_id() -> String {
// UNSAFE: reading a constant which has been translated from C, only reading and not
// modifying.
let zend_build_ts = unsafe { CStr::from_ptr(ZEND_BUILD_TS.as_ptr() as *const i8) };
format!(
"API{}{}",
ZEND_MODULE_API_NO,
zend_build_ts.to_str().unwrap()
)
}

View File

@ -4,23 +4,14 @@
#[macro_use]
pub mod macros;
pub(crate) mod bindings;
pub mod functions;
pub mod module;
pub mod php;
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
#[cfg(test)]
mod tests {
use crate::info_table_start;
#[test]
fn test() {
info_table_start!();
info_table_header!("Hello", "World", "From", "Macro");
info_table_header!();
info_table_row!("Hello", "World!");
info_table_end!();
}
// Bindings used by macros. Used so that the rest of the bindings can be hidden with `pub(crate)`.
extern "C" {
pub fn php_info_print_table_header(num_cols: ::std::os::raw::c_int, ...);
pub fn php_info_print_table_row(num_cols: ::std::os::raw::c_int, ...);
pub fn php_info_print_table_start();
pub fn php_info_print_table_end();
}

View File

@ -1,32 +1,38 @@
/// Starts the PHP extension information table displayed when running `phpinfo();`
/// Must be run *before* rows are inserted into the table.
#[macro_export]
macro_rules! info_table_start {
() => {
unsafe { crate::php_info_print_table_start() };
unsafe { $crate::php_info_print_table_start() };
};
}
/// Ends the PHP extension information table. Must be run *after* all rows have been inserted into the table.
#[macro_export]
macro_rules! info_table_end {
() => {
unsafe { crate::php_info_print_table_end() }
unsafe { $crate::php_info_print_table_end() }
};
}
/// Sets the header for the PHP extension information table. Takes as many string arguments as required.
#[macro_export]
macro_rules! info_table_header {
($($element:expr),*) => {$crate::_info_table_row!(php_info_print_table_header, $($element),*)};
}
/// Adds a row to the PHP extension information table. Takes as many string arguments as required.
#[macro_export]
macro_rules! info_table_row {
($($element:expr),*) => {$crate::_info_table_row!(php_info_print_table_row, $($element),*)};
}
/// INTERNAL: Calls a variadic C function with the number of parameters, then following with the parameters.
#[macro_export]
macro_rules! _info_table_row {
($fn: ident, $($element: expr),*) => {
unsafe {
crate::$fn($crate::_info_table_row!(@COUNT; $($element),*) as i32, $(crate::functions::c_str($element)),*);
$crate::$fn($crate::_info_table_row!(@COUNT; $($element),*) as i32, $($crate::functions::c_str($element)),*);
}
};

View File

@ -1,50 +0,0 @@
use crate::{
functions::c_str, php_info_print_table_end, php_info_print_table_header,
php_info_print_table_row, php_info_print_table_start,
};
pub struct InfoTable {
rows: Vec<(&'static str, &'static str)>,
header: Option<(&'static str, &'static str)>,
}
impl InfoTable {
pub fn new(cols: u32) -> Self {
InfoTable {
rows: vec![],
header: None,
}
}
pub fn header<S>(&mut self, key: S, value: S) -> &mut Self
where
S: Into<&'static str>,
{
self.header = Some((key.into(), value.into()));
self
}
pub fn row<I, S>(&mut self, key: S, value: S) -> &mut Self
where
S: Into<&'static str>,
{
self.rows.push((key.into(), value.into()));
self
}
pub fn build(&self) {
unsafe { php_info_print_table_start() };
if let Some(header) = self.header {
unsafe {
php_info_print_table_header(2, c_str(header.0), c_str(header.1));
}
}
for row in self.rows.iter() {
unsafe { php_info_print_table_row(2, c_str(row.0), c_str(row.1)) };
}
unsafe { php_info_print_table_end() };
}
}

View File

@ -1 +0,0 @@
pub mod info;

1
src/php/function.rs Normal file
View File

@ -0,0 +1 @@

2
src/php/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod function;
pub mod module;

169
src/php/module.rs Normal file
View File

@ -0,0 +1,169 @@
use std::{
ffi::c_void,
mem,
os::raw::{c_char, c_int},
ptr,
};
use crate::{
bindings::{
zend_function_entry, zend_module_entry, zend_result, USING_ZTS, ZEND_DEBUG,
ZEND_MODULE_API_NO,
},
functions::{build_id, c_str},
};
pub type ModuleEntry = zend_module_entry;
pub type FunctionEntry = zend_function_entry;
pub type StartupShutdownFunc = extern "C" fn(type_: c_int, module_number: c_int) -> zend_result;
pub type InfoFunc = extern "C" fn(zend_module: *mut ModuleEntry);
/// Builds a Zend extension. Must be called from within an external function called `get_module`,
/// returning a mutable pointer to a `ModuleEntry`.
///
/// ```
/// #[no_mangle]
/// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
/// print_table_start();
/// print_table_row("column 1", "column 2");
/// print_table_end();
/// }
///
/// #[no_mangle]
/// pub extern "C" fn get_module() -> *mut ModuleEntry {
/// ModuleBuilder::new("ext-name", "ext-version")
/// .info_function(php_module_info)
/// .build()
/// .into_raw()
/// }
/// ```
pub struct ModuleBuilder {
module: ModuleEntry,
functions: Vec<FunctionEntry>,
}
impl ModuleBuilder {
/// Creates a new module builder with a given name and version.
///
/// # Arguments
///
/// * `name` - The name of the extension.
/// * `version` - The current version of the extension. TBD: Deprecate in favour of the `Cargo.toml` version?
pub fn new<N, V>(name: N, version: V) -> Self
where
N: Into<String>,
V: Into<String>,
{
Self {
module: ModuleEntry {
size: mem::size_of::<ModuleEntry>() as u16,
zend_api: ZEND_MODULE_API_NO,
zend_debug: ZEND_DEBUG as u8,
zts: USING_ZTS as u8,
ini_entry: ptr::null(),
deps: ptr::null(),
name: c_str(name),
functions: ptr::null(),
module_startup_func: None,
module_shutdown_func: None,
request_startup_func: None,
request_shutdown_func: None,
info_func: None,
version: c_str(version),
globals_size: 0,
globals_ptr: ptr::null::<c_void>() as *mut c_void,
globals_ctor: None,
globals_dtor: None,
post_deactivate_func: None,
module_started: 0,
type_: 0,
handle: ptr::null::<c_void>() as *mut c_void,
module_number: 0,
build_id: c_str(build_id()),
},
functions: vec![],
}
}
/// Sets the startup function for the extension.
///
/// # Arguments
///
/// * `func` - The function to be called on startup.
pub fn startup_function(mut self, func: StartupShutdownFunc) -> Self {
self.module.module_startup_func = Some(func);
self
}
/// Sets the shutdown function for the extension.
///
/// # Arguments
///
/// * `func` - The function to be called on shutdown.
pub fn shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
self.module.module_shutdown_func = Some(func);
self
}
/// Sets the request startup function for the extension.
///
/// # Arguments
///
/// * `func` - The function to be called when startup is requested.
pub fn request_startup_function(mut self, func: StartupShutdownFunc) -> Self {
self.module.module_startup_func = Some(func);
self
}
/// Sets the request shutdown function for the extension.
///
/// # Arguments
///
/// * `func` - The function to be called when shutdown is requested.
pub fn request_shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
self.module.module_shutdown_func = Some(func);
self
}
/// Sets the extension information function for the extension.
///
/// # Arguments
///
/// * `func` - The function to be called to retrieve the information about the extension.
pub fn info_function(mut self, func: InfoFunc) -> Self {
self.module.info_func = Some(func);
self
}
/// Adds a function to the extension.
///
/// # Arguments
///
/// * `func` - The function to be added to the extension.
pub fn function(mut self, func: FunctionEntry) -> Self {
self.functions.push(func);
self
}
/// Builds the extension and returns a `ModuleEntry`.
pub fn build(mut self) -> ModuleEntry {
// TODO: move to seperate function
self.functions.push(FunctionEntry {
fname: ptr::null() as *const c_char,
handler: None,
arg_info: ptr::null(),
num_args: 0,
flags: 0,
});
self.module.functions =
Box::into_raw(self.functions.into_boxed_slice()) as *const FunctionEntry;
self.module
}
}
impl ModuleEntry {
/// Converts the module entry into a raw pointer, releasing it to the C world.
pub fn into_raw(self) -> *mut Self {
Box::into_raw(Box::new(self))
}
}