Rust ffi include dynamic library in cross platform fashion - rust

I would like to include a dynamic C library in Rust with FFI.
The library is actually also build with Rust, but exposes a C interface, so that it can be used from other languages, too. When I build the library (type: cdylib) with cargo I get a .dylib on MacOS and a .dll as well as a .dll.lib file on windows. These libraries also get different names, derived from the project name (libmy_lib.dylib on MacOS and my_lib.dll as well as my_lib.dll.lib on Windows).
I would like to reference these files in a cross-platform way. Because currently I have to use
#[link(name = "my_lib.dll", kind = "dylib")]
on windows, whereas on MacOS I need to use
#[link(name = "my_lib", kind = "dylib")]
I have already tried to rename the my_lib.dll.lib to my_lib.lib, but I still get a Linker Error, saying
LINK : fatal error LNK1181: cannot open input file 'my_lib.lib'
How can I reference the files, so that I can use my code for Mac and Windows? If thats only possible with cfg_attr tags I would also accept that. Ideally I would also like to get rid of the .lib file for windows if possible.

You could use crate libloading
Example:
let lib = unsafe {
#[cfg(unix)]
let path = "mylib.so";
#[cfg(windows)]
let path = "mylib.dll";
libloading::Library::new(path).expect("Failed to load library")
};
let func: libloading::Symbol<unsafe extern fn() -> u32> = unsafe {
lib.get(b"my_func").expect("Failed to load function `my_func`")
};
// Can call func later while `lib` in scope

For what it's worth, I found a temporary solution for this now.
I used this pattern:
#[cfg(windows)]
#[link(name = "my_lib.dll", kind = "dylib")]
extern {
// Reference the exported functions
}
#[cfg(unix)]
#[link(name = "my_lib", kind = "dylib")]
extern {
// Reference the exported functions
}
I don't like it that much, because I had to define the very same extern{} block twice, but it works and I could also extend this pattern to for example use #[cfg(target_os = "macos")] if needed...
EDIT: Thanks to #Angelicos Phosphoros I improved the code a bit by using a macro like so:
/// Import native functions with the Rust FFI
macro_rules! import_native_functions {
() => {
// Reference the exported functions
};
}
#[cfg(windows)]
#[link(name = "my_lib.dll", kind = "dylib")]
extern {
import_native_functions!();
}
#[cfg(unix)]
#[link(name = "my_lib", kind = "dylib")]
extern {
import_native_functions!();
}

Related

Calling a library function with rust-abi

I have a couple of crates X and Y. The first ones manages low-level stuff and defines an entry point. Then it should transfer control to the Y, which do high-level logic. I would like to explicitly declare Y's function in X and call it (so the linker connects these while building Y-crate).
I know Rust has non-stable ABI, but I use both of these crates in one workspace with the same compiler.
I tried with the following example, but the linker cannot find real_main (undefined reference ...):
// X's main.rs
#[link(name = "y_lib_name", link = "static")]
extern "Rust" {
fn real_main();
}
fn main() {
// is it possible to avoid unsafe here, btw?
unsafe { real_main() }
}
// Y's lib.rs
pub fn real_main() { .. }
Though the symbol exists:
$ nm target/debug/deps/liby_lib_name-8ade2b95fe4044c6.rlib | rg real_main
nm: lib.rmeta: file format not recognized
0000000000000000 T _ZN8y_lib_name9real_main17h8664760a5d2676beE
I guess my issue can be resolved via macro in X like the following:
// in X
#[macro_export]
macro_rules define_ep! {
($fn:expr) => {{ fn main() { $fn(); } }}
}
// in Y
fn f() {}
X::define_ep!(f);
but I would prefer the linker-backed way because:
if you forgot linker just give you an error;
(not sure) linker gives an error if symbols appears more than once.

How to call C function in Rust

