Keep the library C++-first and easy to consume through FFI: flat exported functions, C linkage, simple structs and caller-owned buffers. C-compatible headers are not required. These examples illustrate the proposed API shape.
Export and calling-convention macros
Replace MODULE_API with two library-owned macros:
CPP_CORE_API: C linkage plus platform-specific export/import visibility.
CPP_CORE_CALL: The calling convention used by exported functions and callbacks. FFI declarations must match it. Explicit __cdecl matters particularly on Windows x86; other supported targets use their platform convention.
Proposed definitions in module_api.h:
#if defined(_WIN32) || defined(__CYGWIN__)
#define CPP_CORE_CALL __cdecl
#if defined(CPP_CORE_BUILDING_LIBRARY)
#define CPP_CORE_API extern "C" __declspec(dllexport)
#else
#define CPP_CORE_API extern "C" __declspec(dllimport)
#endif
#else
#define CPP_CORE_CALL
#define CPP_CORE_API \
extern "C" __attribute__((visibility("default")))
#endif
Platform implementations define CPP_CORE_BUILDING_LIBRARY privately when building their shared library. Consumers leave it undefined. This removes the dependency on cpp_bindings_windows_EXPORTS.
CPP_CORE_CALL also belongs on callback pointer types and callback implementations; those do not need CPP_CORE_API merely to be passed by pointer.
Consistent types and results
Use one handle type and one status type throughout. Preserve useful value-or-negative-error returns; separate status from values where necessary.
using SerialHandle = std::int64_t;
using SerialStatus = std::int32_t;
// >= 0: bytes read, < 0: error.
CPP_CORE_API std::int32_t CPP_CORE_CALL serialRead(
SerialHandle handle,
std::uint8_t* buffer,
std::int32_t buffer_size,
const cpp_core::SerialTimeoutConfig* timeout,
ErrorCallbackT error_callback = nullptr);
// Write out_parity only on success; do not encode errors as enum values.
CPP_CORE_API SerialStatus CPP_CORE_CALL serialGetParity(
SerialHandle handle,
cpp_core::Parity* out_parity,
ErrorCallbackT error_callback = nullptr);
Apply the status type and calling convention to ErrorCallbackT too. Keep failures directly available to callers and avoid lossy status conversions.
Callbacks with context and predictable lifetime
Build on #25: include port identification in event data and add a separate caller-owned context pointer.
using SerialEventCallback = void (CPP_CORE_CALL*)(
const cpp_core::SerialEvent* event,
void* user_data);
CPP_CORE_API SerialStatus CPP_CORE_CALL serialSetEventListener(
SerialEventCallback callback,
void* user_data);
void CPP_CORE_CALL onEvent(
const cpp_core::SerialEvent* event,
void* user_data);
// Register:
serialSetEventListener(onEvent, &application_state);
// Unregister before destroying application_state:
serialSetEventListener(nullptr, nullptr);
Specify callback threads, reentrancy and payload lifetime. Successful unregistration from outside a callback must leave no callbacks running or pending for that registration. Define safe behavior for unregistration from inside a callback.
Explicit ABI evolution
Separate ABI version from build metadata. Make extensible structures size-aware.
CPP_CORE_API std::uint32_t CPP_CORE_CALL serialAbiVersion();
CPP_CORE_API SerialStatus CPP_CORE_CALL meta(
cpp_core::Meta* out,
std::uint32_t out_size);
cpp_core::Meta info{};
const auto status = meta(&info, sizeof(info));
Never write beyond the supplied size. Define supported older layouts and errors for incompatible sizes. Apply the same principle to extensible input configurations.
Remaining changes and validation
- Define total/inter-byte timeouts, cancellation and partial transfers. Support bounded drain waits and distinguish terminator-found from timeout or buffer-full.
- Retain strong types and
Result<T>. Optional RAII/std::span wrappers remain above the ABI; no STL objects or exceptions cross it.
- Fix lvalue support in
forwardUnexpected, overflow in belongsTo(INT64_MIN) and lossy status conversions.
- Add helper regression tests and Linux/Windows checks for exports, layouts, version/size compatibility and callback lifetime. Include minimal FFI smoke tests.
Keep direct FFI calls simple and retain the C++26 baseline. Coordinate breaking changes across platform implementations and bindings.
Keep the library C++-first and easy to consume through FFI: flat exported functions, C linkage, simple structs and caller-owned buffers. C-compatible headers are not required. These examples illustrate the proposed API shape.
Export and calling-convention macros
Replace
MODULE_APIwith two library-owned macros:CPP_CORE_API: C linkage plus platform-specific export/import visibility.CPP_CORE_CALL: The calling convention used by exported functions and callbacks. FFI declarations must match it. Explicit__cdeclmatters particularly on Windows x86; other supported targets use their platform convention.Proposed definitions in
module_api.h:Platform implementations define
CPP_CORE_BUILDING_LIBRARYprivately when building their shared library. Consumers leave it undefined. This removes the dependency oncpp_bindings_windows_EXPORTS.CPP_CORE_CALLalso belongs on callback pointer types and callback implementations; those do not needCPP_CORE_APImerely to be passed by pointer.Consistent types and results
Use one handle type and one status type throughout. Preserve useful value-or-negative-error returns; separate status from values where necessary.
Apply the status type and calling convention to
ErrorCallbackTtoo. Keep failures directly available to callers and avoid lossy status conversions.Callbacks with context and predictable lifetime
Build on #25: include port identification in event data and add a separate caller-owned context pointer.
Specify callback threads, reentrancy and payload lifetime. Successful unregistration from outside a callback must leave no callbacks running or pending for that registration. Define safe behavior for unregistration from inside a callback.
Explicit ABI evolution
Separate ABI version from build metadata. Make extensible structures size-aware.
Never write beyond the supplied size. Define supported older layouts and errors for incompatible sizes. Apply the same principle to extensible input configurations.
Remaining changes and validation
Result<T>. Optional RAII/std::spanwrappers remain above the ABI; no STL objects or exceptions cross it.forwardUnexpected, overflow inbelongsTo(INT64_MIN)and lossy status conversions.Keep direct FFI calls simple and retain the C++26 baseline. Coordinate breaking changes across platform implementations and bindings.