Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unsure how to use cxx #255

Closed
iulianR opened this issue Aug 20, 2020 · 7 comments
Closed

Unsure how to use cxx #255

iulianR opened this issue Aug 20, 2020 · 7 comments
Labels

Comments

@iulianR
Copy link
Contributor

iulianR commented Aug 20, 2020

Hello! I am not sure what the steps are for creating a binding to a shared library in C++ (seems I am not alone in this #179 (comment)).

From my understanding, if I have the .so and its header:

#pragma once
#include <string>
namespace foo::set {
    int init();
    int setKey(const std::string& key, const std::string& value);
}

I can create a new foo-sys lib crate which contains something like this in lib.rs:

#[cxx::bridge(namespace = foo::set)]
pub mod ffi {
    extern "C" {
        include!("../../../libs/foo/include_cpp/set.h");

        fn init() -> i32;
        fn setKey(key: &str, value: &str) -> i32;
    }

    extern "Rust" {
    }
}

Then I write a build.rs that retrieves to .so that needs to be linked:

use std::path::PathBuf;
use std::{env, fs};

fn main() {
    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let lib_path = PathBuf::from(manifest_dir);

    let lib_path =
        lib_path.join("../../../../build_dir/debug/lib");
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    fs::copy(
        lib_path.join("libfoo.so"),
        out_path.join("libfoo.so"),
    )
    .unwrap();

    println!("cargo:rustc-link-search={}", out_path.to_str().unwrap());
    println!("cargo:rustc-link-lib=foo");

}

And the I add this foo-sys crate as my dependency and call the functions as:

foo::ffi::init();

I get the following error, but I don't think it's that relevant because this seems more of a problem with me not being able to understand how to properly use the crate.

  = note: ld.lld: error: undefined symbol: foo$set$cxxbridge03$init

I fail to understand the example as I don't have any Rust code that I want to call from C++ and I don't really want to compile (I also don't think I can as I don't have sources) the library in build.rs. Can you please steer me in the right direction or tell me if got this completely backwards? Thanks!

@dtolnay
Copy link
Owner

dtolnay commented Aug 23, 2020

Looks like you are not running cxx's C++ code generator at all in your build script. foo$set$cxxbridge03$init would be defined in the generated C++ code.

Please check out https://github.com/dtolnay/cxx/tree/0.3.4#cargo-based-setup.

@dtolnay
Copy link
Owner

dtolnay commented Aug 23, 2020

In particular, the:

cxx/README.md

Lines 230 to 233 in 85487b0

cxx_build::bridge("src/main.rs") // returns a cc::Build
.file("../demo-cxx/demo.cc")
.flag_if_supported("-std=c++11")
.compile("cxxbridge-demo");

is specifically what generates and links your missing symbol. Your build.rs doesn't have anything like that.

@dtolnay
Copy link
Owner

dtolnay commented Aug 27, 2020

I'll close this for now, but please let me know if something still doesn't work after updating the build script!

@dtolnay dtolnay closed this as completed Aug 27, 2020
@iulianR
Copy link
Contributor Author

iulianR commented Aug 31, 2020

Sorry for the delay, I posted this just before leaving the office for a vacation. I think I'm closer to understanding as I managed to generate bindings. The only thing that is not clear right now is if it's possible to call my setKey C++ API in a way that doesn't require me to change the C++ signature.

The bridge function signature currently is fn setKey(key: &CxxString, value: &CxxString) -> i32, which matches the C++ signature int setKey(const std::string& key, const std::string& value). But as I understand, it's not possible to call this in any way currently. If I want to call it, I'd have to change my C++ signature to int setKey(const rust::String& key, const rust::String& value) and the bridge signature to fn setKey(key: &String, value: &String) -> i32. Do I understand that correctly? If that's the case, can I even do something if I don't have access to the library's sources? I only have the headers and the .so.

Thanks!

@dtolnay
Copy link
Owner

dtolnay commented Aug 31, 2020

can I even do something if I don't have access to the library's sources? I only have the headers and the .so.

This should not be any obstacle because you can insert as many extra include! as you need, containing your own glue code.

Two possible approaches would be:

  • Put in a function that lets you obtain a CxxString from Rust. Call from Rust to obtain strings to pass to setKey.

    // fn make_std_string(s: &str) -> UniquePtr<CxxString>;
    
    std::unique_ptr<std::string> make_std_string(rust::Str s) {
      return std::make_unique<std::string>(s);
    }
  • Put in a function which you bind instead of setKey, and does the std::string allocation internally.

    // fn setKey(key: &str, value: &str) -> i32;
    
    int setKey(rust::Str key, rust::Str value) {
      return ::actual::setKey(std::string(key), std::string(value));
    }

@iulianR
Copy link
Contributor Author

iulianR commented Sep 1, 2020

Thanks for your help, I understand now.

@dtolnay
Copy link
Owner

dtolnay commented Dec 13, 2020

Since cxx 1.0 there is a builtin way to obtain and manipulate std::string from Rust. https://docs.rs/cxx/1.0/cxx/macro.let_cxx_string.html

use cxx::let_cxx_string;

#[cxx::bridge]
mod ffi {
    unsafe extern "C" {
        include!("foo/include_cpp/set.h");

        fn setKey(key: &CxxString, value: &CxxString) -> i32;
    }
}

fn main() {
    let_cxx_string(key = "key...");
    let_cxx_string(value = "value...");
    let i = ffi::setKey(&key, &value);
    println!("i={}", i);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants