Appearance
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 之间传递了。