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

时间:2014-09-16 23:38:23

标签: c string struct rust dlopen

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

但是,在访问struct中的第二个字符串时,我遇到了一些段错误,所以我做了一个小测试程序,试着弄清楚我做错了什么。

这是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-&gt;版本的地址实际上是0xc(12),这是第一个字符串的长度。因此,看起来打包到库中的结构也包含内存地址之后的字符串长度。

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

我在Linux上运行最新的Rust:

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

2 个答案:

答案 0 :(得分:3)

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

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

就在这里,我认为这是一个解决方法,因为无法在静态中将&[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

答案 1 :(得分:2)

切片(&[T]&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。正如评论中sellibitze所建议的那样,您应该在C源文件中定义该变量。