C语言交互

rust在和外部语言交互,有两种情况

  • RUST 调用C语言接口
  • C调用 Rust 接口

我们将分别针对上述两种情况进行说明

类型差异

先要清楚RUST 的FFI定义: Rust 的 FFI(Foreign Function Interface)功能来调用 C 代码

FFI 首先需要解决的一个就是关于类型的问题: 两种语言如何在类型一致上达成共识

关于字符串的类型差异: Rust 使用 String 类型来表示拥有的字符串,使用 str 原始码来表示字符串的借用片段。这两种字符串始终采用 UTF-8 编码, 中间可能包含 nul 字节,也就是说,如果查看组成字符串的字节,其中可能有一个 0 字节。String 和 str 都显式地存储其长度;字符串的末尾不像 C 语言那样有 nul 结束符。

C 字符串与 Rust 字符串不同点:

  • 编码不同: Rust 字符串使用 UTF-8,但 C 字符串可能使用其他编码。 如果使用的是 C 语言字符串,则应明确检查其编码,而不是像 Rust 中那样假定其为 UTF-8。

  • 字符大小: C 语言字符串可以使用 char 或 wchar_t 大小的字符;请注意,C 语言的 char 与 Rust 的 char 不同。 C 标准对这些类型的实际大小不作解释,但为每种字符类型组成的字符串定义了不同的应用程序接口。 Rust 字符串始终是 UTF-8,因此不同的 Unicode 字符将以不同的字节数进行编码。 Rust 类型 char 代表 "Unicode 标量值",与 "Unicode 代码点 "相似,但并不相同。

  • nul 结束符和隐式字符串长度: 通常情况下,C 语言字符串都是 nul 结束符,即字符串末尾有一个 0 字符。 要计算字符串的长度,C 代码必须手动调用类似 strlen() 的函数(用于基于字符的字符串), 或 wcslen() 的函数(用于基于 wchar_t 的字符串)。这些函数返回字符串中不包括 nul 结束符的字符数, 因此缓冲区长度实际上是 len+1 个字符。Rust 字符串没有 nul 结束符;它们的长度始终存储,无需计算。 在 Rust 中,访问字符串的长度是一个 O(1) 运算(因为长度已存储); 而在 C 语言中,访问字符串的长度是一个 O(n) 运算,因为需要通过扫描字符串以查找 nul 结束符来计算长度。
  • 内部 nul 字符: 当 C 语言字符串具有 nul 结束符时,通常意味着中间不能有 nul 字符, nul 字符基本上会截断字符串。Rust 字符串中间可以有 nul 字符,因为在 Rust 中,nul 不一定是字符串的结束符。

类型支持

RUST std::ffi 模块 提供了关于类型的定义 该模块提供跨非 Rust 接口(如其他编程语言和底层操作系统)处理数据的实用程序。 它主要用于 FFI(外来函数接口)绑定以及需要与其他语言交换类似 C 语言字符串的代码。

从 Rust 到 C:CString 代表一个自有的、对 C 语言友好的字符串:它以 nul 结尾,内部没有 nul 字符。 Rust 代码可以从普通字符串中创建 CString(前提是字符串中间没有 nul 字符), 然后使用各种方法获得原始 *mut u8,并将其作为参数传递给使用 C 语言字符串约定的函数。

从 C 到 Rust:CStr 表示借用 C 语言的字符串;您可以用它来封装从C语言函数中获取的原始 *const u8。 CStr 保证是一个以空字节结尾的字节数组。一旦有了 CStr,如果它是有效的 UTF-8,就可以将其转换为 Rust &str, 或者通过添加替换字符进行有损转换。

其他类型:

  • c_char Equivalent to C’s char type.
  • c_double Equivalent to C’s double type.
  • c_float Equivalent to C’s float type.
  • c_int Equivalent to C’s signed int (int) type.
  • c_long Equivalent to C’s signed long (long) type.
  • c_longlong Equivalent to C’s signed long long (long long) type.
  • c_schar Equivalent to C’s signed char type.
  • c_short Equivalent to C’s signed short (short) type.
  • c_uchar Equivalent to C’s unsigned char type.
  • c_uint Equivalent to C’s unsigned int type.
  • c_ulong Equivalent to C’s unsigned long type.
  • c_ulonglong Equivalent to C’s unsigned long long type.
  • c_ushort Equivalent to C’s unsigned short type.

  • c_void: Enums Equivalent to C’s void type when used as a pointer.

RUST 调用C

首先我们定义一个C的函数接口 把他编译为 一个静态库

// gcc 
unsigned int mystrlen(char *str) {
    unsigned int c;
    for (c = 0; *str!= '\0'; c++,*str++);
    return c ;
}

在rust中声明C接口

extern "C" {
    fn mystrlen(str: *const c_char) -> c_uint;
}


fn main() {
    let c_string = CString::new("C from Rust").expect("failed");
    let count = unsafe {
        mystrlen(c_string.as_ptr())
    };

    println!("lenth: {}",count);
}

编译命令:

 gcc -shared -o liblen.so len.c
 export LD_LIBRARY_PATH=./
 rustc  main.rs -L./  -l len
guoweikang@ubuntu-virtual-machine:~/code/rust/c$ tree
.
├── len.c
├── liblen.so
├── main
└── main.rs

C调用RUST

首先 如果是C调用RUST,调用的RUST代码,需要先编译为一个动态库 通过指定 crate-type

rustc crate-type=cdylib

让我们重点关注一下代码这边

use std::ffi::Cstr;
use std::os::raw::c_char;

#[repr(C)] // 布局遵循C语言的布局模式 
pub euum Order {
    Gt,
    Lt,
    Eq
}

#[no_mangle]//ABI符合C语言的ABI
pub extern "C" fn compare_str(a: *const c_char, b: *const c_char) -> Order 
{
    let a = unsafe {Cstr::from_ptr(a).to_bytes()};
    let b = unsafe {Cstr::from_ptr(b).to_bytes()};

    if a > b {
        Order::Gt
    } else if a < b {
        Order::Lt
    } else {
        Order::Eq
    }
}
#include <stdint.h>
#include <stdio.h>

int32_t compare_str(const char* val1,const char* val2);

int main() {
    printf("%d\n",compare_str("amanda","brian"));
    return 0;
}

编译命令:

rustc  --crate-type=cdylib  ./cmp.rs
export LD_LIBRARY_PATH=./
gcc main.c  -L ./ -lcmp -o main