Skip to content

Commit d3876fd

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

File tree

6 files changed

+60
-19
lines changed

6 files changed

+60
-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
@@ -227,6 +227,7 @@ fn build_compiler(opencv: &Library) -> cc::Build {
227227
} else {
228228
out.flag_if_supported("-Wa,-mbig-obj");
229229
}
230+
out.define("OCVRS_FFI_EXPORT_SUFFIX", ffi_export_suffix);
230231
out
231232
}
232233

@@ -247,8 +248,7 @@ fn setup_rerun() -> Result<()> {
247248
Ok(())
248249
}
249250

250-
fn build_wrapper(opencv: &Library) {
251-
let mut cc = build_compiler(opencv);
251+
fn build_wrapper(mut cc: cc::Build) {
252252
eprintln!("=== Compiler information: {:#?}", cc.get_compiler());
253253
let modules = MODULES.get().expect("MODULES not initialized");
254254
static SUPPORTED_MODULES: [&str; 67] = [
@@ -347,7 +347,8 @@ fn main() -> Result<()> {
347347
return Ok(());
348348
}
349349

350-
eprintln!("=== Crate version: {:?}", env::var_os("CARGO_PKG_VERSION"));
350+
let pkg_version = env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "unknown_crate_version".to_string());
351+
eprintln!("=== Crate version: {pkg_version}");
351352
eprintln!("=== Environment configuration:");
352353
for v in AFFECTING_ENV_VARS.into_iter().chain(DEBUG_ENV_VARS) {
353354
eprintln!("=== {v} = {:?}", env::var_os(v));
@@ -421,9 +422,11 @@ fn main() -> Result<()> {
421422

422423
setup_rerun()?;
423424

425+
let ffi_export_suffix = format!("_{}", pkg_version.replace(".", "_"));
424426
let binding_generator = BindingGenerator::new(build_script_path);
425-
binding_generator.generate_wrapper(opencv_header_dir, &opencv)?;
426-
build_wrapper(&opencv);
427+
binding_generator.generate_wrapper(opencv_header_dir, &opencv, &ffi_export_suffix)?;
428+
let cc = build_compiler(&opencv, &ffi_export_suffix);
429+
build_wrapper(cc);
427430
// -l linker args should be emitted after -l static
428431
opencv.emit_cargo_metadata();
429432
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/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ pub mod platform_types {
2626
}
2727

2828
/// Prelude for sys (externs) module and types
29-
pub(crate) mod mod_prelude_sys {
29+
pub mod mod_prelude_sys {
3030
pub use std::ffi::{c_char, c_void};
3131

3232
pub use crate::platform_types::*;
3333
pub use crate::traits::{Boxed, OpenCVFromExtern, OpenCVIntoExternContainer, OpenCVTypeExternContainer};
3434
}
3535

3636
/// Prelude for generated modules and types
37-
pub(crate) mod mod_prelude {
37+
pub mod mod_prelude {
3838
pub use crate::boxed_ref::{BoxedRef, BoxedRefMut};
3939
pub use crate::core::{ToInputArray, ToInputOutputArray, ToOutputArray};
4040
pub use crate::hub_prelude::*;

src/templ.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,15 @@ macro_rules! return_receive {
113113
}
114114

115115
/// 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 {
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

122122
/// 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> {
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: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#ifdef OCVRS_PARSING_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,13 @@ 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 stringification macro trick
35+
#define STRINGIFY1(x) #x
36+
#define STRINGIFY(x) STRINGIFY1(x)
37+
38+
// defined in build/generator/collector.rs Collector::inject_ffi_exports, `asm` is used to rename imported function
39+
extern "C" void* ocvrs_create_string(const char*) asm ("ocvrs_create_string" STRINGIFY(OCVRS_FFI_EXPORT_SUFFIX));
40+
extern "C" void* ocvrs_create_byte_string(const char*, size_t) asm ("ocvrs_create_byte_string" STRINGIFY(OCVRS_FFI_EXPORT_SUFFIX));
3541

3642
template<typename T> struct Result {
3743
int error_code;

0 commit comments

Comments
 (0)