mirror of
https://github.com/danog/ext-php-rs.git
synced 2024-11-26 20:15:22 +01:00
added module builder, example project, documentation
This commit is contained in:
parent
50d774ee31
commit
7587dccd0d
36
build.rs
36
build.rs
@ -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
4
example/skel/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
/.vscode
|
||||
expand.rs
|
14
example/skel/Cargo.toml
Normal file
14
example/skel/Cargo.toml
Normal 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
19
example/skel/src/lib.rs
Normal 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
1
src/bindings.rs
Normal file
@ -0,0 +1 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
25
src/lib.rs
25
src/lib.rs
@ -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();
|
||||
}
|
||||
|
@ -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)),*);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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() };
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
pub mod info;
|
1
src/php/function.rs
Normal file
1
src/php/function.rs
Normal file
@ -0,0 +1 @@
|
||||
|
2
src/php/mod.rs
Normal file
2
src/php/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod function;
|
||||
pub mod module;
|
169
src/php/module.rs
Normal file
169
src/php/module.rs
Normal 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))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user