I'm a C programmer and I'm trying to call Rust function in my application and the rust function also need call C functions which exist at my application.
I know that if I want call C function in Rust I have to do like this
#[link(name = "mylib")]
extern "C" {
pub fn c_function();
}
But the c_function doesn't exist in any lib but only in my application env now.
For example:
My C code is
void c_function()
{
return 1;
}
void main()
{
rust_function();
}
My Rust code is(cargo new --lib myrustlib)
pub unsafe extern "C" fn rust_function() {
//If I want to call c_function which is in C world here, How could I do this?
//I have tried using extern "C" {pub fn c_function();} but faild.
//And an error is outputted like this "undefined reference to `c_function'"
}
You're on the right track. You can auto-generate C header from the Rust program with cbindgen, and the other way, Rust bindings with bindgen.
Add crate-type = ["lib", "staticlib", "cdylib"] to Cargo.toml to generate .a and .so/.dylib/.dll versions of the Rust library that you can link with the C program.

What are the different ways of specifying the linking path to FFI libraries in Rust?

Using the below code as an example:
extern crate libc;
#[link(name = "adder")]
extern {
fn double_input(input: libc::c_int) -> libc::c_int;
}
fn main() {
let input = 4;
let output = unsafe { double_input(input) };
println!("{} * 2 = {}", input, output);
}
Should #[link(name = "adder")] include a relative path to the .o / a / .h files? For example, should it be #[link(name = "../adderlib/adder")]? Is there another way to tell the compiler where adder is?
The answer to the first question is YES! If your lib file is libfoo.o, #[link(name = "foo") is enough in your code. There are more details in the official documentation.
It will be relative to the lib file which is located in the current work path and the system lib path. (I cannot find this in any documentation, but I once made it successfully). You can specify a path using rustc -l XX -L XX. Using Cargo with a build script is a better way.
If you need to control how a library is found or linked to your Rust code, you should do so via a build script.

Importing a crate into a const block

