cxx icon indicating copy to clipboard operation
cxx copied to clipboard

how to use cxx with just .so lib and .h file

Open Tionofzl opened this issue 1 year ago • 13 comments

I have a simple worked cxx project example with a .cc file and .h file, now I want to replace the .cc file to a .so lib, because that is the real scenario I meet. In my opinion, I just need to revise the build.rs. What should I do?

build.rs

cxx_build::bridge("src/main.rs") .file("src/calculate.cc") // I want replace this .cc file to .so lib, like: .file("src/calculate.so") .include("./lib") .flag_if_supported("-std=c++14") .compile("cxx-demo");

Tionofzl avatar Mar 09 '23 08:03 Tionofzl

I'd say, just leave the file call out and link -lcalculate to your final project link, e.g., by printing a cargo link lib instruction from this build.rs; see https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib.

schreter avatar Mar 13 '23 21:03 schreter

I'd say, just leave the file call out and link -lcalculate to your final project link, e.g., by printing a cargo link lib instruction from this build.rs; see https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib.

I think it doesn't solve my problem. println!(r"cargo:rustc-link-search=./lib"); println!("cargo:rustc-link-lib=calculate");

If I use bindgen to translate C to Rust, this cargo code is really helpful, because the code files do not have complex relationship. While when I use cxx , the code files like main.rs build.rs calculate.cc calculate.h main.rs.cc main.rs.h , they have complex relationship. Now I want replace calculate.cc to its calculate.so, I still don't know how it work if I just add a cargo:rustc-link-lib=. Could you please give me a specific example?

Tionofzl avatar Mar 14 '23 01:03 Tionofzl

I think it doesn't solve my problem. println!(r"cargo:rustc-link-search=./lib"); println!("cargo:rustc-link-lib=calculate");

But this does look like it should solve your problem.

The header calculate.h will be used to satisfy symbol lookup on the C++ side for the generated main.rs.*. When doing the final link, the linker will link libcalculate.so, so it will satisfy link-time dependencies. It should just work.

What errors are you facing, if you do it this way?

Could you please give me a specific example?

I didn't have such a use case yet, so, unfortunately, no.

schreter avatar Mar 14 '23 11:03 schreter

After adding the code above, now the build.rs is like `println!(r"cargo:rustc-link-search=./lib"); println!("cargo:rustc-link-lib=calculate");

cxx_build::bridge("src/main.rs") .file("src/calculate.cc") // I want replace this .cc file to .so lib, like: .file("src/calculate.so") .compile("cxx-demo");`

cxx still need .file("src/calculate.cc"), but I don't have .cc file. if I drop this line, compilation will report errors. If I just replace .file("src/calculate.cc") to .file("src/calculate.so"), it doesn't work. So I want to know what should I do with cxx.

Tionofzl avatar Mar 15 '23 01:03 Tionofzl

compilation will report errors

WHAT errors? Without context it's impossible to help you.

cxx still need .file("src/calculate.cc")

In which sense "need"? You mean it won't compile without adding file directive? If so, maybe use empty file? But it should work without.

BTW, what's the name of your lib and what is your OS? For example, on Linux and most other OSes calculate.so is not a valid lib name for standard tools, it would expect libcalculate.so.

Again, I don't have this use case and didn't try it myself. We are in fact doing quite the opposite - exposing our Rust lib to C++ via a .so (which has its own challenges).

schreter avatar Mar 15 '23 07:03 schreter

In which sense "need"? You mean it won't compile without adding file directive? If so, maybe use empty file? But it should work without.

Yes, it doesn't work without file directive. Neither the empty file. This is the compilation error: image

In my opinion, cxx doesn't provide a call the function of. so file or. a file. All the examples from the https://cxx.rs/ show that we need a cxx_build in our build.rs, and we can use #[cxx::bridge] mod ffi {} to link a .cc file which is used in cxx_build::bridge().file() in build.rs. While cxx_build is based on cc, which is just "a builder for compilation of a native library". (See https://docs.rs/cc/latest/cc/struct.Build.html)

