外部块
语法
ExternBlock :
unsafe
?extern
Abi?{
InnerAttribute*
ExternalItem*
}
ExternalItem :
OuterAttribute* (
MacroInvocationSemi
| ( Visibility? ( StaticItem | Function ) )
)
外部块提供当前包中未*定义*的项目的*声明*,并且是 Rust 外部函数接口的基础。这些类似于未经检查的导入。
外部块中允许两种项目*声明*:函数和静态变量。调用函数或访问在外部块中声明的静态变量只允许在 unsafe
上下文中进行。
语法上允许 unsafe
关键字出现在 extern
关键字之前,但在语义级别上会被拒绝。这允许宏在从标记流中删除 unsafe
关键字之前使用它。
函数
外部块中的函数声明方式与其他 Rust 函数相同,但它们不能有函数体,而是以分号结尾。参数中不允许使用模式,只能使用 标识符 或 _
。不允许使用函数限定符(const
、async
、unsafe
和 extern
)。
Rust 代码可以调用外部块中声明的函数,就像调用 Rust 中定义的函数一样。Rust 编译器会在 Rust ABI 和外部 ABI 之间自动转换。
在 extern 块中声明的函数隐式地是 unsafe
的。当强制转换为函数指针时,在 extern 块中声明的函数的类型为 unsafe extern "abi" for<'l1, ..., 'lm> fn(A1, ..., An) -> R
,其中 'l1
、... 'lm
是其生命周期参数,A1
、... An
是其参数的声明类型,R
是其声明的返回类型。
静态变量
外部块中的静态变量声明方式与外部块外的 静态变量 相同,只是它们没有初始化其值的表达式。访问 extern 块中声明的静态项是 unsafe
的,无论它是否可变,因为不能保证静态变量内存中的位模式对其声明的类型有效,因为某些任意(例如 C)代码负责初始化静态变量。
与外部块外的 静态变量 一样,外部静态变量可以是不可变的,也可以是可变的。不可变静态变量*必须*在执行任何 Rust 代码之前初始化。仅仅在 Rust 代码从静态变量读取之前初始化它是不够的。
ABI
默认情况下,外部块假定它们调用的库在特定平台上使用标准 C ABI。可以使用 abi
字符串指定其他 ABI,如下所示
#![allow(unused)] fn main() { // Interface to the Windows API extern "stdcall" { } }
有三个跨平台的 ABI 字符串,所有编译器都保证支持
extern "Rust"
-- 在任何 Rust 代码中编写普通的fn foo()
时的默认 ABI。extern "C"
-- 这与extern fn foo()
相同;无论您的 C 编译器支持什么默认值。extern "system"
-- 通常与extern "C"
相同,但在 Win32 上除外,在这种情况下它是"stdcall"
,或者您应该用来链接到 Windows API 本身的内容
还有一些特定于平台的 ABI 字符串
extern "cdecl"
-- x86_32 C 代码的默认值。extern "stdcall"
-- x86_32 上 Win32 API 的默认值。extern "win64"
-- x86_64 Windows 上 C 代码的默认值。extern "sysv64"
-- 非 Windows x86_64 上 C 代码的默认值。extern "aapcs"
-- ARM 的默认值。extern "fastcall"
--fastcall
ABI -- 对应于 MSVC 的__fastcall
以及 GCC 和 clang 的__attribute__((fastcall))
extern "vectorcall"
--vectorcall
ABI -- 对应于 MSVC 的__vectorcall
和 clang 的__attribute__((vectorcall))
extern "thiscall"
-- MSVC 上 C++ 成员函数的默认值 -- 对应于 MSVC 的__thiscall
以及 GCC 和 clang 的__attribute__((thiscall))
extern "efiapi"
-- 用于 UEFI 函数的 ABI。
可变参数函数
通过指定 ...
作为最后一个参数,外部块中的函数可以是可变参数的。可变参数之前必须至少有一个参数。可变参数可以选择使用标识符指定。
#![allow(unused)] fn main() { extern "C" { fn foo(x: i32, ...); fn with_name(format: *const u8, args: ...); } }
extern 块上的属性
以下 属性 控制 extern 块的行为。
link
属性
link
属性指定编译器应为 extern
块中的项目链接的本机库的名称。它使用 MetaListNameValueStr 语法来指定其输入。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 {
// …
}
在空的 extern 块上添加 link
属性是有效的。您可以使用它来满足代码中其他位置(包括上游 crates)的 extern 块的链接要求,而不是将属性添加到每个 extern 块中。
链接修饰符:bundle
此修饰符仅与 static
链接类型兼容。使用任何其他类型将导致编译错误。
在构建 rlib 或 staticlib 时,+bundle
表示原生静态库将被打包到 rlib 或 staticlib 存档中,然后在最终二进制文件链接期间从那里检索。
在构建 rlib 时,-bundle
表示原生静态库“按名称”注册为该 rlib 的依赖项,并且只有在最终二进制文件链接期间才包含来自它的目标文件,在最终链接期间也会执行按该名称的文件搜索。
在构建 staticlib 时,-bundle
表示原生静态库根本不包含在存档中,一些更高级别的构建系统需要在最终二进制文件链接期间稍后添加它。
此修饰符在构建其他目标(如可执行文件或动态库)时无效。
此修饰符的默认值为 +bundle
。
有关此修饰符的更多实现细节,请参阅 bundle
的 rustc 文档。
链接修饰符:whole-archive
此修饰符仅与 static
链接类型兼容。使用任何其他类型将导致编译错误。
+whole-archive
表示静态库作为整个存档链接,不会丢弃任何目标文件。
此修饰符的默认值为 -whole-archive
。
有关此修饰符的更多实现细节,请参阅 whole-archive
的 rustc 文档。
链接修饰符:verbatim
此修饰符与所有链接类型兼容。
+verbatim
表示 rustc 本身不会向库名称添加任何特定于目标的库前缀或后缀(如 lib
或 .a
),并将尽力向链接器请求相同的内容。
-verbatim
表示 rustc 将在将库名称传递给链接器之前向其添加特定于目标的前缀和后缀,或者不会阻止链接器隐式添加它。
此修饰符的默认值为 -verbatim
。
有关此修饰符的更多实现细节,请参阅 verbatim
的 rustc 文档。
dylib
与 raw-dylib
在 Windows 上,链接到动态库需要向链接器提供导入库:这是一个特殊的静态库,它以链接器知道它们必须在运行时动态加载的方式声明动态库导出的所有符号。
指定 kind = "dylib"
会指示 Rust 编译器根据 name
键链接导入库。然后,链接器将使用其正常的库解析逻辑来查找该导入库。或者,指定 kind = "raw-dylib"
会指示编译器在编译期间生成导入库,并将该导入库提供给链接器。
raw-dylib
仅在 Windows 上受支持。在面向其他平台时使用它将导致编译错误。
import_name_type
键
在 x86 Windows 上,函数的名称会被“修饰”(即添加特定的前缀和/或后缀)以指示其调用约定。例如,名为 fn1
且没有参数的 stdcall
调用约定函数将被修饰为 _fn1@0
。但是,PE 格式 也允许名称没有前缀或未修饰。此外,MSVC 和 GNU 工具链对相同的调用约定使用不同的修饰,这意味着默认情况下,某些 Win32 函数无法使用 GNU 工具链通过 raw-dylib
链接类型调用。
为了允许这些差异,在使用 raw-dylib
链接类型时,您还可以使用以下值之一指定 import_name_type
键,以更改函数在生成的导入库中的命名方式
decorated
:函数名称将使用 MSVC 工具链格式进行完全修饰。noprefix
:函数名称将使用 MSVC 工具链格式进行修饰,但会跳过前导?
、@
或可选的_
。undecorated
:函数名称将不会被修饰。
如果未指定 import_name_type
键,则函数名称将使用目标工具链的格式进行完全修饰。
变量从不进行修饰,因此 import_name_type
键对它们在生成的导入库中的命名方式没有影响。
import_name_type
键仅在 x86 Windows 上受支持。在面向其他平台时使用它将导致编译错误。
link_name
属性
link_name
属性 可以在 extern
块内的声明上指定,以指示要为给定函数或静态变量导入的符号。它使用 MetaNameValueStr 语法来指定符号的名称。
#![allow(unused)] fn main() { extern { #[link_name = "actual_symbol_name"] fn name_in_rust(); } }
将此属性与 link_ordinal
属性一起使用将导致编译错误。
link_ordinal
属性
link_ordinal
属性 可以应用于 extern
块内的声明,以指示在生成要链接的导入库时要使用的数字序号。序号是 Windows 上动态库导出的每个符号的唯一编号,并且可以在加载库时使用该序号来查找该符号,而不必按名称查找。
警告:link_ordinal
仅应在已知符号的序号稳定的情况下使用:如果在构建包含符号的二进制文件时未显式设置符号的序号,则会自动为其分配一个序号,并且分配的序号在二进制文件的构建之间可能会发生变化。
#[link(name = "exporter", kind = "raw-dylib")]
extern "stdcall" {
#[link_ordinal(15)]
fn imported_function_stdcall(i: i32);
}
此属性仅与 raw-dylib
链接类型一起使用。使用任何其他类型将导致编译错误。
将此属性与 link_name
属性一起使用将导致编译错误。
函数参数上的属性
extern 函数参数上的属性遵循与 常规函数参数 相同的规则和限制。