使用类型[my_struct]是将C结构数组传递给Rust函数的正确方法吗?

时间:2018-10-29 16:48:26

标签: c struct rust ffi

C文件:

typedef struct point {
    int x;
    int y;
} point;

typedef struct points {
    int count;
    point *array_of_points;
} points;

锈文件:

#[derive(Debug)]
#[repr(C)]
pub struct point {
    x: c_int,
    y: c_int,
}

#[derive(Debug)]
#[repr(C)]
pub struct points {
    count: c_int,
    array_of_points: [point],
}

#[no_mangle]
pub fn do_something(all_points: &points) {
    for i in 0..all_points.count {
        let crr_point = &all_points.array_of_points[i as usize];
        println!("{:?}", crr_point);
    }
}

在我的C文件中,我分配了很多结构点并将其添加到array_of_points,然后调用do_something函数。

如何在Rust中获得array_of_points中的每个点?

我在Rust中定义array_of_points数组的方式正确吗?

运行它时,会出现以下奇怪结果:

point { x: 0, y: -952095696 }   
point { x: 32674, y: 101 }   

以此类推。

2 个答案:

答案 0 :(得分:4)

这是不确定的行为。在该类型的Rust版本中,类型为array_of_points的成员point*被转换为Rust大小未定的切片{{1} },这既不等效也不兼容。通过添加类型为[point]的成员,您建议[point]在其第一个成员point之后紧随其后的point个对象具有可变数量。这也使count成为未调整大小的类型(或动态调整大小的类型)。

C中points的内存布局应为:

points

但是Rust的定义是这样的:

[ int, point* ]
           |
            -->[ point, point, ...] // dynamically allocated

[ int, point, point, ... ] // unknown compile time size 中的成员需要使用原始指针进行定义:

points

然后#[derive(Debug)] #[repr(C)] pub struct points { count: c_int, array_of_points: *mut point, } 应该通过偏移量取消指针的引用以获取每个点:

do_something

或从#[no_mangle] pub fn do_something(all_points: &points) { for i in 0..all_points.count { unsafe { let crr_point = &*all_points.array_of_points.offset(i as isize); println!("{:?}", crr_point); } } } 中的给定部分中构造适当的Rust切片:

points

请注意在任何一种情况下您如何 #[no_mangle] pub fn do_something(all_points: &points) { let point_array = unsafe { std::slice::from_raw_parts(all_points.array_of_points, all_points.count as usize) }; for crr_point in point_array { println!("{:?}", crr_point); } } 进行编码。

另请参阅:

答案 1 :(得分:0)

注意:这个答案有点不对,建议您为C代码使用其他数据布局。

您可以将C结构更改为以下形式:

typedef struct point {
    int x;
    int y;
} point;

typedef struct points {
    size_t len;
    point points[];
} points;

这称为flexible array member,这是一个非常不错的未知C功能,允许您仅进行一次分配。典型的用例符合您的情况。

此外,即使在C int中也不适合表示大小,您也应该使用size_t

您还应该使用bindgen来处理FAM,它提供了as_slice()之类的有用功能。

给出以下C代码:

typedef struct point {
    int x;
    int y;
} point;

typedef struct points {
    size_t len;
    point points[];
} points;

struct points *new_points(size_t len) {
  struct points *points = malloc(sizeof *points + sizeof *points->points * len);
  if (points) {
    points->len = len;
  }
  return points;
}

它当前生成:

#[repr(C)]
#[derive(Default)]
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>);

impl<T> __IncompleteArrayField<T> {
    #[inline]
    pub fn new() -> Self {
        __IncompleteArrayField(::std::marker::PhantomData)
    }
    #[inline]
    pub unsafe fn as_ptr(&self) -> *const T {
        ::std::mem::transmute(self)
    }
    #[inline]
    pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
        ::std::mem::transmute(self)
    }
    #[inline]
    pub unsafe fn as_slice(&self, len: usize) -> &[T] {
        ::std::slice::from_raw_parts(self.as_ptr(), len)
    }
    #[inline]
    pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
        ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
    }
}
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
    fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
        fmt.write_str("__IncompleteArrayField")
    }
}
impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
    #[inline]
    fn clone(&self) -> Self {
        Self::new()
    }
}
impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct point {
    pub x: ::std::os::raw::c_int,
    pub y: ::std::os::raw::c_int,
}

#[repr(C)]
#[derive(Debug)]
pub struct points {
    pub len: usize,
    pub points: __IncompleteArrayField<point>,
}

extern "C" {
    pub fn new_points(len: usize) -> *mut points;
}

省略了一些行

有了这种绑定,您可以在Rust方面进行操作:

#[no_mangle]
pub fn print_points(points: &points) {
    for point in unsafe { points.points.as_slice(points.len) } {
        println!("{:?}", point);
    }
}

as_ptr()可以避免创建临时切片的开销,您可以根据需要进行操作。

在C端:

#include <stdlib.h>

typedef struct point {
    int x;
    int y;
} point;

typedef struct points {
    size_t len;
    point points[];
} points;

struct points *new_points(size_t len);
void print_points(struct points *points);

int main(void) {
  struct points *points = new_points(42);

  int x = 0;
  for (size_t i = 0; i < points->len; i++, x++) {
    points->points[i] = (struct point){ .x = x, .y = -x};
  }
  print_points(points);
}

但是请记住,这不能保证,您可能会遇到完全未定义的行为,请小心。


#[derive(Debug)]
#[repr(C)]
pub struct points {
    count: c_int,
    array_of_points: [point],
}

您告诉编译器array_of_points是有效的片,但您的代码并非如此:

#[no_mangle]
pub fn do_something(all_points: &points) {
    for i in 0..all_points.count {
        let crr_point = &all_points.array_of_points[i as usize];
        println!("{:?}", crr_point);
    }
}

是完全未定义的行为。我认为没有办法在C端创建这样的东西,我没找到