面试题答案
一键面试关键步骤
- 定义C接口:在C语言中定义要暴露给Rust的函数接口,使用
extern "C"
指定C语言的调用约定。例如:
// c_code.c
#include <stdio.h>
// 定义一个简单的C函数
extern "C" int add(int a, int b) {
return a + b;
}
- 编译C代码为动态链接库:
- Windows:使用MinGW或MSVC编译为
.dll
文件。例如使用MinGW:gcc -shared -o c_code.dll c_code.c
- Linux:使用GCC编译为
.so
文件。gcc -shared -o c_code.so c_code.c
- macOS:使用Clang编译为
.dylib
文件。clang -dynamiclib -o c_code.dylib c_code.c
- Windows:使用MinGW或MSVC编译为
- 在Rust中声明外部函数:在Rust库中使用
extern "C"
块声明要调用的C函数。例如:
// rust_lib.rs
#[cfg(target_os = "windows")]
const LIB_NAME: &str = "c_code.dll";
#[cfg(target_os = "linux")]
const LIB_NAME: &str = "libc_code.so";
#[cfg(target_os = "macos")]
const LIB_NAME: &str = "libc_code.dylib";
extern "C" {
fn add(a: i32, b: i32) -> i32;
}
- 加载动态链接库并调用函数:使用
std::ffi::CString
和std::os::raw::c_char
等处理字符串类型(如果有),使用libloading
等库来加载动态链接库(在Rust 1.36及以上可以使用std::os::unix::ffi::OsStrExt
等处理Unix系统路径)。例如:
use std::ffi::CString;
use std::os::raw::c_char;
use libloading::{Library, Symbol};
fn main() {
let lib = Library::new(LIB_NAME).expect("Failed to load library");
let add: Symbol<unsafe extern "C" fn(i32, i32) -> i32> = lib.get(b"add").expect("Failed to get symbol");
let result = unsafe { add(2, 3) };
println!("The result is: {}", result);
}
跨平台兼容性问题
- 文件命名和路径分隔符:不同操作系统动态链接库的命名规则不同(
.dll
、.so
、.dylib
),且路径分隔符也不同(Windows使用\
,Unix系统使用/
)。可以通过cfg
属性根据目标操作系统选择正确的库名和路径处理方式。 - 调用约定:虽然使用
extern "C"
指定了C调用约定,但某些平台特定的编译器优化或调用规范可能会导致问题。确保在编译C代码和Rust代码时使用一致的调用约定。 - 数据类型:不同平台对某些数据类型的大小和表示可能不同。例如,
long
类型在32位和64位系统上大小不同。尽量使用固定大小的数据类型(如i32
、u64
等)。 - 字符编码:如果涉及字符串交互,Windows通常使用UTF - 16,而Unix系统使用UTF - 8。需要正确转换编码,例如使用
std::ffi::CString
和std::ffi::OsString
进行转换。
示例代码
上述代码段完整展示了从定义C接口、编译动态链接库到在Rust中加载并调用该接口函数的跨平台示例。