Why is my integer value changed when passing a heap-allocated struct from Rust to C? - 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.

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;
}

Using designated initializers with unnamed nested data types

I'm wondering if it is possible to use designated initializers in unnamed data members of structs... (Yikes, a mouthful, but yes, it is the cleanest way to do what I'm trying to do...). If I have:
typedef struct MainStruct {
union {
uint8_t a8[16];
uint64_t a64[2];
};
uint64_t i64;
} MainStruct_t;
typedef struct OtherStruct {
MainStruct_t main;
int otherval;
} OtherStruct_t;
OtherStruct_t instance = { .main.a64 = { 0, 0 }, .otherval = 3 };
and I try to compile, I get the error:
tst3.c:16: error: unknown field ‘a64’ specified in initializer
I've also tried using .main..a64, but I'm getting other issues. This is with gcc 4.4. Unfortunately, the MainStruct is used all over the code, so naming the union would involve changing hundreds of files, so I'd like to avoid that. I'd also like to avoid any assumptions about position of MainStruct within OtherStruct if possible.
You need to change the syntax a bit, initializing .main within the initializer for instance:
typedef struct MainStruct {
union {
uint8_t a8[16];
uint64_t a64[2];
};
uint64_t i64;
} MainStruct_t;
typedef struct OtherStruct {
MainStruct_t main;
int otherval;
} OtherStruct_t;
OtherStruct_t instance = { .main = {.a64 = { 0, 0 }}, .otherval = 3 };
Here is a working test program:
#include <stdio.h>
#include <stdint.h>
typedef struct MainStruct {
union {
uint8_t a8[16];
uint64_t a64[2];
};
uint64_t i64;
} MainStruct_t;
typedef struct OtherStruct {
MainStruct_t main;
int otherval;
} OtherStruct_t;
OtherStruct_t instance = { .main = {.a64 = { 5, 10 }}, .otherval = 3 };
int main(void)
{
printf("%d, %d\n", (int) instance.main.a64[0], (int) instance.main.a64[1]);
printf("%d\n", instance.otherval);
}
Compiled with gcc -std=c11 -Wall -Wextra -Wpedantic, here is the program output:
5, 10
3
Update
This use of designated initializers should also work with at least C99, though C99 does not support unnamed structures or unions. Here is an example:
#include <stdio.h>
struct Inner {
int x;
int arr[2];
};
struct Outer {
char id[100];
struct Inner state;
};
int main(void)
{
struct Outer instance = { .id = "first",
.state = {.x = 5, .arr[0] = 1, .arr[1] = 2 }};
printf("instance id: %s\n", instance.id);
printf("instance state.x = %d\n", instance.state.x);
printf("instance state.arr[0] = %d\n", instance.state.arr[0]);
printf("instance state.arr[1] = %d\n", instance.state.arr[1]);
return 0;
}
Compiled with gcc -std=c99 -Wall -Wextra -Wpedantic, here is the program output:
instance id: first
instance state.x = 5
instance state.arr[0] = 1
instance state.arr[1] = 2
Final Note
It turns out that OP's original syntax of:
OtherStruct_t instance = { .main.a64 = { 0, 0 }, .otherval = 3 };
should also work on both C99 and C11, but is not supported in older standards which do not allow initialization of subobjects.
Unnamed unions are not supported in C99, but are available as a GNU extension. Further investigation has turned up this bug report which suggests that designated initializers for unnamed structs and unions were fixed in gcc 4.6. As workaround, it was suggested at this link to enclose the offending initializer in braces; it is also mentioned that this workaround is a bit finicky and position dependent, which may explain why it does not work here for OP.

Resources