【问题标题】:Creating a static C struct containing strings创建包含字符串的静态 C 结构
【发布时间】:2014-11-10 20:50:27
【问题描述】:

我正在尝试在 Rust 中创建一个动态库,将结构导出为符号,该符号将通过 dlopen() 加载到 C 程序中。

但是,我在访问结构中的第二个字符串时遇到了一些段错误,所以我做了一个小测试程序来尝试找出我做错了什么。

这是 Rust 代码 (test.rs),使用“rustc --crate-type dylib test.rs”编译:

#[repr(C)]
pub struct PluginDesc {
    name: &'static str,
    version: &'static str,
    description: &'static str
}


#[no_mangle]
pub static PLUGIN_DESC: PluginDesc = PluginDesc {
    name: "Test Plugin\0",
    version: "1.0\0",
    description: "Test Rust Plugin\0"
};

这是尝试加载库(test.c)的C程序,使用“gcc test.c -ldl -o test”编译:

#include <dlfcn.h>
#include <stdio.h>


typedef struct {
    const char *name;
    const char *version;
    const char *description;
} plugin_desc;


int main(int argc, char **argv) {
    void *handle;
    plugin_desc *desc;

    handle = dlopen("./libtest.so", RTLD_LOCAL | RTLD_LAZY);
    if (!handle) {
        printf("failed to dlopen: %s\n", dlerror());
        return 1;
    }

    desc = (plugin_desc *) dlsym(handle, "PLUGIN_DESC");
    if (!desc) {
        printf("failed to dlsym: %s\n", dlerror());
        return 1;
    }

    printf("name: %p\n", desc->name);
    printf("version: %p\n", desc->version);
    printf("description: %p\n", desc->description);

    return 0;
}

这是输出:

name: 0x7fa59ef8d750
version: 0xc
description: 0x7fa59ef8d75c

可以看到,desc->version的地址其实是0xc(12),也就是第一个字符串的长度。所以看起来打包到库中的结构也包含内存地址后面的字符串长度。

我在这里使用了错误的字符串类型吗?如您所见,我还必须手动终止字符串 NULL。我尝试使用 CString 包装器,但在这种情况下似乎不起作用(“静态项不允许有析构函数”)。

我每晚在 Linux 上运行最新的 Rust:

$ rustc --version
rustc 0.12.0-pre-nightly (f8426e2e2 2014-09-16 02:26:01 +0000)

【问题讨论】:

  • 您是否尝试过将*i8 指针放在结构中?
  • 看起来 rust-strings 不只是 char * 的。是否有一个包含用于与 C 链接的 rust 字符串定义的 .h 文件?

标签: c string struct rust dlopen


【解决方案1】:

切片的布局(&amp;[T]&amp;str)是一个后跟长度的指针,如 the Slice struct of the std::raw module 所述。这就是为什么从 C 代码中读取 version 字段会显示 name 字段值的长度。 (但是请注意,切片的确切内存布局并不被认为是稳定的,因此它可能会在以后的版本中发生变化。无论如何,您都应该将 Rust 特定的数据类型传递给 C;只传递原始类型(包括原始指针)和带有 #[repr(C)] 注释的类型。)

编辑:不幸的是,目前在 Rust 中似乎没有办法做到这一点。有一些函数可以从切片中获取原始指针,但 function calls are not allowed in static initializers。正如 cmets 中的 sellibitze 所建议的,您应该在 C 源文件中定义该变量。

【讨论】:

  • 字节文字(&amp;[u8] 类型)是否真的强制转换为 *const u8
  • 不幸的是,这也不起作用。我收到错误“预期*const i8,找到&amp;'static [u8](预期i8,找到向量)”,但是在将类型更改为“&'static [u8]”使其编译时,它与原始代码存在相同的问题。
  • @chrippa:嗯,切片上有一个as_ptr 方法。但我认为您不能将其称为静态初始化的一部分。我看到的唯一解决方案是编写一个 C 文件,对其进行编译并将其链接到其余的 Rust 代码。
  • 呜呜。当我在 play.rust-lang.org 上测试这个时,我没有 main 方法,它抱怨了一些其他错误,但不是这个。使用main 方法,它确实会抱怨。我改变了答案,因为目前似乎不可能......
  • Slice 的表示不保证不会改变:就像常规的 rust 结构一样,字段的顺序并不重要。所以 Rust 目前没有给你所需的保证——不要让&amp;str 越过 ffi 边界。
【解决方案2】:

简短的回答是您不能静态分配这样的结构。未来的 Rust 可能会获得这种能力。

您可以做的是静态分配一个包含空指针的结构,并在调用函数时将这些空指针设置为有用的东西。 Rust 有 static mut。它需要不安全的代码,根本不是线程安全的,并且(据我所知)被认为是代码异味

在这里,我认为这是一种解决方法,即无法将 &amp;[T] 转换为静态中的 *const T

static S: &'static [u8] = b"http://example.org/eg-amp_rust\n\0";
static mut desc: LV2Descriptor = LV2Descriptor {
    amp_uri: 0 as *const libc::c_char, // ptr::null() isn't const fn (yet)
};

#[no_mangle]
pub extern fn lv2_descriptor(index: i32) -> *const LV2Descriptor {
     let ptr = S.as_ptr() as *const libc::c_char;
     unsafe {
        desc.amp_uri = ptr;
        &desc as *const LV2Descriptor
     }
}

answer copied from duplicate question

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-06-16
    • 1970-01-01
    • 1970-01-01
    • 2021-11-30
    • 1970-01-01
    • 1970-01-01
    • 2020-04-20
    相关资源
    最近更新 更多