创建包含字符串的静态C结构

chr*_*ppa 8 c string struct dlopen rust

我正在尝试在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"
};
Run Code Online (Sandbox Code Playgroud)

这是尝试加载库(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;
}
Run Code Online (Sandbox Code Playgroud)

这是输出:

name: 0x7fa59ef8d750
version: 0xc
description: 0x7fa59ef8d75c
Run Code Online (Sandbox Code Playgroud)

如您所见,desc-> version的地址实际上是0xc(12),这是第一个字符串的长度.所以看起来像打包到库中的结构也包含内存地址之后的字符串长度.

我在这里使用错误的字符串类型吗?正如您所看到的,我还必须手动将字符串NULL终止.我试图使用CString包装器,但在这种情况下似乎不起作用("静态项目不允许有析构函数").

我在Linux上运行最新的Rust night:

$ rustc --version
rustc 0.12.0-pre-nightly (f8426e2e2 2014-09-16 02:26:01 +0000)
Run Code Online (Sandbox Code Playgroud)

cg9*_*909 2

正如其他答案中已经提到的,主要问题是&str对动态大小类型的引用。Rust 使用也包含长度的“胖”指针来表示内存中的此类引用或指针,而不是使用像 C 那样的简单指针const char *

由于这些引用的内存布局(尚未)稳定,因此您无法可靠地将&str,&[T]dyn T用作 FFI。

由于Rust 1.32(2019 年 1 月)str::as_ptr可在常量上下文中使用,因此可以轻松创建指向静态字符串的原始指针。唯一剩下的问题是原始指针默认被认为是线程不安全的。因此,您还需要实现SynconPluginDesc来断言您的结构是线程安全的。

#[repr(C)]
pub struct PluginDesc {
    name: *const u8,
    version: *const u8,
    description: *const u8
}

unsafe impl Sync for PluginDesc {}

#[no_mangle]
pub static PLUGIN_DESC: PluginDesc = PluginDesc {
    name: "Test Plugin\0".as_ptr(),
    version: "1.0\0".as_ptr(),
    description: "Test Rust Plugin\0".as_ptr()
};
Run Code Online (Sandbox Code Playgroud)

自 2017 年以来,还有一个null_terminated包可以使以 null 结尾的字符串更具可读性且使用更安全,但目前需要不稳定的语言功能,这些功能只能与夜间编译器一起使用:

use null_terminated::{str0_utf8, NulStr};

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

#[no_mangle]
pub static PLUGIN_DESC: PluginDesc = PluginDesc {
    name: str0_utf8!("Test Plugin"),
    version: str0_utf8!("1.0"),
    description: str0_utf8!("Test Rust Plugin")
};
Run Code Online (Sandbox Code Playgroud)