Call functions from a relative path lib.so/dll

Hi everyone,

I am trying to call a function from a local vendor shared object.
I created an example to explain it.
So in main.rs I am trying to call a function from vendor/libadd.so.
It works when i copy the libadd.so into the ./target/[debug|release]/deps/ folder and run it with cargo. When I execute the binary from command line I get “./target/debug/rust_call_c: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory”.

I tried a lot of things like “//println!(“cargo:rustc-link-search=dependency=./src/vendor”);” in build.rs.
I think it is only a small thing which i miss, but I do not get it.

Hopefully someone can give me a hint how to fix this.

My code looks like this:

folder structure:
.
|-- scr
| |-- vendor
| | |-- add.c
| | |-- add.h
| | |-- libadd.so
| |-- main.rs
|-- build.rs
|-- Cargo.toml

add.c

int add(int a, int b) {
    return a + b;
}

add.h

int add(int a, int b) {}

complie to libadd.so with: “gcc -shared -o libadd.so -fPIC add.c”

main.rs

use libc::c_int;

// this extern block links to the libadd library
#[link(name = "add")]
extern "C" {
    // this is a foreign function
    fn add(a: c_int, b: c_int) -> c_int;
}

fn main() {
    let r = unsafe { add(1, 4) };
    println!("{}", r);
}

build.rs

fn main() {
    println!("cargo:rustc-flags=-L ./src/vendor");
}

Cargo.toml

[package]
name = "rust_call_c"
version = "0.1.0"
edition = "2018"
build = "build.rs"

[dependencies]
libc = "0.2"

[profile.dev]
rpath = true

[profile.release]
rpath = true

June 15th:
fixed usage of libc

(The following answer is for unix)

This is about the way a binary that depends on a dynamic library looks for it when run. You can see it with $ ldd ./the-binary.

Now, where does the binary look for the dynamic libraries? You can find all the information in the man page of ld.so.

Long story short, dynamic libraries are searched for in directories specified by the -rpath=... flag given to the linker, then in the LD_LIBRARY_PATH environment variable, then in some places specified by some files in /etc/ld_preload..., and then in /lib and /usr/lib. The -L linker parameter is of no use (it’s just a sanity check, quite surprising to be honest).

Since you have specified the -C rpath flag to rustc in the Cargo.toml file, I guess it must be the reason it worked in the deps folder. The reason it works with cargo run is that cargo sets the LD_LIBRARY_PATH to include the deps folder.

But more generally, you need to add the -C link-args=-Wl,-rpath=PATH_TO_LIB_HERE to the rust flags, either with the RUSTFLAGS env var, or within the .cargo/config file:

  • absolute path;

    • RUSTFLAGS="-L src/vendor-C link-args=-Wl,-rpath=$PWD/src/vendor/"
  • relative path (not recommended, since it is relative to the calling site, not to the binary location);

    • RUSTFLAGS="-L src/vendor-C link-args=-Wl,-rpath=./src/vendor/"
  • relative to the binary location with the $ORIGIN meta-variable.

    • RUSTFLAGS="-L src/vendor -C link-args=-Wl,-rpath=\$ORIGIN/../../src/vendor"

So, for instance, you could try to set the rpath to $ORIGIN/ to make it work when the binary and the library are in the same dir.


Your extern declaration is wrong: you should use ::libc::c_int instead of i8 to represent C’s integer type:

use ::libc::c_int;

#[link(name = "add")]
extern "C" {
    fn add (a: c_int, b: c_int) -> c_int;
}
2 Likes

Thank you @Yandros for your detailed description and your great demo. :grinning:
Now I know what to do.