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

8
我将为您翻译有关IT技术的内容。以下是需要翻译的文本:

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

然而,当访问结构体中的第二个字符串时,我遇到了一些段错误,因此我编写了一个小型测试程序来尝试找出我的错误所在。

以下是使用“rustc --crate-type dylib test.rs”编译的Rust代码(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指针吗? - nneonneo
1
看起来 Rust 字符串不仅仅是 char *。是否有一个包含 Rust 字符串定义以便与 C 连接的 .h 文件? - EOF
3个回答

3
一个切片(&[T]&str)的布局是一个指针后面跟着一个长度,正如std::raw模块的Slice结构文档所述。这就是为什么从你的C代码中读取version字段会显示name字段值的长度。 (但是请注意,切片的确切内存布局不被认为是稳定的,因此它可能在以后的版本中更改。无论如何,您不应该将Rust特定的数据类型传递给C;只传递原始类型-包括原始指针-和使用#[repr(C)]注释的类型。)

编辑:不幸的是,目前似乎没有办法在Rust中实现这一点。有函数可以从切片中获取原始指针,但静态初始化器中不允许函数调用。如评论中所建议的,您应该在C源文件中定义该变量。


1
字节文字(类型为&[u8])真的可以强制转换为*const u8吗? - huon
@chrippa:好的,切片上有一个 as_ptr 方法。但我认为你不能在静态初始化的一部分中调用它。我唯一看到的解决方案是编写一个 C 文件,将其编译并链接到 Rust 代码的其余部分。 - sellibitze
我在play.rust-lang.org上测试时遇到了一些其他错误,因为我没有main方法,但是没有出现这种情况。当我添加了main方法后,就会出现问题。因为目前似乎无法实现,所以我改变了我的答案... - Francis Gagné
Slice 的表示不能保证不改变:就像普通的 Rust 结构体一样,字段的顺序并不重要。因此,Rust 目前无法为您提供所需的保证——不要在 ffi 边界传递 &str - bluss
谢谢!我评论后才意识到你的回答是来自 Rust 1.0 之前的时代。当然,由于新的回答,这一点显露出来了。 - bluss
显示剩余2条评论

2
简短的回答是,你不能静态分配这样一个结构体。未来的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
     }
}

从重复问题中复制的答案

这是一个从重复问题中复制的答案。

2
如其他答案中提到的,主要问题在于&str是一个动态大小类型的引用。Rust使用“fat”指针来在内存中表示这种引用或指针,它还包含一个长度,而不像C语言中的const char *一样只有一个简单指针。
由于这些引用的内存布局尚不稳定,因此您不能可靠地在FFI中使用&str&[T]dyn T
Rust 1.32(2019年1月)以来,可以在常量上下文中使用str::as_ptr,这允许轻松创建指向静态字符串的原始指针。唯一剩下的问题是,原始指针默认被认为是线程不安全的。因此,您还需要在PluginDesc上实现Sync,以断言您的结构体是线程安全的。
#[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()
};

自2017年以来,还有一个名为null_terminated 的软件包,使空终止字符串更易读且更安全,但目前需要使用不稳定的语言特性,只能在夜间编译器中使用:

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")
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接