refactor: refactor c-friendly ffi design doc
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# Allowed Data Types
|
||||
|
||||
Under this section we discuss which data types are allowed to appear in an FFI
|
||||
interface and how to convert Rust data types into these allowed types. The
|
||||
correspondence between these types and their C/C++ counterparts is covered in
|
||||
[C/C++ Headers](c-cpp-headers.md).
|
||||
|
||||
## Primitive Type Conversion
|
||||
|
||||
This subsection covers the conversion of primitive types. The rules described here
|
||||
apply to both parameter and return value positions.
|
||||
|
||||
### Types Allowed to Cross the Boundary Directly
|
||||
|
||||
The following Rust types are allowed to be passed directly across the FFI boundary:
|
||||
|
||||
- `bool`
|
||||
- `f32`
|
||||
- `f64`
|
||||
- `i8`
|
||||
- `i16`
|
||||
- `i32`
|
||||
- `i64`
|
||||
- `u8`
|
||||
- `u16`
|
||||
- `u32`
|
||||
- `u64`
|
||||
- `usize`
|
||||
- `isize`
|
||||
|
||||
### The u128 and i128 Problem
|
||||
|
||||
The `u128` and `i128` types have very poor support in the C/C++ standard libraries.
|
||||
Therefore they cannot be passed directly across the FFI boundary.
|
||||
|
||||
To transfer data of these types, refer to the later section about passing opaque
|
||||
structs.
|
||||
|
||||
### The char Problem
|
||||
|
||||
The Rust `char` type only holds valid Unicode scalar values. The Rust official
|
||||
documentation explicitly states that the `char` type does not have FFI safety, so it
|
||||
cannot be used as a value passed across the FFI boundary.
|
||||
|
||||
The solution is to use the Rust `u32` type for passing. Under this scheme:
|
||||
|
||||
- The function signature uses `u32` as the parameter type.
|
||||
- For input parameters, use `char::try_from` to perform a fallible conversion that
|
||||
validates the incoming character.
|
||||
- For output parameters, use `u32::from` for the conversion.
|
||||
@@ -0,0 +1,72 @@
|
||||
# C/C++ Headers
|
||||
|
||||
Under this section we discuss how to write the C/C++ header files that accompany the
|
||||
Rust FFI library. The Rust-side counterpart of each topic is covered in
|
||||
[FFI Function Signatures](ffi-function-signatures.md) and
|
||||
[Allowed Data Types](allowed-data-types.md).
|
||||
|
||||
## Function Declaration Style
|
||||
|
||||
FFI functions must be exported under C names, that is, they must not go through the
|
||||
C++ name mangling mechanism. In the generated C header file, you need to use the
|
||||
`extern "C"` specifier, the `__cplusplus` macro and macro conditionals to import the
|
||||
declarations correctly, for example:
|
||||
|
||||
```c++
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
CError WFStartup(void);
|
||||
|
||||
// More FFI functions omitted...
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif // __cplusplus
|
||||
```
|
||||
|
||||
## Required Header Files
|
||||
|
||||
The generated C/C++ header files must include the appropriate header files for the
|
||||
types used in the declarations.
|
||||
|
||||
- In C, the `bool` type is provided by `<stddef.h>`. C++ supports `bool` natively
|
||||
and does not need any header file.
|
||||
- In C, the integer types are provided by `<stdint.h>`. In C++, they are provided
|
||||
by `<cstdint>`.
|
||||
- In C, the `uintptr_t` and `intptr_t` types are provided by `<stdint.h>`. In C++,
|
||||
they are provided by `<cstdint>`.
|
||||
- The floating-point types do not require any header file.
|
||||
|
||||
## Type Correspondences
|
||||
|
||||
The following table shows the correspondence between the Rust types that can cross
|
||||
the FFI boundary directly (listed in [Allowed Data Types](allowed-data-types.md))
|
||||
and their C/C++ counterparts. The left column lists the Rust type and the right
|
||||
column lists the corresponding C/C++ type.
|
||||
|
||||
| Rust | C/C++ |
|
||||
|------|-------|
|
||||
| `bool` | `bool` |
|
||||
| `f32` | `float` |
|
||||
| `f64` | `double` |
|
||||
| `i8` | `int8_t` |
|
||||
| `i16` | `int16_t` |
|
||||
| `i32` | `int32_t` |
|
||||
| `i64` | `int64_t` |
|
||||
| `u8` | `uint8_t` |
|
||||
| `u16` | `uint16_t` |
|
||||
| `u32` | `uint32_t` |
|
||||
| `u64` | `uint64_t` |
|
||||
| `usize` | `uintptr_t` |
|
||||
| `isize` | `intptr_t` |
|
||||
|
||||
## The char Problem
|
||||
|
||||
For the Rust `char` type, the C/C++ header side uses `char32_t` as the parameter
|
||||
type. Note that `char32_t` can hold any 32-bit unsigned integer, which is broader
|
||||
than the set of valid Unicode scalar values carried by the Rust `char` type. The
|
||||
Rust side is responsible for validating the incoming value; see
|
||||
[The char Problem](allowed-data-types.md#the-char-problem) in
|
||||
[Allowed Data Types](allowed-data-types.md).
|
||||
@@ -0,0 +1,94 @@
|
||||
# FFI Function Signatures
|
||||
|
||||
Under this section we discuss the signature requirements of the exported FFI
|
||||
functions.
|
||||
|
||||
## C-style Name Export
|
||||
|
||||
FFI functions must be exported under C names, that is, they must not go through the
|
||||
C++ name mangling mechanism.
|
||||
|
||||
In Rust, you need to decorate the function with the `#[unsafe(no_mangle)]` attribute
|
||||
together with `extern "C"`, for example:
|
||||
|
||||
```rust
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn WFStartup() -> CError {
|
||||
// Function body omitted...
|
||||
}
|
||||
|
||||
// More FFI functions omitted...
|
||||
```
|
||||
|
||||
## Export Name Style
|
||||
|
||||
In this subsection, we discuss the naming style of exported functions.
|
||||
|
||||
In this design, there are exactly two styles to choose from. We call them the
|
||||
Windows style and the POSIX style. Either style can be chosen freely, but within a
|
||||
single library the two styles must not be mixed.
|
||||
|
||||
We recommend the following selection rule: if your library is Windows-only, choose
|
||||
the Windows style; otherwise, choose the POSIX style.
|
||||
|
||||
### Windows Style
|
||||
|
||||
The Windows style format is `<PREFIX><FUNC>` for ordinary functions, and
|
||||
`<PREFIX><STRUCT><FUNC>` for opaque struct functions.
|
||||
|
||||
In the format, `<STRUCT>` is the name of the opaque struct and `<FUNC>` is the name
|
||||
of the function. Both of them must use PascalCase naming.
|
||||
|
||||
For `<PREFIX>`, it is a prefix identifier that is specific to this library. As we
|
||||
all know, C has no namespace concept, and all functions are exported into the same
|
||||
space. Therefore, avoiding name collisions between functions is an important task.
|
||||
By prepending `<PREFIX>` to the function name, name duplication can be avoided as
|
||||
much as possible. For this reason, you need to choose `<PREFIX>` carefully and try
|
||||
to pick a prefix that is unlikely to collide.
|
||||
|
||||
In the Windows style, there are two spellings for `<PREFIX>`. One is the
|
||||
all-uppercase style and the other is the style in which only the first letter is
|
||||
uppercase and all remaining letters are lowercase. Choose according to your own
|
||||
needs. In addition, the length of `<PREFIX>` must not exceed 5 characters, and
|
||||
it must not contain anything other than letters and digits (that is, underscores are
|
||||
not allowed).
|
||||
|
||||
The following examples show some legal and illegal Windows style function names:
|
||||
|
||||
| Name | Verdict | Reason |
|
||||
|------|---------|--------|
|
||||
| `WFStartup` | Legal | An ordinary (non opaque struct) function. |
|
||||
| `WFIconGetHandle` | Legal | An opaque struct function whose `<STRUCT>` is `Icon`. |
|
||||
| `WFWordHandleGetHandle` | Legal | An opaque struct function whose `<STRUCT>` is `WordHandle`. |
|
||||
| `FlGetMessage` | Legal | Uses a `<PREFIX>`, which is `Fl`, that capitalizes only its first letter. |
|
||||
| `MyRustGetMessage` | Illegal | `<PREFIX>`, which is `MyRust`, does not keep all remaining letters lowercase. |
|
||||
| `My_RustGetMessage` | Illegal | `<PREFIX>` contains an underscore. |
|
||||
| `IconGetHandle` | Illegal | Missing `<PREFIX>`. |
|
||||
| `QwertyuiopGetData` | Illegal | `<PREFIX>`, which is `Qwertyuiop`, is too long. |
|
||||
| `WFget_message` | Illegal | `<FUNC>`, which is `get_message`, is not PascalCase. |
|
||||
| `WFicon_handleGetMessage` | Illegal | `<STRUCT>`, which is `icon_handle`, is not PascalCase. |
|
||||
|
||||
### POSIX Style
|
||||
|
||||
The POSIX style format is `<PREFIX>_<FUNC>` for ordinary functions, and
|
||||
`<PREFIX>_<STRUCT>_<FUNC>` for opaque struct functions.
|
||||
|
||||
The `<PREFIX>`, `<STRUCT>` and `<FUNC>` components in the format have the same
|
||||
meaning as in the Windows style, but the format is different. `<STRUCT>` and `<FUNC>`
|
||||
must use snake_case naming. `<PREFIX>` must be all lowercase, and it must not contain
|
||||
anything other than letters and digits (that is, underscores are not allowed). Its
|
||||
length and selection requirements are the same as in the Windows style.
|
||||
|
||||
The following examples show some legal and illegal POSIX style function names:
|
||||
|
||||
| Name | Verdict | Reason |
|
||||
|------|---------|--------|
|
||||
| `wf_startup` | Legal | An ordinary (non opaque struct) function. |
|
||||
| `wf_icon_get_handle` | Legal | An opaque struct function whose `<STRUCT>` is `icon`. |
|
||||
| `wf_word_handle_get_handle` | Legal | An opaque struct function whose `<STRUCT>` is `word_handle`. |
|
||||
| `Fl_get_message` | Illegal | `<PREFIX>`, which is `Fl`, is not all lowercase. |
|
||||
| `my_rust_get_message` | Illegal | `<PREFIX>`, which is `my_rust`, contains an underscore. |
|
||||
| `icon_get_handle` | Illegal | Missing `<PREFIX>`. |
|
||||
| `qwertyuiop_get_data` | Illegal | `<PREFIX>`, which is `qwertyuiop`, is too long. |
|
||||
| `wf_GetMessage` | Illegal | `<FUNC>`, which is `GetMessage`, is not snake_case. |
|
||||
| `wf_Icon_get_message` | Illegal | `<STRUCT>`, which is `Icon`, is not snake_case. |
|
||||
@@ -0,0 +1,13 @@
|
||||
# C-friendly FFI Design
|
||||
|
||||
This document is a guide for authoring Rust libraries that expose a C-friendly FFI
|
||||
and distributing them as ordinary CMake/pkg-config packages. It covers how to design
|
||||
C-friendly FFI signatures, how to pass primitive types, enums, strings and
|
||||
Rust-specific constructs such as `Option` and `Result` across the boundary, and how
|
||||
to write the accompanying C/C++ header files.
|
||||
|
||||
## Contents
|
||||
|
||||
- [FFI Function Signatures](ffi-function-signatures.md)
|
||||
- [Allowed Data Types](allowed-data-types.md)
|
||||
- [C/C++ Headers](c-cpp-headers.md)
|
||||
Reference in New Issue
Block a user