How to call functions from a dylib using Crystal? - ffi

I wish to call the various functions defined in a Rust dylib using Crystal. I've gone through the manual, but I was not able to understand it. How do I properly include and call this dylib? Am I using the CLI flags wrong?
Here's the Crystal code:
#[Link("libmy_dylib")]
lib MyDylib
# In C: double cos(double x)
# In Rust: print_number(x:i32)
fun print_number(value : Int32)
end
MyDylib.print_number(10)
I compiled a dylib using this repo. The code compiles fine to a libmy_dylib.dylib:
extern crate libc;
use libc::uint32_t;
#[no_mangle]
pub extern "C" fn hello() {
println!("Hello from Rust!");
}
#[no_mangle]
pub extern "C" fn print_number(x: i32) {
println!("x is: {}", x);
}
#[no_mangle]
pub extern "C" fn addition(a: uint32_t, b: uint32_t) -> uint32_t {
let c = a + b;
println!("Sum : {}", c);
return a + b;
}
#[allow(dead_code)]
pub extern "C" fn fix_linking_when_not_using_stdlib() {
panic!()
}

You need to specify the dylib with an absolute path, and pass it through ldflags. For instance, the following Rust file
extern crate libc;
#[no_mangle]
pub extern "C" fn hello() {
println!("Hello from Rust!")
}
that compiles to libmy_dylib.dylib can be linked like this:
#[Link(ldflags: "/absolute/path/to/libmy_dylib.dylib")]
lib MyDylib
fun hello : Void
end
MyDylib.hello
And the program will compile to print "Hello from Rust!"

Related

Use C variable from Rust without unsafe

I can expose a C function to Rust code via the FFI as follows:
use std::os::raw::c_int;
mod c {
#[link(name="...")]
extern "C" {
pub fn add(a: c_int, b: c_int) -> c_int;
}
}
pub fn add(a: c_int, b: c_int) -> c_int {
unsafe {
c::add(a, b)
}
}
Now I can call add from Rust without having to wrap it in another unsafe block. But what if I want to do the same for a variable? I.e.:
use std::os::raw::c_int;
mod c {
#[link(name="...")]
extern "C" {
pub static VAR: c_int;
}
}
pub static VAR: c_int = unsafe { c::VAR };
This results in a compiler error: "cannot read from extern static". What is the correct way (if there is one) to do this?
It should be unsafe when it is indeed unsafe, although you can make a static borrow of the imported global variable.
static VAR: &i32 = unsafe { &c::VAR };

How to call a Rust struct's method from C using FFI?

