前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Rust FFI 编程 - Rust导出共享库03

Rust FFI 编程 - Rust导出共享库03

作者头像
MikeLoveRust
发布2020-08-04 17:24:42
7580
发布2020-08-04 17:24:42
举报

这次,我们来关注一下Rust语言的基本特性到C的映射。

我们已经了解了,Rust语言是多泛式(混合泛式)的语言,它可以做命令式(过程式)编程,也可以做面向对象编程,也可以做函数式编程。把Rust简单地归类为某种泛式的编程语言,都不太合适。Rust就是Rust。

C语言是比较传统的过程式编程语言,因此,从Rust到C的转换,就会有一些无法直接对标的东西。于是,做这种映射工作就需要一些额外的规范或约定。

本文我们来关注:

  1. 结构体的方法的处理
  2. 泛型的处理
  3. Type alias
  4. Enum 到 C 的映射

结构体的方法的处理

我们知道,Rust中,可以对结构体(或 enum 等)添加方法。这是属于面向对象的特性,而纯C是不支持这种特性的。于是,我们必须将这些方法单独实现为一批函数,在这批函数名前面加上统一的前缀,看下面代码:

rust 代码

// rust

#[repr(C)]
struct Foo {
  a: isize,
  b: isize
}

impl Foo {
  pub fn method1() {
    ...
  }

  pub fn method2(x: isize) -> isize {
    ...
  }

  pub fn method3(x: isize, y: isize) -> isize {
    ...
  }
}

这段代码翻译成C的时候,对应的大概会是下面这个样子:

struct Foo {
  int a;
  int b;
}

void foo_method1(Foo* foo);
int foo_method2(Foo* foo, int x);
int foo_method1(Foo* foo, int x, int y);

然而,这种映射是不能自动转换的(毕竟只是我们自己的约定),需要手动写出来。于是我们需要实现接口层的Rust代码:

// We have struct Foo now

#[no_mangle]
unsafe extern "C" fn foo_method1(foo: *const Foo) {
  let foo = &*foo;
  foo.method1();
}

#[no_mangle]
unsafe extern "C" fn foo_method2(foo: *const Foo, x: isize) -> isize {
  let foo = &*foo;
  foo.method2(x)
}

#[no_mangle]
unsafe extern "C" fn foo_method3(foo: *const Foo, x: isize, y: isize) -> isize {
  let foo = &*foo;
  foo.method3(x, y)
}

然后,用这个接口层代码编译出动态链接库,C那边使用就行了。

泛型的处理

泛型的处理稍微复杂一些。但实际原理也不难。在Rust中,泛型,我们指的是静态分派,另外还有一种,使用 trait object,实现动态分派。在这里,我们专注于静态分派的分析。

静态分派的意思是,编译器在编译时,根据你对泛型的具体化类型,进行特化展开处理。具体类型有几种,就复制几份不同的特化实现(因此增大了代码量)。这样,在调用时,就直接调用的特化后的函数/方法,而不再需要指针跳转一次了。所以,静态分派相对于动态分派,实际是用空间换时间,效率要高一些。

因此,我们在向C导出含泛型的方法时,也用静态分派的思维实现一个接口层就行了。

下面来看实际代码。比如,我们现在有如下Rust结构体:

#[repr(C)]
struct Buffer<T> {
  data: [T; 8],
  len: usize,
}

并且实现了方法:

impl<T> Buffer<T> {
  pub fn print(&self) {
    ...
  }
}

假如我们在实际中,用到了 i32 和 f32 两种类型。那么,我们实现 FFI 层的时候,需要这样写:

#[no_mangle]
extern "C" fn buffer_print_i32(buf: Buffer<i32>) { ... }

#[no_mangle]
extern "C" fn buffer_print_f32(buf: Buffer<f32>) { ... }

然后,对应的 C 这边的代码就是类似下面的:

struct Buffer_i32 {
  int32_t data[8];
  size_t len;
};

struct Buffer_f32 {
  float data[8];
  size_t len;
};

