This is an automated email from the ASF dual-hosted git repository. pnoltes pushed a commit to branch feature/599-provide-and-use-c-service-in-rust in repository https://gitbox.apache.org/repos/asf/celix.git
commit 3ef7b52989a202b5b7b2117fcfede58a57716a70 Author: Pepijn Noltes <pepijnnol...@gmail.com> AuthorDate: Fri Aug 25 15:13:08 2023 +0200 Add use_service poc for Rust experiment --- misc/experimental/rust/celix/src/bundle_context.rs | 134 ++++++++++++++++++++- misc/experimental/rust/rust_shell_api/src/lib.rs | 10 +- .../rust/shell_command_bundle/src/lib.rs | 133 ++++++++++++++------ 3 files changed, 239 insertions(+), 38 deletions(-) diff --git a/misc/experimental/rust/celix/src/bundle_context.rs b/misc/experimental/rust/celix/src/bundle_context.rs index 88fc4ed6..01022fa6 100644 --- a/misc/experimental/rust/celix/src/bundle_context.rs +++ b/misc/experimental/rust/celix/src/bundle_context.rs @@ -21,13 +21,16 @@ use std::any::type_name; use std::any::Any; use std::collections::HashMap; use std::ffi::c_void; -use std::ops::DerefMut; use std::ptr::null_mut; use std::sync::Arc; use std::sync::Mutex; use std::sync::Weak; use celix_bindings::celix_bundleContext_log; +use celix_bindings::celix_service_use_options_t; +use celix_bindings::celix_service_filter_options_t; +use celix_bindings::celix_bundleContext_useServicesWithOptions; +use celix_bindings::celix_bundleContext_useServiceWithOptions; use celix_bindings::celix_bundleContext_registerServiceWithOptions; use celix_bindings::celix_bundleContext_unregisterService; use celix_bindings::celix_bundle_context_t; @@ -193,12 +196,127 @@ impl ServiceRegistrationBuilder<'_> { self.validate()?; let svc_ptr = self.get_c_svc(); unsafe { - //TODO make unsafe part smaller (if possible) self.build_unsafe(svc_ptr) } } } +pub struct ServiceUseBuilder<'a> { + ctx: &'a BundleContextImpl, + many: bool, + service_name: String, + filter: String, + callback: Option<Box<Box<dyn Fn(&dyn Any)>>> +} + +impl ServiceUseBuilder<'_> { + fn new(ctx: &BundleContextImpl, many: bool) -> ServiceUseBuilder { + ServiceUseBuilder { + ctx, + many, + service_name: "".to_string(), + filter: "".to_string(), + callback: None, + } + } + + pub fn with_service<T: ?Sized + 'static>(&mut self) -> &mut Self { + self.service_name = type_name::<T>().to_string(); + self + } + + #[doc = " @brief Provide a callback which will be called when a service is available. T must be a Sized (non trait) type, due to the use of downcast_ref"] + pub fn with_callback<T: Sized + 'static>(&mut self, closure: Box<dyn Fn(&T)>)-> &mut Self { + if self.service_name.is_empty() { + self.with_service::<T>(); + } + + let any_closure = Box::new(move |any_svc: &dyn Any| { + if let Some(svc) = any_svc.downcast_ref::<T>() { + closure(svc); + } + }); + + self.callback = Some(Box::new(any_closure)); + self + } + + #[doc = " @brief Provide a callback which will be called when a service is available, with a dyn Any argument. Note that this is useful for trait objects."] + pub fn with_any_callback(&mut self, closure: Box<dyn Fn(&dyn Any)>)-> &mut Self { + self.callback = Some(Box::new(closure)); //note double boxed + self + } + + pub fn with_service_name(&mut self, name: &str) -> &mut Self { + self.service_name = name.to_string(); + self + } + + pub fn with_filter(&mut self, filter: &str) -> &mut Self { + self.filter = filter.to_string(); + self + } + + fn validate(&self) -> Result<(), Error> { + if self.callback.is_none() || self.service_name.is_empty() { + return Err(Error::BundleException); + } + Ok(()) + } + + unsafe extern "C" fn use_service_c_callback(handle: *mut c_void, svc: *mut c_void) { + let boxed_fn = Box::from_raw(handle as *mut Box<dyn Fn(&dyn Any)>); + let any_svc_ptr = svc as *mut dyn Any; + let any_svc_ref = any_svc_ptr.as_ref().unwrap(); + boxed_fn(any_svc_ref); + } + + pub fn build(&mut self) -> Result<isize, Error> { + self.validate()?; + + let c_service_name = std::ffi::CString::new(self.service_name.as_str()).unwrap(); + let c_filter = std::ffi::CString::new(self.filter.as_str()).unwrap(); + let c_service_name_ptr: *const i8 = c_service_name.as_ptr(); + + //Note filter is for now unused, introduce when updating to use of celix_bundleContext_useServiceWithOptions + let c_filter_ptr: *const i8 = if self.filter.is_empty() { null_mut()} else {c_filter.as_ptr() }; + + let boxed_fn = self.callback.take().unwrap(); + let fn_ptr = Box::into_raw(boxed_fn) as *mut c_void; + + + let opts = celix_service_use_options_t { + filter: celix_service_filter_options_t { + serviceName: c_service_name_ptr, + versionRange: null_mut(), + filter: c_filter_ptr, + serviceLanguage: null_mut(), + ignoreServiceLanguage: false, + }, + waitTimeoutInSeconds: 0.0, + callbackHandle: fn_ptr, + use_: Some(Self::use_service_c_callback), + useWithProperties: None, + useWithOwner: None, + flags: 0, + }; + + unsafe { + if self.many { + let count = celix_bundleContext_useServicesWithOptions(self.ctx.get_c_bundle_context(), &opts); + Ok(count as isize) + } else { + let called = celix_bundleContext_useServiceWithOptions(self.ctx.get_c_bundle_context(), &opts); + if called { + Ok(1) + } else { + Ok(0) + } + } + } + } +} + pub trait BundleContext { fn get_c_bundle_context(&self) -> *mut celix_bundle_context_t; @@ -217,6 +335,10 @@ pub trait BundleContext { fn log_fatal(&self, message: &str); fn register_service(&self) -> ServiceRegistrationBuilder; + + fn use_service(&self) -> ServiceUseBuilder; + + fn use_services(&self) -> ServiceUseBuilder; } struct BundleContextImpl { @@ -305,6 +427,14 @@ impl BundleContext for BundleContextImpl { fn register_service(&self) -> ServiceRegistrationBuilder { ServiceRegistrationBuilder::new(self) } + + fn use_service(&self) -> ServiceUseBuilder { + ServiceUseBuilder::new(self, false) + } + + fn use_services(&self) -> ServiceUseBuilder { + ServiceUseBuilder::new(self, true) + } } pub fn bundle_context_new(c_bundle_context: *mut celix_bundle_context_t) -> Arc<dyn BundleContext> { diff --git a/misc/experimental/rust/rust_shell_api/src/lib.rs b/misc/experimental/rust/rust_shell_api/src/lib.rs index 79a11de2..0969f86c 100644 --- a/misc/experimental/rust/rust_shell_api/src/lib.rs +++ b/misc/experimental/rust/rust_shell_api/src/lib.rs @@ -24,6 +24,12 @@ pub const COMMAND_NAME: &str = "command.name"; pub const COMMAND_USAGE: &str = "command.usage"; pub const COMMAND_DESCRIPTION: &str = "command.description"; -pub trait RustShellCommand { - fn execute_command(&mut self, command_line: &str) -> Result<(), Error>; +#[doc = "A trait to implement a Celix Shell Command"] +pub trait RustShellCommandTrait { + fn execute_command(&self, command_line: &str) -> Result<(), Error>; +} + +#[doc = "A struct to register a Rust Shell Command"] +pub struct RustShellCommandStruct { + pub execute_command: Box<dyn Fn(&str) -> Result<(), Error>> } diff --git a/misc/experimental/rust/shell_command_bundle/src/lib.rs b/misc/experimental/rust/shell_command_bundle/src/lib.rs index 0ab73f78..50d2905f 100644 --- a/misc/experimental/rust/shell_command_bundle/src/lib.rs +++ b/misc/experimental/rust/shell_command_bundle/src/lib.rs @@ -21,14 +21,15 @@ extern crate celix; extern crate celix_bindings; extern crate rust_shell_api; +use std::any::Any; use std::ffi::c_char; use std::ffi::c_void; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use celix::BundleActivator; use celix::BundleContext; use celix::Error; -use rust_shell_api::RustShellCommand; +use rust_shell_api::{RustShellCommandTrait, RustShellCommandStruct}; use celix_bindings::celix_shell_command_t; use celix_bindings::FILE; @@ -53,17 +54,18 @@ impl CShellCommandImpl { return false; } unsafe { - println!("call_execute_command"); let obj = &mut *(handle as *mut CShellCommandImpl); - let str_command_line = std::ffi::CStr::from_ptr(command_line).to_str().unwrap(); - obj.execute_command(str_command_line); + let str_command_line = std::ffi::CStr::from_ptr(command_line).to_str(); + if str_command_line.is_err() { + return false; + } + obj.execute_command(str_command_line.unwrap()); } true } fn execute_command(&mut self, command_line: &str) { - self.ctx - .log_info(format!("Execute command: {}", command_line).as_str()); + self.ctx.log_info(format!("Execute command: \"{}\"", command_line).as_str()); } } @@ -80,8 +82,8 @@ impl RustShellCommandImpl { } } -impl RustShellCommand for RustShellCommandImpl { - fn execute_command(&mut self, command_line: &str) -> Result<(), Error> { +impl RustShellCommandTrait for RustShellCommandImpl { + fn execute_command(&self, command_line: &str) -> Result<(), Error> { self.ctx .log_info(format!("Execute command: {}.", command_line).as_str()); Ok(()) @@ -92,26 +94,13 @@ struct ShellCommandActivator { ctx: Arc<dyn BundleContext>, //log_helper: Box<dyn LogHelper>, shell_command_provider: CShellCommandImpl, - c_registration: Option<celix::ServiceRegistration>, - rust_registration: Option<celix::ServiceRegistration>, - rust_registration2: Option<celix::ServiceRegistration>, + registrations: Vec<celix::ServiceRegistration>, } -impl BundleActivator for ShellCommandActivator { - fn new(ctx: Arc<dyn celix::BundleContext>) -> Self { - let result = ShellCommandActivator { - ctx: ctx.clone(), - shell_command_provider: CShellCommandImpl::new(ctx.clone()), - //log_helper: log_helper_new(&*ctx, "ShellCommandBundle"), - c_registration: None, - rust_registration: None, - rust_registration2: None, - }; - result - } - fn start(&mut self) -> Result<(), Error> { +impl ShellCommandActivator { - //C service registered as direct type + fn register_services(&mut self) -> Result<(), Error> { + //Register C service registered as value let registration = self.ctx.register_service() .with_service(celix_shell_command_t { handle: &mut self.shell_command_provider as *mut CShellCommandImpl as *mut c_void, @@ -121,28 +110,104 @@ impl BundleActivator for ShellCommandActivator { .with_property("command.name", "exe_c_command_in_rust") .with_property("command.description", "Simple command written in Rust") .build()?; - self.c_registration = Some(registration); + self.registrations.push(registration); self.ctx.log_info("C Shell Command registered"); - //Rust service register using a Box with a trait + //Register Rust trait service register using a Box let rust_shell_command = Box::new(RustShellCommandImpl::new(self.ctx.clone())); - let rust_shell_command = Box::<dyn RustShellCommand>::from(rust_shell_command); + let rust_shell_command = Box::<dyn RustShellCommandTrait>::from(rust_shell_command); let registration = self.ctx.register_service() //maybe make svc types more explicit, e.g.with type parameters .with_boxed_service(rust_shell_command) .with_property(rust_shell_api::COMMAND_NAME, "exe_rust_command") - .with_property(rust_shell_api::COMMAND_DESCRIPTION, "Simple command written in Rust") + .with_property(rust_shell_api::COMMAND_DESCRIPTION, "Simple command written in a Rust trait") + .build()?; + self.registrations.push(registration); + self.ctx.log_info("Rust trait Shell Command registered"); + + + //Register Rust struct service (with closures) as value + let cloned_ctx = self.ctx.clone(); + let rust_shell_command = RustShellCommandStruct { + execute_command: Box::new(move |command_line: &str| { + cloned_ctx.log_info(format!("Execute command: {}", command_line).as_str()); + Ok(()) + }), + }; + let registration = self.ctx.register_service() + .with_service(rust_shell_command) + .with_property("command.name", "exe_rust_command2") + .with_property("command.description", "Simple command written in a Rust struct using a Rust closure") .build()?; - self.rust_registration = Some(registration); + self.registrations.push(registration); + self.ctx.log_info("Rust struct Shell Command registered"); + Ok(()) + } + fn use_services(&mut self) -> Result<(), Error> { + //test using C service + + self.ctx.log_info("Use C service command service"); + let count = self.ctx.use_services() + .with_service_name("celix_shell_command") + .with_filter("(command.name=exe_c_command_in_rust)") + .with_callback(Box::new( |svc: &celix_shell_command_t| { + if let Some(exe_cmd) = svc.executeCommand { + let c_str = std::ffi::CString::new("test").unwrap(); + unsafe { + exe_cmd(svc.handle, c_str.as_ptr() as *const c_char, std::ptr::null_mut(), std::ptr::null_mut()); + } + } + })) + .build()?; + self.ctx.log_info(format!("Found {} celix_shell_command_t services", count).as_str()); + + //test using Rust trait service + self.ctx.log_info("Use Rust trait service command service"); + let count = self.ctx.use_services() + .with_service::<dyn RustShellCommandTrait>() + .with_any_callback(Box::new( |svc: &dyn Any| { + let typed_svc = svc.downcast_ref::<RustShellCommandImpl>(); + if let Some(svc) = typed_svc { + let _ = svc.execute_command("test"); + } + })) + .build()?; + self.ctx.log_info(format!("Found {} RustShellCommandTrait services", count).as_str()); + + self.ctx.log_info("Use Rust struct service command service"); + let count = self.ctx.use_services() + .with_callback(Box::new( |svc: &RustShellCommandStruct| { + let exe_cmd = svc.execute_command.as_ref(); + let _ = exe_cmd("test"); + })) + .build()?; + self.ctx.log_info(format!("Found {} RustShellCommandStruct services", count).as_str()); + + self.ctx.log_info("Rust Shell Command started"); + Ok(()) + } +} + +impl BundleActivator for ShellCommandActivator { + fn new(ctx: Arc<dyn celix::BundleContext>) -> Self { + let result = ShellCommandActivator { + ctx: ctx.clone(), + shell_command_provider: CShellCommandImpl::new(ctx.clone()), + //log_helper: log_helper_new(&*ctx, "ShellCommandBundle"), + registrations: Vec::new(), + }; + result + } + fn start(&mut self) -> Result<(), Error> { + self.register_services()?; + self.use_services()?; self.ctx.log_info("Rust Shell Command started"); Ok(()) } fn stop(&mut self) -> Result<(), Error> { - self.rust_registration2 = None; - self.rust_registration = None; - self.c_registration = None; + self.registrations.clear(); self.ctx.log_info("Rust Shell Command stopped"); Ok(()) }