I am trying to call a public function (located inside a Rust struct's impl block) from a C program using the FFI. Calling regular pub fns has not been too much trouble, but I am trying to call a pub fn from inside a struct's impl block, and not finding the right syntax to expose/call it. Surely this is possible, right?
lib.rs
#[repr(C)]
#[derive(Debug)]
pub struct MyStruct {
var: i32,
}
#[no_mangle]
pub extern "C" fn new() -> MyStruct {
MyStruct { var: 99 }
}
#[no_mangle]
impl MyStruct {
#[no_mangle]
pub extern "C" fn print_hellow(&self) {
println!("{}", self.var);
}
}
main.c
typedef struct MyStruct
{
int var;
} MyStruct;
extern MyStruct new (void);
extern void print_hellow(MyStruct);
int main()
{
MyStruct instance1;
MyStruct instance2 = new ();
printf("Instance1 var:%d\n", instance1.var);
/// successfully prints the uninitialized 'var'
printf("Instance2 var:%d\n", instance2.var);
/// successfully prints the initialized 'var'
print_hellow(instance1);
/// fails to link during compilation
return 0;
}
No, this is not possible. You will need to write shim functions for every method you wish to access:
#[no_mangle]
pub unsafe extern "C" fn my_struct_print_hellow(me: *const MyStruct) {
let me = &*me;
me.print_hellow();
}
See also:
Using Rust objects from other languages (disclaimer: I am the primary maintainer)

How do I make a struct for FFI that contains a nullable function pointer?

I have an existing C program that loads shared library plugins. The main C program interacts with those plugins through a C struct containing integers, strings, function pointers, etc. How can I create such a plugin from Rust?
Note that the (real) C program cannot be changed, nor can the API be changed, those are fixed, existing things, so this is not a question about "how best to support plugins in Rust", it's how can Rust make *.so files which interoperate with an existing C program.
Here's a simplified example of a C program + C plugin:
/* gcc -g -Wall test.c -o test -ldl
./test ./test-api.so
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <dlfcn.h>
struct api {
uint64_t i64;
int i;
const char *name; /* can be NULL */
void (*load) (void); /* must not be NULL */
void (*hello) (const char *str); /* can be NULL */
};
int
main (int argc, char *argv[])
{
void *dl = dlopen (argv[1], RTLD_NOW);
if (!dl) { fprintf (stderr, "%s: %s\n", argv[1], dlerror ()); exit (1); }
struct api *(*get_api) (void) = dlsym (dl, "get_api");
printf ("calling get_api ...\n");
struct api *api = get_api ();
printf ("api->i64 = %" PRIi64 "\n", api->i64);
printf ("api->i = %d\n", api->i);
if (api->name)
printf ("api->name = %s\n", api->name);
printf ("calling api->load ...\n");
api->load ();
if (api->hello) {
printf ("calling api->hello ...\n");
api->hello ("world");
}
printf ("exiting\n");
exit (0);
}
/* gcc -g -shared -fPIC -Wall test-api.c -o test-api.so */
#include <stdio.h>
#include <stdint.h>
static void
load (void)
{
printf ("this is the load function in the plugin\n");
}
static void
hello (const char *str)
{
printf ("hello %s\n", str);
}
static struct api {
uint64_t i64;
int i;
const char *name;
void (*load) (void);
void (*hello) (const char *str);
} api = {
1042,
42,
"this is the plugin",
load,
hello,
};
struct api *
get_api (void)
{
return &api;
}
Here's what I wrote in Rust to try to get a plugin, but it doesn't compile:
extern crate libc;
use libc::*;
use std::ffi::*;
use std::ptr;
use std::os::raw::c_int;
#[repr(C)]
pub struct api {
i64: uint64_t,
i: c_int,
name: *const c_char,
load: extern fn (),
hello: extern fn (), // XXX
}
extern fn hello_load () {
println! ("hello this is the load method");
}
#[no_mangle]
pub extern fn get_api () -> *const api {
println! ("hello from the plugin");
let api = Box::new (api {
i64: 4201,
i: 24,
name: CString::new("hello").unwrap().into_raw(), // XXX memory leak?
load: hello_load,
hello: std::ptr::null_mut,
});
return Box::into_raw(api); // XXX memory leak?
}
This is compiled using Cargo.toml containing:
[package]
name = "embed"
version = "0.1.0"
[dependencies]
libc = "0.2"
[lib]
name = "embed"
crate-type = ["cdylib"]
The error is:
error[E0308]: mismatched types
--> src/lib.rs:32:16
|
32 | hello: std::ptr::null_mut,
| ^^^^^^^^^^^^^^^^^^ expected "C" fn, found "Rust" fn
|
= note: expected type `extern "C" fn()`
found type `fn() -> *mut _ {std::ptr::null_mut::<_>}`
error: aborting due to previous error
I didn't get to try loading the module but when I tried this before with the real program the fields were all wrong indicating something much more fundamental was wrong.
tl;dr Use Option to represent nullable function pointers and None for null.
The error message is confusing, first, because std::ptr::null_mut isn't a pointer; it's a generic function that returns a pointer, and you haven't called it. So Rust is seeing you pass a function that has the wrong signature and calling convention, and complaining about that.
But once you fix that, you'll get this error instead:
error[E0308]: mismatched types
--> src/lib.rs:29:16
|
29 | hello: std::ptr::null_mut(),
| ^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found *-ptr
|
= note: expected type `extern "C" fn()`
found type `*mut _`
Function pointers and object pointers are not compatible (this is also the case in C), so you can't cast between them. null_mut returns an object pointer, so you need to find another way to create a null function pointer.
Function pointers (values of type fn(...) -> _) have another interesting property: unlike raw pointers (*const _ and *mut _), they can't be null. You don't need an unsafe block to call a function via pointer, and so creating a null function pointer is unsafe, like creating a null reference.
How do you make something nullable? Wrap it in Option:
#[repr(C)]
pub struct api {
// ...
load: Option<extern fn ()>,
hello: Option<extern fn ()>, // assuming hello can also be null
}
And populate it with Some(function) or None:
let api = Box::new (api {
// ...
load: Some(hello_load),
hello: None,
});
It's not usually a good idea to use enums, including Option, in a repr(C) struct, because C doesn't have an enum equivalent and so you don't know what you're going to get on the other side. But in the case of Option<T> where T is something non-nullable, None is represented by the null value, so it should be okay.
The use of Option to represent a nullable function pointer for FFI is documented in the Unsafe Code Guidelines:
null values are not supported by the Rust function pointer types -- just like references, the expectation is that you use Option to create nullable pointers. Option<fn(Args...) -> Ret> will have the exact same ABI as fn(Args...) -> Ret, but additionally allows null pointer values.

