在 C/C++ 程序中嵌入 Dart VM:Dart Engine API 加载与调用 Snapshot 完整实战指南
在 C/C++ 程序中嵌入 Dart VM:Dart Engine API 加载与调用 Snapshot 完整实战指南
Dart Engine API(dart_engine.h)是 Dart SDK 为 C/C++ 宿主程序提供的一套轻量级嵌入接口,它允许在非 Dart 程序中直接加载 Kernel 或 AOT 快照、创建多个 Isolate 并调用其中的 Dart 函数,从而把 Dart 快照当作"共享库"来复用。本文以 runtime/engine/README.md 为主线,结合头文件与底层实现源码,系统讲解该 API 的定位、全部公开函数、消息调度与 Isolate 锁机制,并通过 samples/embedder 中的可运行示例给出从构建到调用的完整实战路径。
Dart Engine API 是什么
Dart Engine API 位于 runtime/engine/include/dart_engine.h,它提供了一系列额外的函数,用于嵌入 Dart VM,以及从 Kernel 快照和 AOT 快照中调用 Dart 函数。它的设计目标很明确:
- 服务对象:需要在非 Dart 程序中复用既有 Dart 代码的宿主应用(例如用 C++ 写主程序、用 Dart 写业务模块的场景);
- 使用方式:把 Dart 快照当作动态共享库——宿主进程可以基于一个或多个快照启动一个或多个 Isolate,并调用其中的 Dart 函数;
- 定位:它不是一套完整的、功能齐全的 API,必须与
dart_api.h配合使用。例如调用 Dart 函数、操作Dart_Handle、管理 Scope 等仍然依赖dart_api.h的标准接口。
与直接使用 dart_api.h 相比,Engine API 主要带来三方面改进(官方 README 原文要点):
- 完整的 VM 初始化:包括核心库(core libraries)的初始化,使得
print等基础能力开箱即用; - 更简单的 Isolate 消息处理:封装了 isolate 与 scope 的管理,并提供可按 Isolate 定制、也可全局默认的消息调度器;
- 锁保护的进入/退出 Isolate:
DartEngine_AcquireIsolate会阻塞等待直到可以安全进入 Isolate,避免多线程并发进入同一 Isolate 时崩溃。
核心 API 全景图
全部公开函数都在 runtime/engine/include/dart_engine.h 中声明,并通过 runtime/engine/dart_engine_impl.cc 导出实现。下表是完整的 API 清单:
| 函数 / 类型 | 作用 |
|---|---|
DartEngine_Init(char** error) |
初始化 Dart Engine(初始化 embedder 并部分初始化 VM),成功返回 true,失败时 error 携带错误描述(调用者负责释放) |
DartEngine_Shutdown() |
关闭引擎,停止所有 Isolate 并释放所有资源 |
DartEngine_SnapshotKind |
快照种类枚举:DartEngine_SnapshotKind_Kernel 或 DartEngine_SnapshotKind_AOT |
DartEngine_SnapshotData |
快照数据描述结构,包含 script_uri、kind 及联合体(Kernel 缓冲区或 AOT 的 data/text 指针),足以创建一个 Isolate |
DartEngine_CreateIsolate(snapshot_data, char** error) |
根据快照创建并启动一个新的 Isolate,成功返回 Dart_Isolate,失败返回 NULL |
DartEngine_AcquireIsolate(isolate) |
阻塞等待直到 Isolate 可进入,然后进入该 Isolate;Dart_EnterIsolate 的安全替代 |
DartEngine_ReleaseIsolate() |
退出当前 Isolate 并使其可被其他线程进入;Dart_ExitIsolate 的配套替代 |
DartEngine_HandleMessage(isolate) |
为指定 Isolate 处理一条消息,封装了 isolate 与 scope 的进出管理 |
DartEngine_MessageScheduler |
消息调度器结构体:schedule_callback(调度回调)+ context(透传上下文) |
DartEngine_SetDefaultMessageScheduler(scheduler) |
设置所有 Isolate 共享的默认消息调度器 |
DartEngine_SetMessageScheduler(scheduler, isolate) |
为单个 Isolate 设置专属消息调度器(优先于默认调度器) |
DartEngine_HandleMessageErrorCallback |
消息处理出错时的回调类型(携带 Dart_Handle error 与目标 Isolate) |
DartEngine_SetHandleMessageErrorCallback(callback) |
设置消息处理错误的回调 |
DartEngine_DrainMicrotasksQueue() |
排空当前活跃 Isolate 的微任务队列,要求已进入 Isolate |
DartEngine_KernelFromFile(path, char** error) |
从文件加载 Kernel 快照,返回 kind 为 Kernel 的 SnapshotData |
DartEngine_AotSnapshotFromFile(path, char** error) |
从文件加载 AOT 快照(作为动态库打开),返回 kind 为 AOT 的 SnapshotData |
其中 DartEngine_KernelFromFile 与 DartEngine_AotSnapshotFromFile 的文档均特别说明:用户不应释放返回的 uri 和缓冲区——它们由引擎内部持有并统一在关闭时释放(详见下文"内部实现原理")。
生命周期管理:初始化与关闭
初始化
DartEngine_Init 是使用 API 前的必要步骤。从 dart_engine_impl.cc 可以看到它委托给单例 Engine::instance()->Initialize(error)。结合 engine.cc 的实现,初始化做了两件事:
- 调用
dart::embedder::InitOnce完成 embedder 的一次性初始化; - 通过
Dart_SetVMFlags设置 VM 标志——当运行在预编译(AOT)运行时中时,会自动追加--precompilation标志。
值得注意的关键设计:Initialize 只是"部分初始化"VM。完整的 VM 初始化(Dart_Initialize)被推迟到用户创建第一个 Isolate 时才发生。原因在 engine.h 的注释中写得很清楚:在 AOT 模式下,VM snapshot 数据只有在用户加载 AOT 快照(启动第一个 Isolate)之后才能拿到,因此无法提前完成完整初始化。这一点在 engine.cc 中由 first_isolate_started_ 标志保证只执行一次。
关闭
DartEngine_Shutdown 会:
- 将
is_running_置为false; - 遍历所有 Isolate,依次加锁进入、清除
Dart_SetMessageNotifyCallback(nullptr)、删除持久化句柄、调用Dart_ShutdownIsolate,再解锁(engine.cc); - 释放引擎持有的快照缓冲区:Kernel 缓冲区被
free,AOT 缓冲区则注释明确"无需释放"(因为它们归动态库所有),并释放每个快照的script_uri(engine.cc)。
需要注意的是,engine.h 的注释强调:引擎一旦关闭,不应当被重新初始化。
快照数据模型:Kernel 与 AOT
数据结构
DartEngine_SnapshotData 是贯穿整个 API 的核心结构(dart_engine.h):
typedef struct DartEngine_SnapshotData {
const char* script_uri; // 快照唯一标识,会传给 Dart_CreateIsolateGroup(FromKernel)
DartEngine_SnapshotKind kind; // Kernel 或 AOT
union {
struct { // Kernel 模式
const uint8_t* kernel_buffer;
intptr_t kernel_buffer_size;
};
struct { // AOT 模式
const uint8_t* snapshot_data;
const uint8_t* snapshot_text;
};
};
} DartEngine_SnapshotData;
script_uri 是快照的唯一标识,在创建 Isolate 时会作为 Dart_CreateIsolateGroup 或 Dart_CreateIsolateGroupFromKernel 的参数传入。
从文件加载
Kernel 快照加载(engine.cc):DartEngine_KernelFromFile 的实现非常直白——把整个文件读入内存缓冲区,script_uri 用 "file://%s" 格式拼出。文件读取失败或读取字节数不符时返回错误字符串(含 errno 信息)。加载成功后缓冲区会被登记到 owned_snapshots_,由引擎在 Shutdown 时统一 free。
AOT 快照加载(engine.cc):DartEngine_AotSnapshotFromFile 通过 Utils::LoadDynamicLibrary 把 AOT 快照当作共享库打开,然后解析其中 kSnapshotDataCSymbol 与 kSnapshotTextCSymbol 两个符号,得到 snapshot_data 和 snapshot_text 指针。这印证了 README 中"将 Dart 快照当作共享库使用"的定位——AOT 快照本质上是可动态加载的库文件。
创建并启动 Isolate
DartEngine_CreateIsolate 是"快照 → 可调用 Isolate"的关键入口(engine.cc)。它的完整流程如下:
- 快照种类与运行时匹配校验:预编译(AOT)运行时只接受 AOT 快照,非预编译(JIT)运行时只接受 Kernel 快照,否则返回错误(源码中两个分支使用同一错误文本
"AOT Dart VM supports only AOT snapshots")。 - 首次启动时完成完整 VM 初始化:在
engine_lifecycle_互斥锁保护下调用Dart_Initialize(仅第一次)。 - 初始化 Isolate 标志:通过
Dart_IsolateFlagsInitialize获取默认标志。 - 按运行时选择创建路径:
- AOT:调用
Dart_CreateIsolateGroup(script_uri, ...),用strrchr(script_uri, '/')推导库名作为名称参数; - Kernel:调用
Dart_CreateIsolateGroupFromKernel(script_uri, script_uri, kernel_buffer, kernel_buffer_size, ...)。
- AOT:调用
- 安装消息通知回调:
Dart_SetMessageNotifyCallback(Engine::MessageNotifyCallback),把 Dart VM 的消息通知接入引擎内部调度。 - 初始化核心库:进入 Scope 后调用
bin::DartUtils::SetupCoreLibraries——正如 README 所说,这是"完整初始化"的一部分,没有这一步print都不会工作(engine.cc 的注释原话)。 - Kernel 模式额外设置根库:
Dart_LoadScriptFromKernel被再次调用,因为 kernel 快照的根库 URI 与script_uri无关,取决于构建快照时的路径。 - 保存 Isolate 元数据:查找
dart:isolate库,建立持久化句柄(isolate 库与_runPendingImmediateCallback函数名),初始化该 Isolate 的调度器字段为空。 - 退出 Scope 与 Isolate,将 Isolate 登记到
isolates_列表,返回Dart_Isolate。
进入与离开 Isolate:锁保护的 Acquire/Release
这是 Engine API 与 dart_api.h 差异最明显的一个点。官方 README 明确指出:因为 Engine API 使用自己的消息处理机制,所以必须用 DartEngine_AcquireIsolate / DartEngine_ReleaseIsolate 替代 Dart_EnterIsolate / Dart_ExitIsolate。
原因在于两者的行为差异:
DartEngine_AcquireIsolate会先尝试获取该 Isolate 对应的内部互斥锁(每个 Isolate 在Engine::IsolateData中持有一把Mutex,见 engine.h),获取到锁之前会阻塞等待,然后再调用Dart_EnterIsolate(dart_engine_impl.cc);Dart_EnterIsolate则不等待——如果其他线程已经进入了同一个 Isolate,会直接崩溃。
对应的 DartEngine_ReleaseIsolate 先 Dart_ExitIsolate(),再通过 Engine::UnlockIsolate 释放锁,让其他线程可以进入(dart_engine_impl.cc)。
在多线程宿主(例如一个线程池要并发调用同一个 Dart Isolate 中的函数)场景下,这个"阻塞等锁"的语义是保证安全性的关键。
处理 Isolate 消息:三种方案对比
README 梳理了在 dart_api.h 中处理异步 Isolate 消息的两种传统方式,以及 Engine API 提供的第三种替代方案:
| 方案 | 机制 | 局限 |
|---|---|---|
Dart_MessageNotifyCallback |
用户传入回调获得消息到达通知 | 处理消息仍需手动"进入 Isolate → 进入 Scope → Dart_HandleMessage → 退出 Scope → 退出 Isolate" |
Dart_RunLoop |
在独立线程上运行事件循环 | 运行后无法从其他线程进入 Isolate |
DartEngine_HandleMessage(Engine API) |
封装了 isolate 与 scope 的管理 | 需配合消息调度器,且只能处理"单条消息" |
DartEngine_HandleMessage 的实现非常能体现"封装"二字(engine.cc):
void Engine::HandleMessage(Dart_Isolate isolate) {
LockIsolate(isolate);
Dart_EnterIsolate(isolate);
Dart_EnterScope();
Dart_Handle handle_result = Dart_HandleMessage();
if (Dart_IsError(handle_result)) {
// 调用用户错误回调,或打印错误日志
}
Dart_ExitScope();
Dart_ExitIsolate();
UnlockIsolate(isolate);
}
一次调用就把"锁、进入、Scope、Dart_HandleMessage、错误处理、退出 Scope、退出 Isolate、解锁"整套样板代码收敛了。调用者唯一要做的是:把 DartEngine_HandleMessage(isolate) 调度到合适的时机与线程执行——这正是消息调度器(Message Scheduler)的职责。
消息调度器:默认调度器与按 Isolate 调度器
调度器结构
typedef void (*DartEngine_ScheduleMessageCallback)(Dart_Isolate isolate, void* context);
typedef struct DartEngine_MessageScheduler {
DartEngine_ScheduleMessageCallback schedule_callback;
void* context;
} DartEngine_MessageScheduler;
调度器本身不负责执行消息处理,它只负责"调度"——每当某个 Isolate 有新消息时,引擎调用 schedule_callback(isolate, context),回调里只需安排一次 DartEngine_HandleMessage(isolate) 的执行(可以在本线程、独立线程、std::async 等任意方式)。
调度优先级与内部流程
引擎对消息通知的处理集中在 Engine::NotifyMessage(engine.cc):
- 先尝试获取
engine_lifecycle_锁并检查is_running_——如果关停正在进行或已完成,直接放弃本次通知(这是防止关停期死锁的关键,详见下文); - 查找该 Isolate 的专属调度器(
DataForIsolate(isolate)->scheduler),若为空则回退到默认调度器(default_scheduler_); - 若两者都为空,忽略消息;否则调用
schedule_callback(isolate, scheduler.context)。
因此优先顺序是:按 Isolate 设置的调度器 > 默认调度器。两个设置函数对应 dart_engine.h:
DartEngine_SetDefaultMessageScheduler(scheduler):影响所有未单独设置的 Isolate;DartEngine_SetMessageScheduler(scheduler, isolate):只影响指定 Isolate。
关停期间的防死锁设计
NotifyMessage 中对 engine_lifecycle_ 的 TryLock 使用并非偶然。engine.cc 的注释描述了需要防范的经典死锁场景:
- 一边,消息通知线程(正持有
PortMap::mutex_)想锁住一个 Isolate 去调度DartEngine_HandleMessage; - 另一边,关停线程正持有该 Isolate 的锁、等待
PortMap::mutex_(发生在Dart_ShutdownIsolate内部)。
通过让 NotifyMessage 在拿不到 engine_lifecycle_ 锁时直接放弃调度(视作"关停进行中"),就切断了这个环。这是源码级可见的工程细节,也解释了为什么调度器回调必须"只调度、不阻塞"。
微任务队列与消息错误回调
手动排空微任务队列
正常情况下,引擎在每处理完一条 Isolate 消息后都会排空微任务队列。但有一种情况需要手动干预:当宿主通过 Engine API 直接调用进入 Dart 代码时,可能需要在调用返回后手动排空微任务队列。
DartEngine_DrainMicrotasksQueue() 要求调用者已处于活跃 Isolate 中,其实现(engine.cc)是对 Isolate 库上的 _runPendingImmediateCallback(源码中 kRunPendingImmediateCallback 常量)发起一次 Dart_Invoke。头文件还给出两条重要语义:
- 如果某个微任务抛出异常,错误会返回给调用者,队列中可能仍有剩余条目;
- 调用者应在处理完错误后继续排空队列。
消息错误回调
通过 DartEngine_SetHandleMessageErrorCallback 注册的回调,会在 Dart_HandleMessage 返回错误时被触发(签名见 dart_engine.h)。如果没有注册回调,引擎会退回到 Syslog::PrintErr 打印错误(engine.cc)。
实战示例:samples/embedder
官方在 samples/embedder 中提供了完整的可编译示例,覆盖了从最简单到多快照协作的全部典型用法。示例同时支持 Kernel 与 AOT 快照,取决于链接的是哪种运行时库变体。
重要前提:快照文件格式不稳定,运行示例所用的
dart可执行文件必须与快照版本匹配,最稳妥的方式是从同一份 checkout 构建 SDK(见 docs/Building.md)。
run_main:最简调用流程
run_main.cc 是所有示例中最简单的一个:加载快照 → 创建 Isolate → 获取 Isolate → 进入 Scope → 调用 main 函数 → 退出。核心代码模式如下:
DartEngine_SnapshotData snapshot_data = AutoSnapshotFromFile(argv[1], &error);
CheckError(error, "reading snapshot");
Dart_Isolate isolate = DartEngine_CreateIsolate(snapshot_data, &error);
CheckError(error, "starting isolate");
DartEngine_AcquireIsolate(isolate);
Dart_EnterScope();
Dart_Handle main_args = ToDartStringList({"world"});
CheckError(Dart_Invoke(Dart_RootLibrary(), Dart_NewStringFromCString("main"),
1, const_cast<Dart_Handle*>(main_args.begin())),
"calling main");
Dart_ExitScope();
DartEngine_ReleaseIsolate();
DartEngine_Shutdown();
注意:它不处理 Isolate 消息,因此无法运行带异步函数的 Dart 程序(samples/embedder/README.md 原文明确说明)。目标 Dart 代码是 hello.dart,其 main 通过 @pragma('vm:entry-point', 'call') 标记为可从宿主调用的入口点。
构建与运行命令(来自 samples/embedder/README.md):
# Kernel 快照
./tools/build.py --mode=release samples/embedder:run_main_kernel && \
out/ReleaseX64/run_main_kernel out/ReleaseX64/gen/hello_kernel.dart.snapshot.
# AOT 快照
./tools/build.py --mode=release samples/embedder:run_main_aot && \
out/ReleaseX64/run_main_aot out/ReleaseX64/hello_aot.snapshot.
run_timer 与 run_timer_async:两种消息调度器实现
run_timer.cc 演示了在独立线程上运行 Isolate 事件循环:ThreadedMessageHandler 用"队列 + 条件变量"实现了一个专用消息处理线程,其 ScheduleDartMessage 静态方法把 Isolate 压入队列并唤醒处理线程,然后注册为默认调度器:
ThreadedMessageHandler message_handler;
std::thread message_handler_thread(&ThreadedMessageHandler::Run, &message_handler);
DartEngine_SetDefaultMessageScheduler(
{ThreadedMessageHandler::ScheduleDartMessage, &message_handler});
run_timer_async.cc 则展示了用 C++11 std::async 实现的自定义调度器——调度回调只有一行:
void ScheduleDartMessage(Dart_Isolate isolate, void* context) {
std::ignore = std::async(DartEngine_HandleMessage, isolate);
}
两者的主流程一致:设置默认调度器 → AutoSnapshotFromFile 加载快照 → DartEngine_CreateIsolate 创建 Isolate → 调用 Call_startTimer(isolate, 1) 启动 Dart 侧定时器 → 等待 100ms → Call_stopTimer(isolate) → 读取 Get_ticks(isolate) 打印结果 → 停止消息线程 → DartEngine_Shutdown。
Call_startTimer、Call_stopTimer、Get_ticks 等函数来自 timer.h,这是通过 dart pkg/vm/tool/generate_entry_point_shims.dart 从 Dart 代码自动生成的入口点 shim(头文件注释给出了生成命令),封装了"获取 Isolate → 调用函数"的样板,是"把 Dart 模块变成 C/C++ 可调用库"的典型形态。
run_two_programs:多快照协作
run_two_programs.cc 演示了同时启动两个快照:先从 program1 快照的 Isolate 调用 getValue 拿到返回值,再把该字符串作为参数传入 program2 快照的 Isolate 调用 printValue。整个过程展示了:
DartEngine_CreateIsolate可以被多次调用,创建多个相互独立的 Isolate;- 宿主可以在不同 Isolate 之间自由切换(
DartEngine_AcquireIsolate/ReleaseIsolate成对出现); - 调用结果通过
StringFromHandle(helpers.h 中的辅助函数)转成 C++ 字符串。
公共辅助设施
所有示例共用 helpers.h,其中值得关注的几个工具:
AutoSnapshotFromFile:根据Dart_IsPrecompiledRuntime()自动选择DartEngine_AotSnapshotFromFile或DartEngine_KernelFromFile,让同一份 C++ 代码同时适配两种运行时;CheckError:统一的char*/Dart_Handle错误检查与退出;DartScope/IsolateScope:RAII 封装Dart_EnterScope/ExitScope与DartEngine_AcquireIsolate/ReleaseIsolate;WithIsolate(isolate, body):模板函数,把"进 Isolate + 进 Scope + 执行 + 退出"收敛为一行。
示例还包含 run_futures.cc(演示 Future 相关的异步调用)。从 samples/embedder/BUILD.gn 可以看到,由于 FFI 无法在 VM 模拟器上执行,run_futures 仅在 dart_target_arch == host_cpu 时构建。
构建目标与集成方式
Engine API 的构建目标定义在 runtime/engine/BUILD.gn 中,核心是两个 source_set(engine_jit_set 与 engine_aot_set),它们各自编译 dart_engine_impl.cc 和 engine.cc,并分别链接 JIT(libdart_jit)与 AOT(libdart_aotruntime)运行时,同时引入 common_embedder_dart_io 等依赖。
由此派生出四种链接形态:
| 目标 | 形态 | 用途 |
|---|---|---|
dart_engine_jit_shared |
共享库 | JIT/内核模式动态链接 |
dart_engine_jit_static |
静态库(complete_static_lib) |
JIT/内核模式静态链接 |
dart_engine_aot_shared |
共享库 | AOT 模式动态链接 |
dart_engine_aot_static |
静态库(complete_static_lib) |
AOT 模式静态链接 |
samples/embedder/BUILD.gn 中的 _all_configs 展示了宿主程序如何选择这些库:_kernel 后缀的示例链接 dart_engine_jit_shared/static 并以 application_snapshot 生成 Kernel 快照;_aot 后缀的示例链接 dart_engine_aot_shared/static 并以 aot_snapshot 生成 AOT 快照(gen_kernel_args 中的 --link-platform 用于链接平台库)。
内部实现原理速览
单例 Engine 与两把锁
Engine 是一个进程级单例(Engine::instance() 静态局部变量),因为 Dart VM 拥有全局状态,引擎实例必须唯一(engine.h 的注释明确说明)。它内部使用两把锁:
engine_lifecycle_:控制初始化/关停,并在关停期间阻止消息投递以防止死锁;engine_state_:保护引擎状态数据(快照列表、已加载动态库、Isolate 数据表)。
此外,每个 Isolate 在 IsolateData 中还有一把独立的 Mutex,供 AcquireIsolate/ReleaseIsolate 使用(engine.h)。
消息通知的完整调用链
从 Dart VM 到用户调度器,消息的流转路径为:
Dart VM 内部发消息
→ Engine::MessageNotifyCallback(isolate) // 注册到 Dart_SetMessageNotifyCallback
→ Engine::NotifyMessage(isolate) // TryLock 防死锁 + 选择调度器
→ scheduler.schedule_callback(isolate, context) // 用户调度回调(例如压入队列 / std::async)
→ DartEngine_HandleMessage(isolate) // 加锁进入 → 处理 → 退出解锁
这条链路把 README 中"调度器只需安排一次 DartEngine_HandleMessage 的执行"这句话完整落到了源码层面。
使用约束与注意事项
综合官方文档、头文件注释与源码实现,使用 Dart Engine API 时有以下几点需要牢记:
- 它不是独立 API:必须与
dart_api.h配合使用,Dart_Handle、Scope、Dart_Invoke等基础操作仍走标准接口; - 快照类型与运行时严格匹配:AOT 运行时只能加载 AOT 快照,JIT 运行时只能加载 Kernel 快照,
DartEngine_CreateIsolate会做校验并报错; - 进出 Isolate 必须成对:使用
DartEngine_AcquireIsolate进入后,必须用DartEngine_ReleaseIsolate退出,不要混用Dart_EnterIsolate/Dart_ExitIsolate,否则多线程场景会崩溃或死锁; - 调度器只调度不阻塞:调度回调应尽快返回,实际的
DartEngine_HandleMessage可以在任意线程执行;引擎在关停期间会丢弃消息通知,调度器设计上要能容忍这一点; - 快照缓冲区由引擎管理:
KernelFromFile/AotSnapshotFromFile返回的 uri 与缓冲区归引擎所有,用户不得释放,引擎会在DartEngine_Shutdown时统一清理; - 引擎不可重启:
DartEngine_Shutdown之后不应再尝试重新初始化; - 异步代码需要消息循环:不注册任何消息调度器(或不用
DartEngine_HandleMessage处理消息)的宿主无法运行带异步函数的 Dart 程序,请参考run_timer系列示例搭建自己的事件循环; - 版本匹配:快照文件格式不稳定,宿主程序对应的
dart/SDK 构建版本必须与快照生成版本一致。
Dart Engine API 的完整文档与示例均可在仓库内继续深入:接口声明见 runtime/engine/include/dart_engine.h,核心实现见 runtime/engine/engine.cc 与 runtime/engine/dart_engine_impl.cc,可直接复用的完整示例位于 samples/embedder。