I'm trying to write a procedural macro that implements a #[derive()] trait for a struct. In the generated implementation code I need to use AnyMap.
To avoid multi crate imports, and based on what I read in some other crate's code (namely Serde) I put my generated code into a const _IMPL_xxx_FOR_xxx : () = { /* generated code */ }; block but it fails to compile.
I was able to replicate my issue with the following code
const BLOCK_1: () = {
extern crate anymap;
use anymap::AnyMap;
};
const BLOCK_2: () = {
extern crate anymap;
use anymap::AnyMap;
};
fn main() {
println!("foo");
}
The compile error I'm getting is the following:
error[E0432]: unresolved import `anymap::AnyMap`
--> src/main.rs:3:9
|
3 | use anymap::AnyMap;
| ^^^^^^^^^^^^^^ Maybe a missing `extern crate anymap;`?
error[E0432]: unresolved import `anymap::AnyMap`
--> src/main.rs:9:9
|
9 | use anymap::AnyMap;
| ^^^^^^^^^^^^^^ Maybe a missing `extern crate anymap;`?
Is it an issue specific to AnyMap? Would you know of any way to fix this (including maybe a different approach to generating procedural macro code if the pattern I'm using is not recommended?
I can replicate this on the playground using simply
const A: () = {
extern crate core;
use core::option::Option;
};
fn main() {}
However, it appears only the use statement is broken, and I can still use items from core, but I have to name them explicitly each time:
const A: () = {
extern crate core;
do_stuff!(core::option::Option)
};
The reason is that use statements typically assume a path relative to the root, and there is no way to explicitly name the block you are in (self refers to the current module unfortunately).
Here's a better workaround - as I said before, Rust uses self to refer to the current module, so you can just put a module in your code block and then have the use statements reference self.
In your case it would be:
const BLOCK_1: () = {
mod inner {
extern crate anymap;
use self::anymap::AnyMap;
}
};
On the specific issue of making this work for procedural macro, a suggested solution was to reexport the needed crate as part of the crate containing the derive macro (or the one containing the class I'm trying to export) using something like pub extern crate anymap and then use <my_crate>::anymap::AnyMap;

How do I specify the linker path in Rust?

I'm trying to link a Rust program with libsoundio. I'm using Windows and there's a GCC binary download available. I can link it like this if I put it in the same folder as my project:
#[link(name = ":libsoundio-1.1.0/i686/libsoundio.a")]
#[link(name = "ole32")]
extern {
fn soundio_version_string() -> *const c_char;
}
But I really want to specify #[link(name = "libsoundio")] or even #[link(name = "soundio")], and then provide a linker path somewhere else.
Where can I specify that path?
I tried the rustc-link-search suggestion as follows:
#[link(name = "libsoundio")]
#[link(name = "ole32")]
extern {
fn soundio_version_string() -> *const c_char;
}
And in .cargo/config:
[target.i686-pc-windows-gnu.libsoundio]
rustc-link-search = ["libsoundio-1.1.0/i686"]
rustc-link-lib = ["libsoundio.a"]
[target.x86_64-pc-windows-gnu.libsoundio]
rustc-link-search = ["libsoundio-1.1.0/x86_64"]
rustc-link-lib = ["libsoundio.a"]
But it still only passes "-l" "libsoundio" to gcc and fails with the same ld: cannot find -llibsoundio. Am I missing something really obvious? The docs seem to suggest this should work.
As stated in the documentation for a build script:
All the lines printed to stdout by a build script [... starting] with cargo: is interpreted directly by Cargo [...] rustc-link-search indicates the specified value should be passed to the compiler as a -L flag.
In your Cargo.toml:
[package]
name = "link-example"
version = "0.1.0"
authors = ["An Devloper <an.devloper#example.com>"]
build = "build.rs"
And your build.rs:
fn main() {
println!(r"cargo:rustc-link-search=C:\Rust\linka\libsoundio-1.1.0\i686");
}
Note that your build script can use all the power of Rust and can output different values depending on target platform (e.g. 32- and 64-bit).
Finally, your code:
extern crate libc;
use libc::c_char;
use std::ffi::CStr;
#[link(name = "soundio")]
extern {
fn soundio_version_string() -> *const c_char;
}
fn main() {
let v = unsafe { CStr::from_ptr(soundio_version_string()) };
println!("{:?}", v);
}
The proof is in the pudding:
$ cargo run
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target\debug\linka.exe`
"1.0.3"
Ideally, you will create a soundio-sys package, using the convention for *-sys packages. That simply has a build script that links to the appropriate libraries and exposes the C methods. It will use the Cargo links key to uniquely identify the native library and prevent linking to it multiple times. Other libraries can then include this new crate and not worry about those linking details.
Another possible way is setting the RUSTFLAGS like:
RUSTFLAGS='-L my/lib/location' cargo build # or cargo run
I don't know if this is the most organized and recommended approach, but it worked for my simple project.
I found something that works OK: you can specify links in your Cargo.toml:
[package]
links = "libsoundio"
build = "build.rs"
This specifies that the project links to libsoundio. Now you can specify the search path and library name in the .cargo/config file:
[target.i686-pc-windows-gnu.libsoundio]
rustc-link-search = ["libsoundio-1.1.0/i686"]
rustc-link-lib = [":libsoundio.a"]
[target.x86_64-pc-windows-gnu.libsoundio]
rustc-link-search = ["libsoundio-1.1.0/x86_64"]
rustc-link-lib = [":libsoundio.a"]
(The : prefix tells GCC to use the actual filename and not to do all its idiotic lib-prepending and extension magic.)
You also need to create an empty build.rs:
fn main() {}
This file is never run, because the values in .cargo/config override its output, but for some reason Cargo still requires it - any time you use links = you have to have build =, even if it isn't used.
Finally in main.rs:
#[link(name = "libsoundio")]
#[link(name = "ole32")]
extern {
fn soundio_version_string() -> *const c_char;
}

Resources