Files
sarasacw-omrf/assets/c-friendly-ffi-design/c-cpp-side/c-header.md
T

105 lines
3.7 KiB
Markdown
Raw Normal View History

2026-08-11 22:47:50 +08:00
# C Header
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).
2026-08-13 21:40:16 +08:00
## Include Guard
When writing the include guard of a C header file, we require using both the modern `#pragma once` approach and the traditional approach based on the `#ifndef`, `#define` and `#endif` preprocessing directives. The file should be written like this:
```c
#pragma once
#ifndef SOME_HEADER_H_
#define SOME_HEADER_H_
// The actual content of the header file...
#endif // SOME_HEADER_H_
```
The `SOME_HEADER` prefix is usually related to the name of the header file. It must consist of all uppercase characters, but underscores are allowed. The `_H_` suffix is fixed and cannot be changed; this suffix style originates from the Linux kernel.
For example, when the header file is named `wfassoc.h`, the macro name would be `WFASSOC_H_`.
However, if the name of the header file is too simple and may collide with other header files, it would be better to prepend something to the prefix to remove this ambiguity. For example, for a project called MyRust with a header file named `utils.h`, a good macro name would be `MY_RUST_UTILS_H_`.
## 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
```
2026-08-09 22:12:10 +08:00
## Parameter Declarations
A declaration of an exported FFI function in the C header file generally looks like
the following:
```c++
CError FBAdd(
OMRF_IN_PARAM_TY(uint32_t, in_a),
OMRF_IN_PARAM_TY(uint32_t, in_b),
OMRF_OUT_PARAM_TY(uint32_t, out_rv),
OMRF_OUT_PARAM_TY(bool, out_overflow));
```
## 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).