【问题标题】:How can I access a global C array of structs from a Rust library?如何从 Rust 库访问全局 C 结构数组?
【发布时间】:2019-11-18 01:06:43
【问题描述】:

我正在将一个静态库移植到 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 shows how to define an extern for a single integer.

我已经看到如果你有一个长度参数you can use std::slice::from_raw_parts to get a slice,但我们还没有长度。我可以修改 C 代码以提供一个,但如果可以的话,我希望能够提供一个替代品。

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

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

【问题讨论】:

  • @PitaJ 但是在访问结构字段之前我是否需要使用from_raw_parts 之类的东西(显然这将是一个不同的函数)?
  • “没有长度参数”一个 C 数组总是有一个长度,如果不是,它就不是一个定义的数组。你可以通过const size_t len = sizeof glkunix_arguments / sizeof *glkunix_arguments;
  • @Stargateur 我的意思是没有为函数提供长度参数。我可以更改最终用户代码,但它不会是替代品。
  • @Stargateur 我从来没有说过它没有长度。我说没有长度参数,我的意思是它没有暴露为一个全局变量,我可以简单地使用 Rust 的外部访问,也没有将它提供给库的函数。

标签: c rust ffi


【解决方案1】:

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

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()
}

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

【讨论】:

  • 有趣的是,我以前从未见过函数内部的外部或结构。谢谢!
  • Rust 在这方面非常灵活。你可以把任何你想要的东西放在一个函数中。
  • 其实你可以在一个范围内放任何你想要的东西,我可以混搭fnmodstructenumuniontype别名, traits、consts、externstatic[mut]等;有一些限制(即,除了成员之外,您不能在 struct/enum/union 声明中放入任何其他内容)
猜你喜欢
  • 1970-01-01
  • 2014-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-15
  • 1970-01-01
  • 2014-04-07
  • 2010-09-22
相关资源
最近更新 更多