Skip to content

Commit 6938fb4

Browse files
committed
Add a suffix to rust functions exported to C++ side to avoid duplicate link symbols
1 parent 558178a commit 6938fb4

File tree

5 files changed

+64
-19
lines changed

5 files changed

+64
-19
lines changed

build.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ fn make_modules(opencv_dir: &Path) -> Result<()> {
177177
Ok(())
178178
}
179179

180-
fn build_compiler(opencv: &Library) -> cc::Build {
180+
fn build_compiler(opencv: &Library, ffi_export_suffix: &str) -> cc::Build {
181181
let mut out = cc::Build::new();
182182
out.cpp(true)
183183
.std("c++14") // clang says error: 'auto' return without trailing return type; deduced return types are a C++14 extension
@@ -231,6 +231,7 @@ fn build_compiler(opencv: &Library) -> cc::Build {
231231
} else {
232232
out.flag_if_supported("-Wa,-mbig-obj");
233233
}
234+
out.define("OCVRS_FFI_EXPORT_SUFFIX", ffi_export_suffix);
234235
out
235236
}
236237

@@ -251,8 +252,7 @@ fn setup_rerun() -> Result<()> {
251252
Ok(())
252253
}
253254

254-
fn build_wrapper(opencv: &Library) {
255-
let mut cc = build_compiler(opencv);
255+
fn build_wrapper(mut cc: cc::Build) {
256256
eprintln!("=== Compiler information: {:#?}", cc.get_compiler());
257257
let modules = MODULES.get().expect("MODULES not initialized");
258258
static SUPPORTED_MODULES: [&str; 67] = [
@@ -351,7 +351,8 @@ fn main() -> Result<()> {
351351
return Ok(());
352352
}
353353

354-
eprintln!("=== Crate version: {:?}", env::var_os("CARGO_PKG_VERSION"));
354+
let pkg_version = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "unknown_crate_version".to_string());
355+
eprintln!("=== Crate version: {pkg_version}");
355356
eprintln!("=== Environment configuration:");
356357
for v in AFFECTING_ENV_VARS.into_iter().chain(DEBUG_ENV_VARS) {
357358
eprintln!("=== {v} = {:?}", env::var_os(v));
@@ -425,9 +426,11 @@ fn main() -> Result<()> {
425426

426427
setup_rerun()?;
427428

429+
let ffi_export_suffix = format!("_{}", pkg_version.replace(".", "_"));
428430
let binding_generator = BindingGenerator::new(build_script_path);
429-
binding_generator.generate_wrapper(opencv_header_dir, &opencv)?;
430-
build_wrapper(&opencv);
431+
binding_generator.generate_wrapper(opencv_header_dir, &opencv, &ffi_export_suffix)?;
432+
let cc = build_compiler(&opencv, &ffi_export_suffix);
433+
build_wrapper(cc);
431434
// -l linker args should be emitted after -l static
432435
opencv.emit_cargo_metadata();
433436
Ok(())

build/generator.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ impl BindingGenerator {
2222
Self { build_script_path }
2323
}
2424

25-
pub fn generate_wrapper(&self, opencv_header_dir: &Path, opencv: &Library) -> Result<()> {
25+
pub fn generate_wrapper(&self, opencv_header_dir: &Path, opencv: &Library, ffi_export_suffix: &str) -> Result<()> {
2626
let target_docs_dir = env::var_os("OCVRS_DOCS_GENERATE_DIR").map(PathBuf::from);
2727
let target_module_dir = OUT_DIR.join("opencv");
2828
let manual_dir = SRC_DIR.join("manual");
@@ -48,8 +48,7 @@ impl BindingGenerator {
4848

4949
self.run(modules, opencv_header_dir, opencv)?;
5050

51-
let collector = Collector::new(modules, &target_module_dir, &manual_dir, &OUT_DIR);
52-
collector.collect_bindings()?;
51+
Collector::new(modules, &ffi_export_suffix, &target_module_dir, &manual_dir, &OUT_DIR).collect_bindings()?;
5352

5453
if let Some(target_docs_dir) = target_docs_dir {
5554
if !target_docs_dir.exists() {

build/generator/collector.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,23 @@ use super::super::{files_with_extension, Result};
99

1010
pub struct Collector<'r> {
1111
modules: &'r [String],
12+
ffi_export_suffix: &'r str,
1213
target_module_dir: &'r Path,
1314
manual_dir: &'r Path,
1415
out_dir: &'r Path,
1516
}
1617

1718
impl<'r> Collector<'r> {
18-
pub fn new(modules: &'r [String], target_module_dir: &'r Path, manual_dir: &'r Path, out_dir: &'r Path) -> Self {
19+
pub fn new(
20+
modules: &'r [String],
21+
ffi_export_suffix: &'r str,
22+
target_module_dir: &'r Path,
23+
manual_dir: &'r Path,
24+
out_dir: &'r Path,
25+
) -> Self {
1926
Self {
2027
modules,
28+
ffi_export_suffix,
2129
target_module_dir,
2230
manual_dir,
2331
out_dir,
@@ -80,6 +88,7 @@ impl<'r> Collector<'r> {
8088
writeln!(hub_rs, "\tpub use super::{module}::prelude::*;")?;
8189
}
8290
writeln!(hub_rs, "}}")?;
91+
self.inject_ffi_exports(&mut hub_rs)?;
8392
eprintln!("=== Total binding collection time: {:?}", start.elapsed());
8493
Ok(())
8594
}
@@ -172,6 +181,30 @@ impl<'r> Collector<'r> {
172181
Ok(())
173182
}
174183

184+
/// The #no_mangle function in the bindings cause duplicate export names when 2 different version of the crate are used
185+
/// (https://github.com/twistedfall/opencv-rust/issues/597). This function injects the version of the exported functions with
186+
/// a crate version suffix to avoid this conflict. On the C++ side it works with the help of the `OCVRS_FFI_EXPORT_SUFFIX`
187+
/// macro which is passed in `build_compiler()`.
188+
fn inject_ffi_exports(&self, hub_rs: &mut impl Write) -> Result<()> {
189+
writeln!(hub_rs, "\nmod ffi_exports {{")?;
190+
writeln!(hub_rs, "\tuse crate::mod_prelude_sys::*;")?;
191+
write!(hub_rs, "\t")?;
192+
writeln!(
193+
hub_rs,
194+
r#"#[no_mangle] unsafe extern "C" fn ocvrs_create_string{}(s: *const c_char) -> *mut String {{ crate::templ::ocvrs_create_string(s) }}"#,
195+
self.ffi_export_suffix
196+
)?;
197+
write!(hub_rs, "\t")?;
198+
writeln!(
199+
hub_rs,
200+
r#"#[no_mangle] unsafe extern "C" fn ocvrs_create_byte_string{}(v: *const u8, len: size_t) -> *mut Vec<u8> {{ crate::templ::ocvrs_create_byte_string(v, len) }}"#,
201+
self.ffi_export_suffix
202+
)?;
203+
writeln!(hub_rs, "}}")?;
204+
205+
Ok(())
206+
}
207+
175208
fn write_use_manual(&self, file: &mut BufWriter<File>, module: &str) -> Result<bool> {
176209
if self.manual_dir.join(format!("{module}.rs")).exists() {
177210
writeln!(file, "pub use crate::manual::{module}::*;")?;

src/templ.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,16 +112,16 @@ macro_rules! return_receive {
112112
};
113113
}
114114

115-
/// The return type of this function goes into `receive_string`
116-
#[export_name = "ocvrs_create_string"]
117-
unsafe extern "C" fn ocvrs_create_string(s: *const c_char) -> *mut String {
115+
/// The return type of this function goes into `receive_string::<String>()`
116+
#[inline]
117+
pub unsafe fn ocvrs_create_string(s: *const c_char) -> *mut String {
118118
let s = CStr::from_ptr(s).to_string_lossy().into_owned();
119119
Box::into_raw(Box::new(s))
120120
}
121121

122-
/// The return type of this function goes into `receive_byte_string`
123-
#[export_name = "ocvrs_create_byte_string"]
124-
unsafe extern "C" fn ocvrs_create_byte_string(v: *const u8, len: size_t) -> *mut Vec<u8> {
122+
/// The return type of this function goes into `receive_string::<Vec<u8>>()`
123+
#[inline]
124+
pub unsafe fn ocvrs_create_byte_string(v: *const u8, len: size_t) -> *mut Vec<u8> {
125125
let byte_slice = if v.is_null() {
126126
&[]
127127
} else {

src_cpp/ocvrs_common.hpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
// strip dnn experimental ns when generating headers
1212
#define CV_DNN_DONT_ADD_EXPERIMENTAL_NS
1313
#define CV_DNN_DONT_ADD_INLINE_NS
14+
// the FFI export suffix only matters during actual linking
15+
#define OCVRS_FFI_EXPORT_SUFFIX
1416
#endif
1517

1618
#include <memory>
@@ -29,9 +31,17 @@ catch (cv::Exception& e) { \
2931
OCVRS_HANDLE(cv::Error::StsError, "Unspecified error, neither from OpenCV nor from std", return_name); \
3032
}
3133

32-
// defined in src/templ.rs
33-
extern "C" void* ocvrs_create_string(const char*);
34-
extern "C" void* ocvrs_create_byte_string(const char*, size_t);
34+
// double-expansion macro trick to expand the OCVRS_FFI_EXPORT_SUFFIX macro
35+
#define CONCATENATE(prefix, suffix) prefix##suffix
36+
#define SUFFIXED_NAME(name, suffix) CONCATENATE(name, suffix)
37+
38+
// defined in build/generator/collector.rs Collector::inject_ffi_exports, see `inject_ffi_exports()` function for explanation
39+
extern "C" void* SUFFIXED_NAME(ocvrs_create_string, OCVRS_FFI_EXPORT_SUFFIX)(const char*);
40+
extern "C" void* SUFFIXED_NAME(ocvrs_create_byte_string, OCVRS_FFI_EXPORT_SUFFIX)(const char*, size_t);
41+
42+
// "aliases" for the above functions provided by Rust, to be used in the generated code
43+
inline void* ocvrs_create_string(const char* s) { return SUFFIXED_NAME(ocvrs_create_string, OCVRS_FFI_EXPORT_SUFFIX)(s); }
44+
inline void* ocvrs_create_byte_string(const char* s, size_t len) { return SUFFIXED_NAME(ocvrs_create_byte_string, OCVRS_FFI_EXPORT_SUFFIX)(s, len); }
3545

3646
template<typename T> struct Result {
3747
int error_code;

0 commit comments

Comments
 (0)