为什么从Rust读取时C结构返回错位垃圾数据?

时间:2017-07-12 15:20:37

标签: rust ffi

我正在尝试在Rust中包装一个C函数。 C函数struct elem* get_list()返回以下结构:

struct elem {
    char data[5],
    struct elem* next
};

在Rust中,我已按以下方式声明了该函数。 C函数的声明返回*const c_void,如旧版本的Rust文档中所述,在撰写本文时我无法找到。我尝试返回*const elem并使用指针,实现相同的结果:

extern "C" {
    pub fn get_list() -> *const c_void;
}

struct表示链表,next是指向列表下一个元素的指针。在Rust内部,我以下列方式声明了结构:

#[repr(C)]
pub struct elem {
    pub data: [u8; 5],
    pub next: *const c_void,
}

该函数返回一个*const c_void指针,指向链表的第一个元素(类型为elem)。我正在尝试使用以下代码读取链表的元素:

let head = get_list();
while !head.is_null() {
    let el: &elem = mem::transmute(head);
    let str = el.data;
    let str = CStr::from_bytes_with_nul(&str).unwrap();
    //do something
    head = el.next();
}

这会读取垃圾数据 - 指针未正确对齐,字符串都是错误且非空终止,下一个指针导致随机数据(当从C直接调用函数时,列表具有不同的大小)

我尝试使用该函数返回指向elem的指针并仅使用指针,我尝试从str的地址转换el - 它总是读取相同的垃圾数据。如何正确对齐?

我知道如何使用指针而不是数组来执行此操作,这就是在Rust文档中演示的方式,但我无法更改C代码。

1 个答案:

答案 0 :(得分:1)

在我为这个案例编写了一个示例库之后,我发现它不是一个外部问题,而是一个CStr问题。正如在示例中修复的那样,我将缓冲区切片到第一个NUL终止符的位置,我提供了我为正确的externing编写的示例。

<强> list.c

#include <stdlib.h>
#include <string.h>

struct elem {
    char data[5];
    struct elem* next;
};

struct elem* get_list() {
    struct elem* head = malloc(sizeof(struct elem));
    strcpy(head->data, "1");

    struct elem* el = malloc(sizeof(struct elem));
    head->next = el;

    strcpy(el->data, "2");

    el->next = malloc(sizeof(struct elem));
    el = el->next;
    strcpy(el->data, "3");
    el->next = NULL;

    return head;
}

<强> main.rs

use std::ffi::CStr;

#[repr(C)]
pub struct elem {
    pub data: [u8; 5],
    pub next: *const elem
}

#[link(name = "list", kind = "static")]
extern {
    pub fn get_list() -> *const elem;
}

fn main() {
    unsafe {
        let mut list = get_list();
        // Note, that, if we call from_bytes_with_nul it will throw  
        // an NulInternal error, therefore,
        // we have to slice the buffer to the first NUL-terminator
        while !list.is_null() {
            let mut null_pos = (*list).data.len() - 1;
            {
                for i in 0..(*list).data.len() {
                    if (*list).data[i] == 0 {
                        null_pos = i + 1;
                        break
                    }
                }
            }
            let str = CStr::from_bytes_with_nul(
                          (*list).data[..null_pos]
                      ).unwrap();
            println!("{:?}", str);
            list = (*list).next;
        }
    }
}

输出

"1"
"2"
"3"

实施的关键方面:

  • 定义相同的结构,用#[repr(C)]注释,这样就可以了 以与C one相同的方式对齐。

  • 定义extern函数以返回指向结构的const指针。

  • 使用指针代替std::mem::transmute

  • 小心使用空指针和终结符