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

3.7 KiB

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 and Allowed Data Types.

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:

#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:

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

CError WFStartup(void);

// More FFI functions omitted...

#ifdef __cplusplus
}  // extern "C"
#endif  // __cplusplus

Parameter Declarations

A declaration of an exported FFI function in the C header file generally looks like the following:

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) 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 in Allowed Data Types.