And cxx_build::bridge().file().compile() will generate a .a file in target folder, which could linked #[cxx::bridge] mod ffi {} in main.rs. So We can call .cc file in Rust. That is what I think cxx works, but I don't know the underlying logic of their connection.

image

Tionofzl avatar Mar 15 '23 14:03 Tionofzl

I have read all the author's answer about how to use static or shared C++ lib in cxx, he said it will work because cxx just append on .h file, while he didn't give more details or specific examples.

Tionofzl avatar Mar 15 '23 14:03 Tionofzl

In my opinion, cxx doesn't provide a call the function of. so file or. a file.

Well, I just implemented a small test, and it works just fine.

I created a shared library containing one C++ symbol returning Rust String type (via string type defined in rust/cxx.h; in fact, I used cxx bridge to create it within cargo as cdylib - THAT has some issues which won't work with the stock Rust linker due to issues with linker script, but that's another story).

C++ code:

#include "rust/cxx.h"

namespace rte
{
    namespace cxx
    {
        namespace test_lib
        {
            rust::String hello_from_cxx() {
                return rust::String("Hello!");
            }
        }
    }
}

Output from nm on the linked file:

$ nm -C libcxx_library.dylib | grep -i hello
0000000000003488 T rte::cxx::test_lib::hello_from_cxx()

Then, I created a crate using this library via cxx.

Cargo.toml:

[package]
name = "cxx_library_use"
version = "0.1.0"
edition = "2021"

[dependencies]
cxx = { workspace = true }

[build-dependencies]
cxx-build = { workspace = true }

build.rs:

fn main() {
    cxx_build::bridge("src/lib.rs")
        .include("path/to/header")
        .flag_if_supported("-std=c++17")
        .compile("rte-cxx-use-lib");

    println!("cargo:rerun-if-changed=src/lib.rs");
    println!("cargo:rustc-link-lib=cxx_library");
}

and finally, src/lib.rs:

#[cfg(test)]
mod tests {
    #[cxx::bridge(namespace = "rte::cxx::test_lib")]
    pub(crate) mod ffi {
        unsafe extern "C++" {
            include!("path/to/header/cxx_library.hpp");

            pub fn hello_from_cxx() -> String;
        }
    }

    #[test]
    fn check() {
        assert_eq!(ffi::hello_from_cxx(), "Hello!");
    }
}

Running this test produces:

cargo test --package cxx_library_use --lib -- tests::check --exact --nocapture 
[...]
running 1 test
test tests::check ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Examining the test executable shows, that it indeed depends on the shared library:

$ otool -L target/debug/deps/cxx_library_use-1750aadfc35f7d0e
target/debug/deps/cxx_library_use-1750aadfc35f7d0e:
        <path>/libcxx_library.dylib (compatibility version 0.0.0, current version 0.0.0)
        /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1300.36.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)

And looking at symbols, it has an undefined symbol satisfied by this library:

nm -C target/debug/deps/cxx_library_use-1750aadfc35f7d0e | grep hello
00000001000039c4 t cxx_library_use::tests::ffi::hello_from_cxx::h1919e2bcda45da9d
                 U rte::cxx::test_lib::hello_from_cxx()
0000000100003d6c T _rte$cxx$test_lib$cxxbridge1$hello_from_cxx

It works.

QED

I tested this on MacOS, which is quite picky. Linux should work exactly the same.

schreter avatar Mar 16 '23 18:03 schreter

Linux should work exactly the same.

BTW, I added the test to our project's unit tests and pushed it to our CI for verification. Yes, it works on Linux w/o any issues as well.

schreter avatar Mar 16 '23 23:03 schreter

In my opinion, cxx doesn't provide a call the function of. so file or. a file.

