外部块
外部块提供了在当前 crate 中未定义的条目的 声明 ,是 Rust 实现外部函数接口 (FFI) 的基础。这类似于未经检查的导入。
在外部块中允许两种条目声明: 函数 和 静态 。
仅在 unsafe
上下文中才允许调用外部块中声明的函数或访问静态。
unsafe
关键字在 extern
关键字之前语法上是被允许的,但是在语义层面上会被拒绝。
因而宏可以从语法上使用 unsafe
关键字,然后从令牌流中移除。
函数
在外部块中声明的函数与其他 Rust 函数的声明方式相同,但是声明不能有函数体,仅以分号结束。
参数中不允许使用模式,只能使用 标识符 或 _
。不允许使用函数修饰符 (如 const
、 async
、 unsafe
和 extern
)。
外部块中的函数可以像 rust 中定义的函数一样被 rust 代码调用。 Rust 编译器会自动在 Rust ABI 和外部 ABI 之间进行转换。
在外部块中声明的函数隐式地被认为是 unsafe
的。
当将其强转为函数指针时,外部块中声明的函数类型为 unsafe extern "abi" for<'l1, ..., 'lm> fn(A1, ..., An) -> R
,
其中 'l1
, ... 'lm
为其生命周期参数, A1
, ... , An
为参数的声明类型,而 R
为返回值的声明类型。
Statics
在外部块中声明的静态的方式与 静态 在外部块之外声明的方式相同,但没有初始化表达式。 访问在外部块中声明的静态条目是非安全的,无论它是否可变,因为无法保证静态内存中的位模式是否有效,因为负责初始化该静态的代码是不确定的 (例如 C 语言) 。
就像 静态 在外部块之外声明一样,外部静态可以是不可变的也可以是可变的。 在执行任何 Rust 代码之前, 必须 先初始化不可变的静态。仅在 Rust 代码读取它之前,才将其初始化是不够的。
ABI
默认情况下,外部块假设它们正在调用的库在特定平台上使用标准 C ABI。
可以使用 abi
字符串指定其他 ABI,如下所示:
#![allow(unused)] fn main() { // 对于Windows API的接口 extern "stdcall" { } }
有三个跨平台的 ABI 字符串,以确保所有编译器支持:
extern "Rust"
-- 在 Rust 代码中编写普通fn foo()
时的默认 ABI。extern "C"
-- 与extern fn foo()
相同,使用你的 C 编译器支持的默认 ABI。extern "system"
-- 通常与extern "C"
相同,但在 Win32 上是"stdcall"
,在链接到 Windows API 本身时应使用它。
此外,还有一些特定于平台的 ABI 字符串:
extern "cdecl"
-- x86_32 C 代码的默认 ABI。extern "stdcall"
-- x86_32 上 Win32 API 的默认 ABI。extern "win64"
-- x86_64 Windows 上 C 代码的默认 ABI。extern "sysv64"
-- 非 Windows x86_64 上 C 代码的默认 ABI。extern "aapcs"
-- ARM 的默认 ABI。extern "fastcall"
--fastcall
ABI,对应于 MSVC 的__fastcall
和 GCC 和 clang 的__attribute__((fastcall))
。extern "vectorcall"
--vectorcall
ABI,对应于 MSVC 的__vectorcall
和 clang 的__attribute__((vectorcall))
。extern "efiapi"
-- 用于 UEFI 函数的 ABI。
可变函数
在外部块中的函数可以通过将 ...
指定最后一个参数为变参。
在变参之前必须至少有一个参数。可以使用标识符选择性地指定变量参数。
#![allow(unused)] fn main() { extern "C" { fn foo(x: i32, ...); fn with_name(format: *const u8, args: ...); } }
外部块的属性
以下 属性 控制外部块的行为。
link
属性
link
属性 指定编译器为 extern
块中的条目链接的本地库的名称。
它使用 元列表名称值字符串 语法来指定其输入。
name
键是要链接的本地库的名称。 kind
键是可选值,用于指定以下可能值的库类型:
dylib
- 表示动态库。如果未指定kind
,则为默认值。static
- 表示静态库。framework
- 表示 macOS 框架。仅适用于 macOS 目标。raw-dylib
- 表示动态库,其中编译器将生成要链接的导入库 (详情参见dylib
对比raw-dylib
)。仅适用于 Windows 目标。
如果指定了 kind
,必须包括 name
键。
可选的 modifiers
参数是指定要链接的库的链接修饰符的一种方法。
修饰符以逗号分隔的字符串形式指定,每个修饰符前有 +
或 -
前缀,以表明该修饰符已启用或已禁用。
目前不支持在单个 link
属性中指定多个 modifiers
参数,或在同一 modifiers
参数中指定多个相同的修饰符。
例如: #[link(name = "mylib", kind = "static", modifiers = "+whole-archive")]
。
当从主机环境导入符号时,可以使用 wasm_import_module
键来指定 extern
块中条目的 WebAssembly 模块 名称。
如果未指定 wasm_import_module
,则默认模块名为 env
。
#[link(name = "crypto")]
extern {
// …
}
#[link(name = "CoreFoundation", kind = "framework")]
extern {
// …
}
#[link(wasm_import_module = "foo")]
extern {
// …
}
可以在空的外部块上添加 link
属性。
你可以使用它来满足代码中其他地方 (包括上游 crates) 的外部块的链接要求,而不是为每个外部块都添加属性。
链接修饰符: bundle
此修饰符仅与 static
链接类型兼容。使用任何其他类型都会导致编译器错误。
在构建 rlib 或 staticlib 时, +bundle
表示本地静态库将被打包到 rlib 或 staticlib 归档中,然后在链接最终二进制文件时从其中检索出来。
在构建 rlib 时, -bundle
表示本地静态库以名称的方式注册为该 rlib 的依赖项,并且其中的目标文件仅在链接最终二进制文件时包含,文件搜索也在最终链接期间按该名称执行。
在构建 staticlib 时, -bundle
表示本地静态库不会被包含在档案中,某些更高级别的构建系统需要在链接最终二进制文件时随后添加它。
当构建其他目标 (如可执行文件或动态库) 时,此修饰符不起作用。
修饰符的默认值是 +bundle
。
关于这个修饰符的更多实现细节可在 rustc 文档 bundle
找到 。
链接修饰符: whole-archive
此修饰符仅与 static
链接类型兼容。使用任何其他类型都会导致编译器错误。
+whole-archive
表示静态库作为整个归档进行链接,而不会丢弃任何对象文件。
这个修饰符的默认值是 -whole-archive
。
关于这个修改器的更多实现细节可在 rustc 文档 whole-archive
中找到。
链接修饰符: verbatim
此修饰符与所有链接类型兼容。
+verbatim
表示 rustc 本身不会为库名称添加任何目标指定的库前缀或后缀 (如 lib
或 .a
) ,并尝试尽可能要求链接器也是如此。
-verbatim
表示 rustc 将在将库名称传递给链接器之前添加特定目标的前缀和后缀,且不会阻止链接器隐式添加它。
这个修饰符的默认值是 -verbatim
。
关于这个修饰符的更多实现细节可在 rustc 文档 verbatim
中找到。
dylib
对比 raw-dylib
在 Windows 上,链接动态库需要向链接器提供导入库:这是一个特殊的静态库,以这样一种方式声明动态库导出的所有符号,使得链接器知道它们必须在运行时动态加载。
指定 kind = "dylib"
会指示 Rust 编译器基于 name
键链接导入库。
然后,链接器将使用其正常的库解析逻辑来找到该导入库。另外,指定 kind = "raw-dylib"
会指示编译器在编译期间生成一个导入库,并提供给链接器。
只有在 Windows 上才支持 raw-dylib
,不支持 32 位 x86 ( target_arch="x86"
) 。将其用于针对其他平台或 Windows 上的 x86 时将导致编译器错误。
link_name
属性
在 extern
块内的声明上可以指定 link_name
属性 ,以指示要为给定函数或静态导入的符号。
使用 元名称值字符串 语法来指定符号的名称。
#![allow(unused)] fn main() { extern { #[link_name = "actual_symbol_name"] fn name_in_rust(); } }
将此属性与 link_ordinal
属性一起使用会导致编译器错误。
link_ordinal
属性
可以在 extern
块内的声明上应用 link_ordinal
属性,以指示生成要链接的导入库时要使用的数字序数。
在 Windows 上,每个动态库导出的符号都有一个唯一的编号,可以在加载库时使用该编号查找该符号,而不必通过名称查找它。
警告:link_ordinal
只应在符号的序数已知为稳定时使用:如果没有在构建其所在二进制文件时显式设置符号的序数,则将自动分配一个序数,而该分配的序数可能会在二进制文件的不同构建之间发生变化。
#[link(name = "exporter", kind = "raw-dylib")]
extern "stdcall" {
#[link_ordinal(15)]
fn imported_function_stdcall(i: i32);
}
此属性仅与 raw-dylib
链接类型一起使用。使用任何其他类型都会导致编译器错误。
将此属性与 link_name
属性一起使用将导致编译器错误。
函数参数的属性
外部函数参数的属性遵循与 常规函数参数 相同的规则和限制。