diff options
| author | Leitwolf11 | 2026-10-18 20:00:39 +0300 |
|---|---|---|
| committer | Leitwolf11 | 2026-10-18 20:00:39 +0300 |
| commit | 8d1fb4807edc7920fbecd0554c9c61625827e19c (patch) | |
| tree | dd3d7e1d4f48ca27140a2bd5f970817ad02ae35a /Guide/C-INTERFACES.md | |
| download | progmasoft-cpp-8d1fb4807edc7920fbecd0554c9c61625827e19c.tar.gz | |
Write the first revision of Progmasoft C++ Style
Progmasoft C++ Style is a style guide for C++20, written for anyone
who wants a complete rule set to adopt. It writes down the conventions
the Progmasoft C++ projects already follow (naming, the clang-format
and clang-tidy profiles, exception-free error handling, Doxygen) and
decides what they left open.
Thirteen chapters under Guide/:
1. Principles: what the guide optimizes for and how to decide a case
it does not cover.
2. Formatting: the full .clang-format profile, the reason for each
choice, and the rules the tool cannot apply.
3. Naming: casing for every kind of name, acronyms, choosing words,
namespaces, files.
4. Files and headers: extensions, #pragma once, includes, what goes in
a header, the order inside a class, file size, vendored code.
5. Language: functions, auto, initialization, const and constexpr,
casts, integers, strings, enumerations, templates and concepts,
lambdas, control flow, and the features not used.
6. Types and classes: struct or class, final, construction, the rule of
zero, inheritance, operators, strong types, optional and variant.
7. Errors: no exceptions by default, Expected and Error, invariants,
fatal defects, destructors, diagnostics.
8. Ownership and memory: owners and borrowers, views and lifetimes,
RAII, containers and deterministic iteration, allocation, undefined
behavior.
9. Comments and documentation: Doxygen for public declarations,
comments that explain why, TODOs that name an issue.
10. C interfaces: C11 headers, prefixes, opaque types, ownership and
failure across the boundary, stability.
11. Concurrency: sharing less, threads, locks, thread-safety
annotations, atomics, statics.
12. Testing: unit tests, fuzzing, sanitizers, benchmarks, coverage.
13. Tooling: the compiler flags, the full .clang-tidy profile with the
reason for every disabled check, other checks, version pinning.
The guide prescribes no test, fuzzing or benchmark framework and no
error library; it prescribes how they are used. ADOPTING.md explains
how a project points to the guide, copies the profiles and records its
deviations. CONTRIBUTING.md explains how to propose a change.
The text is licensed under CC BY 4.0. The configuration and code
examples are dedicated to the public domain under CC0 1.0, so projects
can copy them under their own license.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'Guide/C-INTERFACES.md')
| -rw-r--r-- | Guide/C-INTERFACES.md | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/Guide/C-INTERFACES.md b/Guide/C-INTERFACES.md new file mode 100644 index 0000000..3fc7b60 --- /dev/null +++ b/Guide/C-INTERFACES.md @@ -0,0 +1,124 @@ +<!-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> --> +<!-- SPDX-License-Identifier: CC-BY-4.0 --> + +# 10. C interfaces + +A C interface is the stable binary boundary of a library: what another language, another compiler or another version of +the library links against. It is written in C11 and kept small; everything behind it is C++20. + +## 10.1 The header + +```c +// SPDX-FileCopyrightText: 2026 Example <contact@example.com> +// SPDX-License-Identifier: MPL-2.0 +#ifndef EXAMPLE_PARSER_H +#define EXAMPLE_PARSER_H + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/// Version of this interface; bumped on every incompatible change. +#define EXAMPLE_PARSER_ABI_VERSION 3 + +/// Opaque parser state, owned by the library. +typedef struct example_parser example_parser; + +/// Result of an operation of this interface. +typedef enum example_status +{ + EXAMPLE_STATUS_OK = 0, + EXAMPLE_STATUS_INVALID_ARGUMENT = 1, + EXAMPLE_STATUS_MALFORMED_INPUT = 2, +} example_status; + +/// Create a parser. Release it with example_parser_destroy. +/// @param out Receives the parser on success; untouched on failure. +example_status +example_parser_create(example_parser **out); + +/// Release a parser created by example_parser_create. Accepts NULL. +void +example_parser_destroy(example_parser *parser); + +#ifdef __cplusplus +} +#endif + +#endif // EXAMPLE_PARSER_H +``` + +- **`.h`, C11, and include guards.** A C header may be read by a C compiler that does not know `#pragma once`, so it uses + a guard named after the file. +- **Every name has the project prefix**: functions and types `example_`, macros and enumerators `EXAMPLE_`. C has one + namespace for the whole program. +- **`extern "C"`** guards wrap the declarations, so C++ callers link against the same symbols. +- **Opaque types**: callers see a forward-declared struct and a pointer, never the layout. The layout can then change + without breaking callers. +- **Fixed-width types** (`uint32_t`, `size_t` for sizes) and no `bool` in a struct whose layout is part of the ABI; use + `uint8_t` with documented values. +- **A version macro or a version function**, so a caller can check at build time or at run time that it talks to the + interface it was written for. + +## 10.2 Ownership across the boundary + +- **Whoever allocates, frees.** A buffer the library returns is released by a function of the library, never by the + caller's `free`, because the two may not share an allocator. +- **Borrowed data is copied before the call returns** when the library needs it longer. A callback that receives a + pointer and a length may use them only until it returns; the library copies anything it keeps into memory it owns. +- **Document every pointer parameter**: may it be null, who owns it after the call, how long it must stay valid. +- **No C++ type crosses the boundary**: no `std::string`, no reference, no class with a constructor, no template. + +## 10.3 Failure across the boundary + +- **No exception crosses a C interface**, ever. With exceptions disabled this holds by construction; a project that + enables them catches everything at the boundary. +- **Every fallible function returns a status** and writes its results through out-parameters, which it leaves untouched + on failure. A function that cannot fail returns `void` or the value. +- **Detailed messages** are available through a separate call or an error object the caller owns and releases, not + through a global "last error" that breaks with threads. + +## 10.4 The C++ side + +Behind the interface, the implementation is ordinary C++20 that follows the rest of this guide: + +```cpp +extern "C" auto +example_parser_create(example_parser **out) -> example_status +{ + if (out == nullptr) + { + return EXAMPLE_STATUS_INVALID_ARGUMENT; + } + auto parser = std::make_unique<example_parser>(); + *out = parser.release(); + return EXAMPLE_STATUS_OK; +} +``` + +C++ callers wrap a C handle in an RAII type right away: + +```cpp +struct ParserDeleter final +{ + void + operator()(example_parser *parser) const noexcept + { + example_parser_destroy(parser); + } +}; + +using ParserHandle = std::unique_ptr<example_parser, ParserDeleter>; +``` + +## 10.5 Keeping it stable + +- **Record the interface in one place**: a manifest or a header that is the single source of truth for every symbol, + struct layout and magic number, checked in CI against the code. +- **Add, do not change.** A new function or a new enumerator is compatible; changing a signature, a layout or the meaning + of a value is not, and bumps the interface version. +- **Test the interface from C**: a test translation unit compiled as C11 includes the header and calls every function, + so that a C++-only construct in the header cannot slip in. |