I’m using cbindgen/SWIG and the cc crate to ultimately use a Rust library I’m building from C#. This requires including the contents of a single .o file into the resulting dynamic library, but I’m unable to make that happen: the required symbols never show up in the output generated by rustc.
For a static library I can use +whole-archive, for example like so
and nm target/debug/libmycrate.a shows me all the desired symbols.
How can I achieve this for a dynamic library? Even mentioning symbols in the extern block does not get them included in target/debug/libmycrate.so. (I did check that using the wrong name yields a linker error, so the link attribute does something).
I found another strange behaviour: I can make a symbol show up in the dynamic library by referencing it from a pub fn, but only if that function has attribute #[no_mangle].
I believe if you include all functions you want to export in the extern "C" {} block (and make them public I think) that they will be exported. Otherwise rustc doesn't know that those functions exist in the first place and as such doesn't add them to the symbol export list.
Edit: are you writing a cdylib? in that case my suggestion doesn't work I think. You need #[no_mangle] on a function calling those functions to be exported. Without #[no_mangle] rustc won't export public rust functions as they can't be used anyway for cdylibs due to the lack of crate metadata compared to rust dylibs.
Thanks for the confirmation! Meanwhile I dug deeper and found the precise mechanism that thwarts all my efforts: rustc uses cc -Wl,--version-file=... to install a whitelist for exporting symbols, and it seems that no following cmdline options can change ld’s mind afterwards — I tried replacing the whole linker script with -C link-args=-Wl,-T,myscript or adding a new version script in the same way, no success.
This is somewhat annoying, I think it should be possible to modify the whitelist without having to declare an auxiliary symbol that is only needed to “use” the symbols I want to export — it would be much more robust to be able to say “just add this .o to the link and export all public symbols from it”. Do you think it makes sense to open a discussion on IRLO or a github ticket?
An extern block just declares that some functions are available somewhere. When you add the #[link(...)] attribute it tells the Rust compiler that your crate will need to statically link against the swig_wrap library, however the linker is still free to throw out any code that is never used (--gc-sections).
When defining your own cdylib, you should be able to pub use the foreign symbols from your crate root to make them available in the end binary.
For example, I made this experiment using a random static library I have on my computer (ZeroMQ).
# Cargo.toml
[package]
name = "repro"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "staticlib"]
// build.rs
fn main() {
// rustc didn't seem to add this by default for some reason
println!("cargo:rustc-link-search=/lib");
// other dependencies
println!("cargo:rustc-link-lib=stdc++");
println!("cargo:rustc-link-lib=sodium");
println!("cargo:rustc-link-lib=pgm");
}
Thanks for your help! As I said above everything works fine when building a static library — does the symbol also get exported when you look into librepro.so?
Oh oops, guess that's what happens when you don't read the original post properly
Neither the debug or release versions of my librepro.so contain the zmq_socket symbol so I'd say this is a bug in the way modifiers = "+whole-archive" is implemented.
I did manage to find some zmq symbols though, so maybe the functions are still present but not exported?
$ nm target/debug/librepro.so | grep zmq
0000000000001020 t _GLOBAL__sub_I__ZN3zmq5ctx_tC2Ev
0000000000004010 b _ZN3zmq5ctx_t13max_socket_idE
The wrapper writing could probably be automated using bindgen and syn and kept up to date with a test, but you get the gist. This falls firmly under the "hacky workaround" umbrella, but could work until a proper solution is merged upstream.
Meanwhile, I’ll look into solving this problem outside rust/cargo — but that’s a shame since I’ll have to reinvent quite a bit of cross-building infrastructure for that (we’re targeting about a dozen platforms, including Windows).