void buffer_print_i32(Buffer_i32 buf);
void buffer_print_f32(Buffer_f32 buf);

可见,我们在 FFI 的 rust 方面,把方法名具体化了。在 C 这边,除了具体化的方法名,还把类型具体化了。就这样,适应了 C 这边无泛型的困扰。

细节的读者可能会发现,如果有M个方法,N种类型,最后分出来的函数有:M x N 个。

Type alias

Type alias 在 Rust 中,就使用 type 关键字,正好在 C 中,有 typedef 这个关键字,起到类似的功能。

比如,在 Rust 这边,有如下代码:

// type.rs

#[repr(C)]
struct Buffer<T> {
  data: [T; 8],
  len: usize,
}

type IntBuffer = Buffer<i32>;

#[no_mangle]
extern "C" fn buffer_print_int(buf: IntBuffer) { }

对应的 C 代码,会类似下面这个样子:

struct Buffer_i32 {
  int32_t data[8];
  size_t len;
};

typedef Buffer_i32 IntBuffer;

void buffer_print_int(IntBuffer buf);

Type Alias 能让两边的类型名,看起来更一致。

枚举到 C 的映射

Rust 中,枚举分三大类:空枚举(Empty Enum),无字段枚举(Fieldless Enum)和带负载枚举(Data-carrying enum) 。

空枚举指的是:enum Foo; 这种形式。空枚举没有变体,是一个空类型,等于 !

无字段枚举,就是我们通常所说的 C-like 枚举。它的变体中不带有额外数据/字段。

enum SomeEnum { 
  A, 
  B, 
  C, 
}
enum SomeEnum { 
  Variant22 = 22, 
  Variant44 = 44, 
  Variant45, 
}

带负载枚举是 Rust 的特色,就是变体中还带数据负载的枚举,类似下面这种:

enum Foo { 
  Bar(String), 
  Baz, 
}

既然此处我们是要研究与C的对应关系,其实真正Rust要导出共享库给C使用的场景,涉及到的枚举(基本)都是 Fieldless Enum。所以我们这里只限于说明 Fieldless Enum 到 C 枚举布局上的一些细节。

Rust 的枚举上,可以标注其内存布局,像下面这样:

#[repr(C)]
enum SomeEnum { 
  A, 
  B, 
  C, 
}

Rust 的枚举可以标注的布局种类有如下一些:

指定int位数布局

  • #[repr(u8)] 每个变体占用一个字节内存,以下类推
  • #[repr(u16)]
  • #[repr(u32)]
  • #[repr(u64)]
  • #[repr(i8)]
  • #[repr(i16)]
  • #[repr(i32)]
  • #[repr(i64)]

指定C布局

  • #[repr(C)]

指定C布局,具体的每一个变体占用多少内存,是由当前平台的C编译器来决定的。也就是说Rust这边与对手方的C编译器的约定保持一致(比如,4个字节),可能不同的平台,不同的C编译器,会有所不同。

组合指定

  • #[repr(C, u8)]
  • #[repr(C, u16)]

组合指定只能用在带负载枚举上(但是带负载枚举在实际场合中,跨FFI边界的场景并不多,如果有必要,后面开专题说明)。

而 Fieldless enum 只能指定 int 位数布局和 C 布局中的一种,不能组合指定。如:

#[repr(C)]
enum SomeEnum { 
  A, 
  B, 
  C, 
}

转换到C中,可以把 A 与整数进行比较(从0开始递增,此处A=0,B=1,C=2)。其它后续的就是 C 中枚举的知识了,此不赘述。

重要参考

以下链接,都值得一读。

  • https://blog.eqrion.net/announcing-cbindgen/
  • https://s3.amazonaws.com/temp.michaelfbryan.com/objects/index.html
  • https://rust-lang.github.io/unsafe-code-guidelines/layout/enums.html
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust语言学习交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 结构体的方法的处理
  • 泛型的处理
  • Type alias
  • 枚举到 C 的映射
  • 重要参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档