From 6accf686201c6c52909606639b1cfdd425447e5f Mon Sep 17 00:00:00 2001 From: Pro Date: Thu, 19 Sep 2024 20:47:22 +0200 Subject: [PATCH] Add a suffix to rust functions exported to C++ side to avoid duplicate link symbols --- build.rs | 15 +++++++++------ build/generator.rs | 5 ++--- build/generator/collector.rs | 35 ++++++++++++++++++++++++++++++++++- src/lib.rs | 4 ++-- src/templ.rs | 8 ++++---- src_cpp/ocvrs_common.hpp | 12 +++++++++--- 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/build.rs b/build.rs index ff9e1680..11815b24 100644 --- a/build.rs +++ b/build.rs @@ -177,7 +177,7 @@ fn make_modules(opencv_dir: &Path) -> Result<()> { Ok(()) } -fn build_compiler(opencv: &Library) -> cc::Build { +fn build_compiler(opencv: &Library, ffi_export_suffix: &str) -> cc::Build { let mut out = cc::Build::new(); out.cpp(true) .std("c++14") // clang says error: 'auto' return without trailing return type; deduced return types are a C++14 extension @@ -227,6 +227,7 @@ fn build_compiler(opencv: &Library) -> cc::Build { } else { out.flag_if_supported("-Wa,-mbig-obj"); } + out.define("OCVRS_FFI_EXPORT_SUFFIX", ffi_export_suffix); out } @@ -247,8 +248,7 @@ fn setup_rerun() -> Result<()> { Ok(()) } -fn build_wrapper(opencv: &Library) { - let mut cc = build_compiler(opencv); +fn build_wrapper(mut cc: cc::Build) { eprintln!("=== Compiler information: {:#?}", cc.get_compiler()); let modules = MODULES.get().expect("MODULES not initialized"); static SUPPORTED_MODULES: [&str; 67] = [ @@ -347,7 +347,8 @@ fn main() -> Result<()> { return Ok(()); } - eprintln!("=== Crate version: {:?}", env::var_os("CARGO_PKG_VERSION")); + let pkg_version = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "unknown_crate_version".to_string()); + eprintln!("=== Crate version: {pkg_version}"); eprintln!("=== Environment configuration:"); for v in AFFECTING_ENV_VARS.into_iter().chain(DEBUG_ENV_VARS) { eprintln!("=== {v} = {:?}", env::var_os(v)); @@ -421,9 +422,11 @@ fn main() -> Result<()> { setup_rerun()?; + let ffi_export_suffix = format!("_{}", pkg_version.replace(".", "_")); let binding_generator = BindingGenerator::new(build_script_path); - binding_generator.generate_wrapper(opencv_header_dir, &opencv)?; - build_wrapper(&opencv); + binding_generator.generate_wrapper(opencv_header_dir, &opencv, &ffi_export_suffix)?; + let cc = build_compiler(&opencv, &ffi_export_suffix); + build_wrapper(cc); // -l linker args should be emitted after -l static opencv.emit_cargo_metadata(); Ok(()) diff --git a/build/generator.rs b/build/generator.rs index cf7737e9..3fb131df 100644 --- a/build/generator.rs +++ b/build/generator.rs @@ -22,7 +22,7 @@ impl BindingGenerator { Self { build_script_path } } - pub fn generate_wrapper(&self, opencv_header_dir: &Path, opencv: &Library) -> Result<()> { + pub fn generate_wrapper(&self, opencv_header_dir: &Path, opencv: &Library, ffi_export_suffix: &str) -> Result<()> { let target_docs_dir = env::var_os("OCVRS_DOCS_GENERATE_DIR").map(PathBuf::from); let target_module_dir = OUT_DIR.join("opencv"); let manual_dir = SRC_DIR.join("manual"); @@ -48,8 +48,7 @@ impl BindingGenerator { self.run(modules, opencv_header_dir, opencv)?; - let collector = Collector::new(modules, &target_module_dir, &manual_dir, &OUT_DIR); - collector.collect_bindings()?; + Collector::new(modules, &ffi_export_suffix, &target_module_dir, &manual_dir, &OUT_DIR).collect_bindings()?; if let Some(target_docs_dir) = target_docs_dir { if !target_docs_dir.exists() { diff --git a/build/generator/collector.rs b/build/generator/collector.rs index 5c048e29..98678b3b 100644 --- a/build/generator/collector.rs +++ b/build/generator/collector.rs @@ -9,15 +9,23 @@ use super::super::{files_with_extension, Result}; pub struct Collector<'r> { modules: &'r [String], + ffi_export_suffix: &'r str, target_module_dir: &'r Path, manual_dir: &'r Path, out_dir: &'r Path, } impl<'r> Collector<'r> { - pub fn new(modules: &'r [String], target_module_dir: &'r Path, manual_dir: &'r Path, out_dir: &'r Path) -> Self { + pub fn new( + modules: &'r [String], + ffi_export_suffix: &'r str, + target_module_dir: &'r Path, + manual_dir: &'r Path, + out_dir: &'r Path, + ) -> Self { Self { modules, + ffi_export_suffix, target_module_dir, manual_dir, out_dir, @@ -80,6 +88,7 @@ impl<'r> Collector<'r> { writeln!(hub_rs, "\tpub use super::{module}::prelude::*;")?; } writeln!(hub_rs, "}}")?; + self.inject_ffi_exports(&mut hub_rs)?; eprintln!("=== Total binding collection time: {:?}", start.elapsed()); Ok(()) } @@ -172,6 +181,30 @@ impl<'r> Collector<'r> { Ok(()) } + /// The #no_mangle function in the bindings cause duplicate export names when 2 different version of the crate are used + /// (https://github.com/twistedfall/opencv-rust/issues/597). This function injects the version of the exported functions with + /// a crate version suffix to avoid this conflict. On the C++ side it works with the help of the `OCVRS_FFI_EXPORT_SUFFIX` + /// macro which is passed in `build_compiler()`. + fn inject_ffi_exports(&self, hub_rs: &mut impl Write) -> Result<()> { + writeln!(hub_rs, "\nmod ffi_exports {{")?; + writeln!(hub_rs, "\tuse crate::mod_prelude_sys::*;")?; + write!(hub_rs, "\t")?; + writeln!( + hub_rs, + r#"#[no_mangle] unsafe extern "C" fn ocvrs_create_string{}(s: *const c_char) -> *mut String {{ crate::templ::ocvrs_create_string(s) }}"#, + self.ffi_export_suffix + )?; + write!(hub_rs, "\t")?; + writeln!( + hub_rs, + r#"#[no_mangle] unsafe extern "C" fn ocvrs_create_byte_string{}(v: *const u8, len: size_t) -> *mut Vec {{ crate::templ::ocvrs_create_byte_string(v, len) }}"#, + self.ffi_export_suffix + )?; + writeln!(hub_rs, "}}")?; + + Ok(()) + } + fn write_use_manual(&self, file: &mut BufWriter, module: &str) -> Result { if self.manual_dir.join(format!("{module}.rs")).exists() { writeln!(file, "pub use crate::manual::{module}::*;")?; diff --git a/src/lib.rs b/src/lib.rs index 6c48e1c6..8890e119 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ pub mod platform_types { } /// Prelude for sys (externs) module and types -pub(crate) mod mod_prelude_sys { +pub mod mod_prelude_sys { pub use std::ffi::{c_char, c_void}; pub use crate::platform_types::*; @@ -34,7 +34,7 @@ pub(crate) mod mod_prelude_sys { } /// Prelude for generated modules and types -pub(crate) mod mod_prelude { +pub mod mod_prelude { pub use crate::boxed_ref::{BoxedRef, BoxedRefMut}; pub use crate::core::{ToInputArray, ToInputOutputArray, ToOutputArray}; pub use crate::hub_prelude::*; diff --git a/src/templ.rs b/src/templ.rs index a1112ebb..7f3ad853 100644 --- a/src/templ.rs +++ b/src/templ.rs @@ -113,15 +113,15 @@ macro_rules! return_receive { } /// The return type of this function goes into `receive_string` -#[export_name = "ocvrs_create_string"] -unsafe extern "C" fn ocvrs_create_string(s: *const c_char) -> *mut String { +#[inline] +pub unsafe fn ocvrs_create_string(s: *const c_char) -> *mut String { let s = CStr::from_ptr(s).to_string_lossy().into_owned(); Box::into_raw(Box::new(s)) } /// The return type of this function goes into `receive_byte_string` -#[export_name = "ocvrs_create_byte_string"] -unsafe extern "C" fn ocvrs_create_byte_string(v: *const u8, len: size_t) -> *mut Vec { +#[inline] +pub unsafe fn ocvrs_create_byte_string(v: *const u8, len: size_t) -> *mut Vec { let byte_slice = if v.is_null() { &[] } else { diff --git a/src_cpp/ocvrs_common.hpp b/src_cpp/ocvrs_common.hpp index d6d0cf65..0b675713 100644 --- a/src_cpp/ocvrs_common.hpp +++ b/src_cpp/ocvrs_common.hpp @@ -11,6 +11,8 @@ #ifdef OCVRS_PARSING_HEADERS #define CV_DNN_DONT_ADD_EXPERIMENTAL_NS #define CV_DNN_DONT_ADD_INLINE_NS + // the FFI export suffix only matters during actual linking + #define OCVRS_FFI_EXPORT_SUFFIX "" #endif #include @@ -29,9 +31,13 @@ catch (cv::Exception& e) { \ OCVRS_HANDLE(cv::Error::StsError, "Unspecified error, neither from OpenCV nor from std", return_name); \ } -// defined in src/templ.rs -extern "C" void* ocvrs_create_string(const char*); -extern "C" void* ocvrs_create_byte_string(const char*, size_t); +// double-expansion stringification macro trick +#define STRINGIFY1(x) #x +#define STRINGIFY(x) STRINGIFY1(x) + +// defined in build/generator/collector.rs Collector::inject_ffi_exports, `__asm` is used to rename imported function +extern "C" void* ocvrs_create_string(const char*) __asm ("ocvrs_create_string" STRINGIFY(OCVRS_FFI_EXPORT_SUFFIX)); +extern "C" void* ocvrs_create_byte_string(const char*, size_t) __asm ("ocvrs_create_byte_string" STRINGIFY(OCVRS_FFI_EXPORT_SUFFIX)); template struct Result { int error_code;