Architecture
This content is for v1. Switch to the latest version for up-to-date documentation.
此内容尚不支持你的语言。
Layered Architecture
Section titled “Layered Architecture”Dart / Flutter App └─ dart_cpp_bridge Dart package (DartCppBridge, codec, FFI bindings) └─ FFI binary frames (wire protocol)Native library ├─ Runtime (asio io_context + AsioExecutor + thread_pool) ├─ Session registry (per-Isolate reply port, DartFn closures) ├─ Wire dispatch (frame routing, method_id → user code) ├─ Codec (ByteReader/Writer, frame + payload encoding/decoding) ├─ Channel / ForeignExecutor / Cbridge adapters └─ User code (BRIDGE_SYNC / BRIDGE_ASYNC / BRIDGE_NORMAL functions)The code generator dcb_gen_tool scans BRIDGE_* markers in user headers and generates wire dispatch, Dart FFI bindings, and the Dart API layer.
Core Components
Section titled “Core Components”Runtime
Section titled “Runtime”Process-wide singleton managing:
asio::io_contextsingle-threaded event loopdcb::AsioExecutorscheduling async-simple coroutines ontoio_contextasio::thread_poolblocking work pool- Session registry
Business code can use the utilities Runtime provides directly: spawn, spawn_blocking, spawn_detached, channel, sleep, etc. See Basic Runtime.
Session
Section titled “Session”Each Dart Isolate that calls DartCppBridge.init() gets one Session:
- A dedicated long-lived reply port
- A DartFn closure registry (
fn_id→ closure) - A generation counter; late messages arriving after
dispose()are discarded - Lifecycle managed by
NativeFinalizer; automatically closed when the Dart object becomes unreachable or the isolate exits
Wire Dispatch
Section titled “Wire Dispatch”Generated by dcb_gen_tool or written by hand. It is responsible for:
- Receiving binary frames from Dart and validating
magic/version/ payload length - Routing by
method_idto the user C++ function - Catching
std::exceptionthrown by user code and encoding it as aresponseErrframe returned to Dart - Forwarding sync / async / stream / DartFn calls to the appropriate handling path
The low-level binary frame format is documented in Wire Protocol. ByteReader / ByteWriter handle encoding/decoding of primitive types, strings, containers, optionals, and data classes inside the payload. Both sides must read and write in exactly the same order.
ObjectHandleRegistry (Opaque Classes)
Section titled “ObjectHandleRegistry (Opaque Classes)”C++ objects marked with BRIDGE_OPAQUE are passed by handle: each instance is assigned a handle and stored in a per-Session ObjectHandleRegistry. Dart invokes instance methods through the handle, and Dart GC calls dcb_drop_object via NativeFinalizer to release the C++ object.
Call Flow
Section titled “Call Flow”Sync Call (BRIDGE_SYNC)
Section titled “Sync Call (BRIDGE_SYNC)”Dart function call → FFI dcb_invokeSyncMethod → wire dispatch decodes arguments → user C++ function (runs synchronously on the io_context thread, must not block) → encodes return value → responseOk frame → Dart returns synchronouslyAsync Call (BRIDGE_ASYNC)
Section titled “Async Call (BRIDGE_ASYNC)”Dart Future call → FFI dcb_invokeAsyncMethod → wire dispatch decodes arguments → starts an async_simple::Lazy<T> coroutine on the io_context thread → coroutine can co_await and suspend (no thread is held) → coroutine completes → responseOk / responseErr frame posted to the Dart reply port → Dart Completer completesNormal Call (BRIDGE_NORMAL)
Section titled “Normal Call (BRIDGE_NORMAL)”Dart Future call → FFI dcb_invokeNormalMethod → wire dispatch posts the blocking task to the thread_pool → user function runs on the thread pool (may block) → encodes result → responseOk / responseErr frame posted to the Dart reply port → Dart Completer completesStream
Section titled “Stream”Dart subscribe → FFI creates a C++ StreamSink → C++ side sink.add(item) sends a streamData frame → Dart StreamController receives the data → C++ side sink.end() sends a streamEnd frame → Dart Stream endsUnsubscription only stops receiving on the Dart side; the C++ side continues to run, and subsequent add() calls are silently dropped.
DartFn Reverse Call
Section titled “DartFn Reverse Call”Dart passes a closure as an argument to C++ → C++ stores the fn_id → when needed, C++ sends a DartFnCall frame to Dart → Dart executes the closure → Dart sends a DartFnReply frame → C++ oneshot channel completes → waiting coroutine resumesco_await callback(args) truly suspends on the io thread without occupying the thread pool.
Threading Model
Section titled “Threading Model”- io_context thread: event loop, coroutine scheduling, Dart frame send/receive, DartFn callback triggering
- thread_pool: blocking business logic for
BRIDGE_NORMALandspawn_blocking - External runtime threads: non-asio event loops integrated via the
ForeignExecutoradapter - Dart Isolate thread: Dart code execution and Dart closure execution context
Prefer co::oneshot / co::mpsc channels for cross-thread / cross-runtime communication instead of raw locks + condition variables.
External Runtimes and Pure C Integration
Section titled “External Runtimes and Pure C Integration”The bridge supports integrating non-asio event loops (libuv, glib, custom loops, etc.) into the coroutine system through the ForeignExecutor adapter, enabling cross-runtime non-blocking communication and Dart callback invocation. See Foreign Runtime Integration.
For pure C code or scenarios that do not depend on async-simple, the Pure C Bridge API is provided (callback style, zero C++ dependency).
Code Generation Output
Section titled “Code Generation Output”For a typical dcb_gen_tool project, generated artifacts include:
native/generated/wire_dispatch.{hpp,cpp}— routing and codeclib/src/native_gen/dcb_bindings.dart— FFI function signatureslib/src/native_gen/api/{api}.dart— top-level function entry pointslib/src/native_gen/dcb_generated.dart— internal implementation (impl + singleton)
Business logic remains in user-written .cpp files; the generated layer is only responsible for routing Dart calls to the correct C++ functions.