Zig "translate c" doesn't translate main function

I created a C file:
int main() {
return 1;
}
I used Zig's translate-c command line option to generate a zig file, and I only get some global variable declarations like
pub const __GCC_ATOMIC_TEST_AND_SET_TRUEVAL = 1;
pub const __FLT16_MAX_EXP__ = 15;
pub const __BIGGEST_ALIGNMENT__ = 16;
pub const __SIZEOF_FLOAT__ = 4;
pub const __INT64_FMTd__ = c"ld";
pub const __STDC_VERSION__ = c_long(201112);
... // and many
And no main function is found. But if I change the function name to myFunction like this:
int myFunction(int a) {
return a;
}
A function appears when I re-generate it:
pub export fn myFunction(a: c_int) c_int {
return a;
}
Am I missing something? What's the rule of zig's translate-c function?
When this question was asked, translate-c did not yet support functions with unspecified parameters. This was visible by using --verbose-cimport:
test.c:1:5: warning: unsupported type: 'FunctionNoProto'
test.c:1:5: warning: unable to resolve prototype of function 'main'
In C, if you leave the parameters empty, it's not actually zero parameters, it's unspecified. You have to use void to mean "no parameters".
So that's why the second example worked - because the parameter list was not empty.
However as of e280dce3, Zig supports translating C functions with unspecified parameters, and the example from the question turns into this Zig code:
pub export fn main() c_int {
return 1;
}

Why is my integer value changed when passing a heap-allocated struct from Rust to C?

I am passing data from Rust to C. While passing primitives seems to be easy, I am kind of lost with structs.
I have the following Rust code:
use ::std::os::raw::*;
static NAME: &[c_char] = &[65, 66, 67, 0];
#[repr(C)]
pub struct MyStruct {
pub x: c_int,
pub y: *const c_char,
}
#[no_mangle]
pub extern "C" fn get_my_struct() -> *const MyStruct {
let my_struct = MyStruct {
x: 11 as c_int,
y: NAME.as_ptr(),
};
unsafe {
::std::mem::transmute(Box::new(my_struct))
}
}
And the following C code:
typedef struct _my_struct my_struct;
extern const my_struct get_my_struct(void);
struct _my_struct {
int x;
const char *y;
};
int main(void) {
my_struct my_complex_struct = get_my_struct();
return 0;
}
The output from gdb says:
(gdb) p my_complex_struct
$1 = {x = 6295568, y = 0x7ffff7bce1e0 <ref> "ABC"}
The string looks fine but the int is definitely off. What am I missing here? Why is the value of x 6295568 and not 11?
Compiled by:
gcc (Debian 4.9.2-10) 4.9.2
rustc 1.20.0-nightly (8d22af87d 2017-07-22)
cargo 0.21.0-nightly (ffab51954 2017-07-18)
using:
cargo build
gcc --std=c11 -g -o main src/main.c /test/target/debug/libtest.so -L target/debug/
You have an issue because your ABI doesn't match. You are returning a pointer to an allocated structure but your C code claims the function returns a struct directly.
As demonstrated in The Rust FFI Omnibus chapter on objects, you should use Box::into_raw:
#[no_mangle]
pub extern "C" fn get_my_struct() -> *const MyStruct {
let my_struct = MyStruct {
x: 11 as c_int,
y: NAME.as_ptr(),
};
Box::into_raw(Box::new(my_struct))
}
Your C function should be marked as returning a pointer:
extern const my_struct *get_my_struct(void);
// ...
int main(void) {
const my_struct *my_complex_struct = get_my_struct();
// ...
}
(lldb) p *my_complex_struct
(my_struct) $1 = (x = 11, y = "ABC")
The code also has a memory leak; you need to return the pointer back to Rust so it can be properly deallocated.
If you meant to return the struct directly, change your Rust code to not perform an allocation:
#[no_mangle]
pub extern "C" fn get_my_struct() -> MyStruct {
MyStruct {
x: 11 as c_int,
y: NAME.as_ptr(),
}
}
(lldb) p my_complex_struct
(my_struct) $0 = (x = 11, y = "ABC")
Disclaimer: I'm the primary author of the Omnibus.

Resources