#Rust/C FFI with bindgen, how to create minimal working example

10 messages · Page 1 of 1 (latest)

wary gate
#

I've tagged this as "beginner" because, alright I'm experienced in Rust, I'm very inexperienced with C; I know the syntax and I know a lot of concepts about it, but I've never used it properly. I would like to use Rust/C FFI with bindgen, but it's not quite working. Here's a minimal example:
build.rs

use std::env;
use std::path::PathBuf;

fn main() {
    println!("cargo:rustc-link-search=./");

    let bindings = bindgen::Builder::default()
        .header("c_code.h")
        
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .generate()
        .expect("Unable to generate bindings");

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}

src/main.rs

use std::ffi::CStr;

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

fn main() {
    unsafe {   
        let pointer = test_string();
        let c_str = CStr::from_ptr(pointer);
        println!("{}", c_str.to_str().unwrap());
        free_test_string(pointer);
    }
}

c_code.h

void write_to_string(char* pointer);

void free_test_string(char* pointer);

char* test_string();

c_code.c

#include <stdlib.h>

void write_to_string(char* pointer) {
    pointer = "tset";
}

void free_test_string(char* pointer) {
    free(pointer);
}

char* test_string() {
    char* test_string = malloc(sizeof(char) * 5);
    test_string = "test";
    write_to_string(test_string);
    return test_string;
}

And when I try to run this, it fails:

$ gcc -shared -o libc_code.so -fPIC c_code.c
> [Success, no output]
$ cargo build
>    Compiling c-ffi-testing v0.1.0 (/home/cha14031/playground/c-ffi-testing)
error: linking with `cc` failed: exit status: 1
  |
  = note: LC_ALL="C" PATH="/home [LONG OUTPUT OMITTED]

  = note: /usr/bin/ld: /home/cha14031/playground/c-ffi-testing/target/debug/deps/c_ffi_testing-ac44afdba4a8831e.294zjevdd0plx23d.rcgu.o: in function `c_ffi_testing::main':
          /home/cha14031/playground/c-ffi-testing/src/main.rs:7: undefined reference to `test_string'
          /usr/bin/ld: /home/cha14031/playground/c-ffi-testing/src/main.rs:10: undefined reference to `free_test_string'
          collect2: error: ld returned 1 exit status

  = note: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified
  = note: use the `-l` flag to specify native libraries to link
  = note: use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib)

error: could not compile `c-ffi-testing` (bin "c-ffi-testing") due to 1 previous error

All the docs and guides for bindgen don't include compilation instructions for the C-side, which is really frustrating. I think I'm having issues with the C packaging system, I tried putting the extern keyword in front of the functions I used in Rust, but that didn't help. Thank you in advance for any help!

#

Even when changing the functions to have extern on it, it doesn't make a difference:
c_code.h

void write_to_string(char* pointer);

extern void free_test_string(char* pointer);

extern char* test_string();

Terminal

$ gcc -shared -o libc_code.so -fPIC c_code.c
> [Success, no output]
$ cargo build
>    Compiling c-ffi-testing v0.1.0 (/home/cha14031/playground/c-ffi-testing)
error: linking with `cc` failed: exit status: 1
  |
  = note: LC_ALL="C" PATH="/home [LONG OUTPUT OMITTED]

  = note: /usr/bin/ld: /home/cha14031/playground/c-ffi-testing/target/debug/deps/c_ffi_testing-ac44afdba4a8831e.294zjevdd0plx23d.rcgu.o: in function `c_ffi_testing::main':
          /home/cha14031/playground/c-ffi-testing/src/main.rs:7: undefined reference to `test_string'
          /usr/bin/ld: /home/cha14031/playground/c-ffi-testing/src/main.rs:10: undefined reference to `free_test_string'
          collect2: error: ld returned 1 exit status

  = note: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified
  = note: use the `-l` flag to specify native libraries to link
  = note: use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib)

error: could not compile `c-ffi-testing` (bin "c-ffi-testing") due to 1 previous error

[output is identical]

opal totem
#

uh

#

Maybe try setting the path for searching the libraries to absolute

#

you can also directly specify the libraries to link against, as the output suggests

#

cargo:rustc-link-lib

#

cargo::rustc-link-lib=c_code

#

(note there's no lib prefix)

#

and the search path to the absolute (you can use canonicalize to make it absolute out of the relative one)

wary gate
#

So I managed to fix it after some tinkering. List of changes I made:

  • Added println!("cargo:rustc-link-lib=c_code"); after line 5 println!("cargo:rustc-link-search=./");, it turns out that cargo doesn't search for libraries in the specified path, you have to specify the names of the .so files.
  • Added visibility attributes (see https://gcc.gnu.org/wiki/Visibility ), see new c_code.h below, and run gcc with -fvisibility=hidden:
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
#define DLL_LOCAL  __attribute__ ((visibility ("hidden")))

DLL_LOCAL void write_to_string(char* pointer);

DLL_PUBLIC void free_test_string(char* pointer);

DLL_PUBLIC char* test_string();
  • Add LD_LIBRARY_PATH=. as an environment variable when running cargo run. On Windows this isn't necessary because it looks for .dll files in the directory where the .exe file is, but Linux doesn't do this. I think there's a way to hardcode this so you don't have to pass it every time, but I don't know how, would appreciate someone telling me!