Well, I just implemented a small test, and it works just fine.

I created a shared library containing one C++ symbol returning Rust String type (via string type defined in rust/cxx.h; in fact, I used cxx bridge to create it within cargo as cdylib - THAT has some issues which won't work with the stock Rust linker due to issues with linker script, but that's another story).

C++ code:

#include "rust/cxx.h"

namespace rte
{
    namespace cxx
    {
        namespace test_lib
        {
            rust::String hello_from_cxx() {
                return rust::String("Hello!");
            }
        }
    }
}

Output from nm on the linked file:

$ nm -C libcxx_library.dylib | grep -i hello
0000000000003488 T rte::cxx::test_lib::hello_from_cxx()

Then, I created a crate using this library via cxx.

Cargo.toml:

[package]
name = "cxx_library_use"
version = "0.1.0"
edition = "2021"

[dependencies]
cxx = { workspace = true }

[build-dependencies]
cxx-build = { workspace = true }

build.rs:

fn main() {
    cxx_build::bridge("src/lib.rs")
        .include("path/to/header")
        .flag_if_supported("-std=c++17")
        .compile("rte-cxx-use-lib");

    println!("cargo:rerun-if-changed=src/lib.rs");
    println!("cargo:rustc-link-lib=cxx_library");
}

and finally, src/lib.rs:

#[cfg(test)]
mod tests {
    #[cxx::bridge(namespace = "rte::cxx::test_lib")]
    pub(crate) mod ffi {
        unsafe extern "C++" {
            include!("path/to/header/cxx_library.hpp");

            pub fn hello_from_cxx() -> String;
        }
    }

    #[test]
    fn check() {
        assert_eq!(ffi::hello_from_cxx(), "Hello!");
    }
}

Running this test produces:

cargo test --package cxx_library_use --lib -- tests::check --exact --nocapture 
[...]
running 1 test
test tests::check ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Examining the test executable shows, that it indeed depends on the shared library:

$ otool -L target/debug/deps/cxx_library_use-1750aadfc35f7d0e
target/debug/deps/cxx_library_use-1750aadfc35f7d0e:
        <path>/libcxx_library.dylib (compatibility version 0.0.0, current version 0.0.0)
        /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1300.36.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)

And looking at symbols, it has an undefined symbol satisfied by this library:

nm -C target/debug/deps/cxx_library_use-1750aadfc35f7d0e | grep hello
00000001000039c4 t cxx_library_use::tests::ffi::hello_from_cxx::h1919e2bcda45da9d
                 U rte::cxx::test_lib::hello_from_cxx()
0000000100003d6c T _rte$cxx$test_lib$cxxbridge1$hello_from_cxx

It works.

QED

I tested this on MacOS, which is quite picky. Linux should work exactly the same.

@schreter Thanks for the post. Is it possible you share a runnable cargo project with the above code? It would be nice if I can clone the runnable project and learn a bit more about what you say in the post. I couldn't get it running because of the placeholders in the code. Also it is not clear to me how to create libcxx_library.dylib.

ssh352 avatar Oct 23 '23 01:10 ssh352

Is it possible you share a runnable cargo project with the above code?

@ssh352 Well, the above is the runnable project, it only consists of the three files I posted above (plus the one C++ file, which you can compile with g++ -o<libname> -shared <cppfile> or so, for instance). I didn't put it into GitHub, it was only created locally.

schreter avatar Oct 23 '23 13:10 schreter

@schreter Thanks I've figured it out.

ssh352 avatar Oct 24 '23 03:10 ssh352

I have read all the author's answer about how to use static or shared C++ lib in cxx, he said it will work because cxx just append on .h file, while he didn't give more details or specific examples.

Hello, @Tionofzl. Can you tell me how these authors say to answer you, and if you can give me the link to the website that would be even better, I've also been trying to use C++ to call a .so file recently.

coalooball avatar Jan 23 '24 02:01 coalooball