diff --git a/assets/c-friendly-ffi-design/c-cpp-headers.md b/assets/c-friendly-ffi-design/c-cpp-headers.md index c6fabc9..1fd575d 100644 --- a/assets/c-friendly-ffi-design/c-cpp-headers.md +++ b/assets/c-friendly-ffi-design/c-cpp-headers.md @@ -26,6 +26,19 @@ CError WFStartup(void); #endif // __cplusplus ``` +## 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 diff --git a/assets/c-friendly-ffi-design/ffi-function-signatures.md b/assets/c-friendly-ffi-design/ffi-function-signatures.md index 1c771e1..cb83ad3 100644 --- a/assets/c-friendly-ffi-design/ffi-function-signatures.md +++ b/assets/c-friendly-ffi-design/ffi-function-signatures.md @@ -92,3 +92,78 @@ The following examples show some legal and illegal POSIX style function names: | `qwertyuiop_get_data` | Illegal | ``, which is `qwertyuiop`, is too long. | | `wf_GetMessage` | Illegal | ``, which is `GetMessage`, is not snake_case. | | `wf_Icon_get_message` | Illegal | ``, which is `Icon`, is not snake_case. | + +## Parameters and Return Value + +Under this section we discuss the parameters and return value of the exported +functions. A standard FFI function looks like the following on the Rust side: + +```rust +#[unsafe(no_mangle)] +pub extern "C" fn FBAdd( + in_a: in_param_ty!(u32), + in_b: in_param_ty!(u32), + out_rv: out_param_ty!(u32), + out_overflow: out_param_ty!(bool), +) -> CError { + // Function body omitted... +} +``` + +As shown in the example, this function has four parameters. The first two are input +parameters and the last two are output parameters. The return value of the function +is `CError`. + +### Parameter Rules + +To follow the best practice of C for the signature of functions with any number of +input parameters and output parameters, we must design the function signature this +way. To implement such a function that accepts any number of inputs and outputs, we +need to put both the input parameters and the output parameters into the parameter +list. All input parameters must precede any output parameter. The return value, +`CError`, is used only to indicate whether the function succeeded or failed. + +The naming style of the function parameters (both input and output) must be +snake_case and must not start with an underscore. + +#### Input Parameters + +Input parameters are passed directly by value (for example, integers, floats or +pointers). In the example, `in_a` and `in_b` are both of type `u32`. You can use +the `in_param_ty!` macro provided by the crate to conveniently mark this. + +By convention, input parameters are prefixed with `in_`, but this is not enforced. + +#### Output Parameters + +Output parameters are passed as pointers. In the example, `out_rv` is actually of +type `*mut u32` and `out_overflow` is actually of type `*mut bool`. To avoid +repeatedly writing these pointer types and to avoid writing them incorrectly, you +can use the `out_param_ty!` macro provided by the crate, which is symmetric with +`in_param_ty!`. + +At the same time, you can use the `deref_out_param!` macro provided by the crate to +conveniently dereference an output parameter for assignment, or directly use the +`set_out_param!` macro provided by the crate for assignment. But you usually do not +need to do that, because in later chapters you will be guided on how to correctly +use `cffi_wrapper!` to maximize the efficiency of writing FFI functions. + +By convention, output parameters are prefixed with `out_`, but this is not enforced. + +### The Return Value + +The return value of an FFI function must be of the `CError` type. + +`CError` is defined in `last_error::CError` as a plain `u32`. It is not an enum with +a fixed set of members. Instead, `CERROR_OK` is the single value that represents +absolute, unambiguous success. Every other value's meaning is decided by the +consuming project: it may denote a failure, or, like some Win32 functions, a +partial success. + +#### Migrating from a bool Return Value + +Projects that previously returned a plain `bool` can adopt the error-code scheme +with minimal change: define exactly one non-success code, for example +`const CERROR_FAIL: CError = 1;`, and map every error type to it. The result is +equivalent to a `bool`: `CERROR_OK` for success and `CERROR_FAIL` for any failure, +while leaving room to introduce finer-grained codes later.