Skip to content

34.4 更复杂的数据类型

对于交互接口中的简单类型,我们直接使用标准库中定义好的std::os::raw里面的类型就够了。而更复杂的类型就需要我们手动封装了。 比如结构体就需要用#[repr(C)]修饰,以保证这个结构体在 Rust 和 C 双方的内存布局是一致的。

如果我们需要做的是跟常见的操作系统交互,许多常用的数据结构都已经有人封装好了,可以在 crates.io 找 libc 库直接使用。接下来我们用一个示例演示一下在接口中包含结构体该怎样做。这个示例同时也使用了 cargo 来管理 Rust 项目,且使用动态链接库的方式执行。

Rust 项目Cargo.toml如下所示:


rust
[package]
name = "log"
version = "0.1.0"
authors = ["F001"]

[dependencies]
libc = "0.2"

[lib]
name = "rust_log"
crate-type = ["cdylib"]

src/lib.rs文件内容如下所示:


rust
#![crate_type = "cdylib"]

extern crate libc;

use libc::{c_int, c_char};
use std::ffi::CStr;

// this struct is used as an public interface
#[repr(C)]
#[no_mangle]
pub struct RustLogMessage {
    id: c_int,
    msg: *const c_char
}

#[no_mangle]
pub extern "C" fn rust_log(msg: RustLogMessage) {
    let s = unsafe { CStr::from_ptr(msg.msg) };
    println!("id:{} message:{:?}", msg.id, s);
}

使用 cargo build 就可以编译出对应的动态库。

C 语言的调用代码如下所示:


rust
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
struct RustLogMessage {
    int id;
    char *msg;
};

int main()
{
    void *rust_log_lib;

    void (*rust_log_fn)(struct RustLogMessage msg);

    rust_log_lib = dlopen("./rust_log/target/debug/librust_log.so", RTLD_LAZY);
    if ( rust_log_lib != NULL ) {
        rust_log_fn = dlsym(rust_log_lib, "rust_log");
    } else {
        printf("load so library failed.\n");
        return 1;
    }

    for (int i = 0; i < 10; i++) {
        struct RustLogMessage msg = {
            id : i,
            msg : "string in C\n",
        };
        rust_log_fn(msg);
    }

    if (rust_log_lib != NULL ) dlclose(rust_log_lib);

    return EXIT_SUCCESS;
}

编译命令为gcc main.c -ldl。执行程序之前,要保证动态链接库和可执行程序之间的相对路径关系是正确的,然后执行。可见,参数正确地在 Rust 和 C 之间传递了。

Released under the MIT License