首页 > 解决方案 > 如何从 Rust 库访问全局 C 结构数组?

问题描述

我正在将一个静态库移植到 Rust,它将与一个提供全局数组的 C 应用程序链接。下面是结构体和数组的定义:

typedef struct glkunix_argumentlist_struct {
    char *name;
    int argtype;
    char *desc;
} glkunix_argumentlist_t;
extern glkunix_argumentlist_t glkunix_arguments[];

没有长度参数,数组中的最后一个条目将是{NULL, 0, NULL}( example ),您在遍历数组时对其进行测试。

我认为这是结构的正确 Rust 表示:

#[repr(C)]
struct GlkUnixArgument {
    name: *const c_char,
    argtype: c_int,
    desc: *const c_char,
}

Rust Nomicon展示了如何为单个整数定义 extern

我已经看到,如果您有一个长度参数,您可以使用它std::slice::from_raw_parts来获取 slice,但我们还没有长度。我可以修改 C 代码以提供一个,但如果可以的话,我希望能够提供一个替代品。

我还没有看到如何从外部创建一个单一的 Rust 结构。虽然我确实只是有std::slice::from_raw_parts长度为 1 的想法。

有没有更好的方法在 Rust 中更直接地定义外部结构?

标签: crustffi

解决方案


在使用以下命令创建切片之前,您必须计算 C 数组的长度from_raw_parts

use libc::{c_char, c_int};

#[repr(C)]
pub struct GlkUnixArgument {
    name: *const c_char,
    argtype: c_int,
    desc: *const c_char,
}

pub unsafe fn glkunix_arguments() -> &'static [GlkUnixArgument] {
    extern "C" {
        pub static glkunix_arguments: *const GlkUnixArgument;
    }

    let len = (0..)
        .take_while(|i| {
            let arg = glkunix_arguments.offset(*i);
            (*arg).name != std::ptr::null()
                || (*arg).argtype != 0
                || (*arg).desc != std::ptr::null()
        })
        .count();

    std::slice::from_raw_parts(glkunix_arguments, len)
}

你甚至可以将 C 结构映射到 Rust 结构:

use libc::{c_char, c_int, strlen};
use std::{ptr, slice, str};

pub struct GlkUnixArgument {
    pub name: &'static str,
    pub argtype: i32,
    pub desc: &'static str,
}

pub unsafe fn glkunix_arguments() -> Vec<GlkUnixArgument> {
    extern "C" {
        pub static glkunix_arguments: *const GlkUnixArgument_;
    }

    #[repr(C)]
    pub struct GlkUnixArgument_ {
        name: *const c_char,
        argtype: c_int,
        desc: *const c_char,
    }

    impl GlkUnixArgument_ {
        fn is_not_empty(&self) -> bool {
            self.name != ptr::null() || self.argtype != 0 || self.desc != ptr::null()
        }
    }

    unsafe fn c_str_to_rust_str(s: *const i8) -> &'static str {
        str::from_utf8(slice::from_raw_parts(s as *const u8, strlen(s))).unwrap()
    }

    let len = (0..)
        .map(|i| glkunix_arguments.offset(i))
        .take_while(|&arg| (*arg).is_not_empty())
        .count();

    slice::from_raw_parts(glkunix_arguments, len)
        .iter()
        .map(|args| GlkUnixArgument {
            name: c_str_to_rust_str(args.name),
            desc: c_str_to_rust_str(args.desc),
            argtype: args.argtype,
        })
        .collect()
}

使用惰性构造可以确保您只执行一次操作。


推荐阅读