[native_assets_cli] Add `DartCApi`
diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml
index 3744888..6617443 100644
--- a/.github/workflows/native.yaml
+++ b/.github/workflows/native.yaml
@@ -111,6 +111,9 @@
       - run: dart pub get -C test_data/no_hook/
         if: ${{ matrix.package == 'native_assets_builder' }}
 
+      - run: dart pub get -C test_data/use_dart_api/
+        if: ${{ matrix.package == 'native_assets_builder' }}
+
       - run: dart pub get -C example/build/download_asset/
         if: ${{ matrix.package == 'native_assets_cli' }}
 
diff --git a/pkgs/native_assets_builder/test/build_runner/dart_c_api_test.dart b/pkgs/native_assets_builder/test/build_runner/dart_c_api_test.dart
new file mode 100644
index 0000000..5656f27
--- /dev/null
+++ b/pkgs/native_assets_builder/test/build_runner/dart_c_api_test.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+
+import '../helpers.dart';
+import 'helpers.dart';
+
+const Timeout longTimeout = Timeout(Duration(minutes: 5));
+
+void main() async {
+  test('use_dart_api build', timeout: longTimeout, () async {
+    await inTempDir((tempUri) async {
+      await copyTestProjects(targetUri: tempUri);
+      final packageUri = tempUri.resolve('use_dart_api/');
+      await runPubGet(
+        workingDirectory: packageUri,
+        logger: logger,
+      );
+
+      // Assume we're run from Dart SDK and try to find the include dir.
+      final includeDirectory = dartExecutable.resolve('../include/');
+      final versionFile = includeDirectory.resolve('dart_version.h');
+      final versionContents =
+          await File(versionFile.toFilePath()).readAsString();
+      final regex = RegExp(
+          r'#define DART_API_DL_MAJOR_VERSION (\d+)\s*#define DART_API_DL_MINOR_VERSION (\d+)');
+      final match = regex.firstMatch(versionContents)!;
+      final major = int.parse(match.group(1)!);
+      final minor = int.parse(match.group(2)!);
+      final dartCApi = DartCApi(
+        includeDirectory: includeDirectory,
+        version: Version(major, minor, 0),
+      );
+
+      // Run the build.
+      final result = (await buildCodeAssets(
+        packageUri,
+        dartCApi: dartCApi,
+      ))!;
+      final codeAsset = CodeAsset.fromEncoded(result.encodedAssets.single);
+
+      // Check that we can load the dylib and run the init.
+      final dylib = DynamicLibrary.open(codeAsset.file!.toFilePath());
+      final initDartApiDl = dylib.lookupFunction<IntPtr Function(Pointer<Void>),
+          int Function(Pointer<Void>)>('InitDartApiDL');
+      final initResult = initDartApiDl(NativeApi.initializeApiDLData);
+      expect(initResult, 0);
+    });
+  });
+}
diff --git a/pkgs/native_assets_builder/test/build_runner/helpers.dart b/pkgs/native_assets_builder/test/build_runner/helpers.dart
index 122772f..d346211 100644
--- a/pkgs/native_assets_builder/test/build_runner/helpers.dart
+++ b/pkgs/native_assets_builder/test/build_runner/helpers.dart
@@ -52,6 +52,7 @@
   Uri packageUri, {
   String? runPackageName,
   List<String>? capturedLogs,
+  DartCApi? dartCApi,
 }) =>
     build(
       packageUri,
@@ -63,6 +64,7 @@
       buildValidator: validateCodeAssetBuildOutput,
       applicationAssetValidator: validateCodeAssetInApplication,
       runPackageName: runPackageName,
+      dartCApi: dartCApi,
     );
 
 Future<BuildResult?> build(
@@ -84,6 +86,7 @@
   bool linkingEnabled = false,
   required List<String> buildAssetTypes,
   Map<String, String>? hookEnvironment,
+  DartCApi? dartCApi,
 }) async {
   final targetOS = target?.os ?? OS.current;
   final runPackageName_ =
@@ -122,6 +125,7 @@
             android: targetOS == OS.android
                 ? AndroidCodeConfig(targetNdkApi: targetAndroidNdkApi!)
                 : null,
+            dartCApi: dartCApi,
           );
         }
         return inputBuilder;
diff --git a/pkgs/native_assets_builder/test_data/manifest.yaml b/pkgs/native_assets_builder/test_data/manifest.yaml
index 819718c..7a269b4 100644
--- a/pkgs/native_assets_builder/test_data/manifest.yaml
+++ b/pkgs/native_assets_builder/test_data/manifest.yaml
@@ -164,6 +164,14 @@
 - use_all_api/hook/build.dart
 - use_all_api/hook/link.dart
 - use_all_api/pubspec.yaml
+- use_dart_api/ffigen.yaml
+- use_dart_api/hook/build.dart
+- use_dart_api/lib/src/use_dart_api_bindings_generated.dart
+- use_dart_api/lib/use_dart_api.dart
+- use_dart_api/pubspec.yaml
+- use_dart_api/src/use_dart_api.c
+- use_dart_api/src/use_dart_api.h
+- use_dart_api/test/use_dart_api_test.dart
 - wrong_build_output/hook/build.dart
 - wrong_build_output/pubspec.yaml
 - wrong_build_output_2/hook/build.dart
diff --git a/pkgs/native_assets_builder/test_data/use_dart_api/README.md b/pkgs/native_assets_builder/test_data/use_dart_api/README.md
new file mode 100644
index 0000000..bb8514f
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/use_dart_api/README.md
@@ -0,0 +1,11 @@
+An example that uses the [C API of the Dart VM].
+
+The example shows how to pass an object from the Dart heap to native code and
+hold on to it via a `PersistentHandle`. For more documentation about handles,
+and the other C API features refer to the documentation in the header files.
+
+## Usage
+
+Run tests with `dart --enable-experiment=native-assets test`.
+
+[C API of the Dart VM]: https://github.com/dart-lang/sdk/tree/main/runtime/include
diff --git a/pkgs/native_assets_builder/test_data/use_dart_api/ffigen.yaml b/pkgs/native_assets_builder/test_data/use_dart_api/ffigen.yaml
new file mode 100644
index 0000000..5e0e4e3
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/use_dart_api/ffigen.yaml
@@ -0,0 +1,20 @@
+# Run with `flutter pub run ffigen --config ffigen.yaml`.
+name: NativeAddBindings
+description: |
+  Bindings for `src/use_dart_api.h`.
+
+  Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.
+output: 'lib/src/use_dart_api_bindings_generated.dart'
+headers:
+  entry-points:
+    - 'src/use_dart_api.h'
+  include-directives:
+    - 'src/use_dart_api.h'
+preamble: |
+  // Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+  // for details. All rights reserved. Use of this source code is governed by a
+  // BSD-style license that can be found in the LICENSE file.
+comments:
+  style: any
+  length: full
+ffi-native:
diff --git a/pkgs/native_assets_builder/test_data/use_dart_api/hook/build.dart b/pkgs/native_assets_builder/test_data/use_dart_api/hook/build.dart
new file mode 100644
index 0000000..85ce205
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/use_dart_api/hook/build.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:logging/logging.dart';
+import 'package:native_assets_cli/code_assets_builder.dart';
+import 'package:native_assets_cli/native_assets_cli.dart';
+import 'package:native_toolchain_c/native_toolchain_c.dart';
+
+void main(List<String> arguments) async {
+  await build(arguments, (input, output) async {
+    final dartCApi = input.config.code.dartCApi;
+    if (dartCApi == null) {
+      throw UnsupportedError(
+        'This doesn\'t work with access to the Dart C API!',
+      );
+    }
+
+    final packageName = input.packageName;
+    final cbuilder = CBuilder.library(
+      name: packageName,
+      assetName: 'src/${packageName}_bindings_generated.dart',
+      sources: [
+        'src/$packageName.c',
+        dartCApi.dartApiDlC.toFilePath(),
+      ],
+      includes: [
+        dartCApi.includeDirectory.toFilePath(),
+      ],
+    );
+    await cbuilder.run(
+      input: input,
+      output: output,
+      logger: Logger('')
+        ..level = Level.ALL
+        ..onRecord.listen((record) {
+          print('${record.level.name}: ${record.time}: ${record.message}');
+        }),
+    );
+  });
+}
diff --git a/pkgs/native_assets_builder/test_data/use_dart_api/lib/src/use_dart_api_bindings_generated.dart b/pkgs/native_assets_builder/test_data/use_dart_api/lib/src/use_dart_api_bindings_generated.dart
new file mode 100644
index 0000000..5cb03ae
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/use_dart_api/lib/src/use_dart_api_bindings_generated.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+// ignore_for_file: type=lint
+import 'dart:ffi' as ffi;
+
+@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(symbol: 'add')
+external int add(
+  int a,
+  int b,
+);
+
+@ffi.Native<ffi.IntPtr Function(ffi.Pointer<ffi.Void>)>(symbol: 'InitDartApiDL')
+external int InitDartApiDL(
+  ffi.Pointer<ffi.Void> data,
+);
+
+@ffi.Native<ffi.Pointer<ffi.Void> Function(ffi.Handle)>(
+    symbol: 'NewPersistentHandle')
+external ffi.Pointer<ffi.Void> NewPersistentHandle(
+  Object non_persistent_handle,
+);
+
+@ffi.Native<ffi.Handle Function(ffi.Pointer<ffi.Void>)>(
+    symbol: 'HandleFromPersistent')
+external Object HandleFromPersistent(
+  ffi.Pointer<ffi.Void> persistent_handle,
+);
diff --git a/pkgs/native_assets_builder/test_data/use_dart_api/lib/use_dart_api.dart b/pkgs/native_assets_builder/test_data/use_dart_api/lib/use_dart_api.dart
new file mode 100644
index 0000000..84062bb
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/use_dart_api/lib/use_dart_api.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'src/use_dart_api_bindings_generated.dart';
diff --git a/pkgs/native_assets_builder/test_data/use_dart_api/pubspec.yaml b/pkgs/native_assets_builder/test_data/use_dart_api/pubspec.yaml
new file mode 100644
index 0000000..57f0e0a
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/use_dart_api/pubspec.yaml
@@ -0,0 +1,22 @@
+name: use_dart_api
+description: Uses some functions from `dart_api_dl.h`.
+version: 0.1.0
+
+publish_to: none
+
+environment:
+  sdk: '>=3.3.0 <4.0.0'
+
+dependencies:
+  logging: ^1.1.1
+  # native_assets_cli: ^0.11.0
+  native_assets_cli:
+    path: ../../../native_assets_cli/
+  # native_toolchain_c: ^0.8.0
+  native_toolchain_c:
+    path: ../../../native_toolchain_c/
+
+dev_dependencies:
+  ffigen: ^10.0.0
+  lints: ^3.0.0
+  test: ^1.23.1
diff --git a/pkgs/native_assets_builder/test_data/use_dart_api/src/use_dart_api.c b/pkgs/native_assets_builder/test_data/use_dart_api/src/use_dart_api.c
new file mode 100644
index 0000000..a1accc7
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/use_dart_api/src/use_dart_api.c
@@ -0,0 +1,17 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "use_dart_api.h"
+
+int32_t add(int32_t a, int32_t b) { return a + b; }
+
+intptr_t InitDartApiDL(void *data) { return Dart_InitializeApiDL(data); }
+
+void *NewPersistentHandle(Dart_Handle non_persistent_handle) {
+  return Dart_NewPersistentHandle_DL(non_persistent_handle);
+}
+
+Dart_Handle HandleFromPersistent(void *persistent_handle) {
+  return Dart_HandleFromPersistent_DL(persistent_handle);
+}
diff --git a/pkgs/native_assets_builder/test_data/use_dart_api/src/use_dart_api.h b/pkgs/native_assets_builder/test_data/use_dart_api/src/use_dart_api.h
new file mode 100644
index 0000000..b3d15e2
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/use_dart_api/src/use_dart_api.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include <stdint.h>
+
+#include "dart_api_dl.h"
+
+#if _WIN32
+#define MYLIB_EXPORT __declspec(dllexport)
+#else
+#define MYLIB_EXPORT
+#endif
+
+MYLIB_EXPORT int32_t add(int32_t a, int32_t b);
+
+MYLIB_EXPORT intptr_t InitDartApiDL(void *data);
+
+MYLIB_EXPORT void *NewPersistentHandle(Dart_Handle non_persistent_handle);
+
+MYLIB_EXPORT Dart_Handle HandleFromPersistent(void *persistent_handle);
diff --git a/pkgs/native_assets_builder/test_data/use_dart_api/test/use_dart_api_test.dart b/pkgs/native_assets_builder/test_data/use_dart_api/test/use_dart_api_test.dart
new file mode 100644
index 0000000..babf018
--- /dev/null
+++ b/pkgs/native_assets_builder/test_data/use_dart_api/test/use_dart_api_test.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+
+import 'package:test/test.dart';
+import 'package:use_dart_api/use_dart_api.dart';
+
+void main() {
+  InitDartApiDL(NativeApi.initializeApiDLData);
+
+  test('use dart_api_dl.h', () {
+    const x = 42;
+    final persistentHandle = NewPersistentHandle(x);
+    HandleFromPersistent(persistentHandle);
+  });
+}
diff --git a/pkgs/native_assets_cli/lib/code_assets.dart b/pkgs/native_assets_cli/lib/code_assets.dart
index d1d9c8f..b171f41 100644
--- a/pkgs/native_assets_cli/lib/code_assets.dart
+++ b/pkgs/native_assets_cli/lib/code_assets.dart
@@ -24,6 +24,7 @@
         CodeAssetLinkOutputBuilder,
         CodeAssetLinkOutputBuilderAdd,
         CodeConfig,
+        DartCApi,
         IOSCodeConfig,
         MacOSCodeConfig;
 export 'src/code_assets/ios_sdk.dart' show IOSSdk;
diff --git a/pkgs/native_assets_cli/lib/src/code_assets/code_asset.dart b/pkgs/native_assets_cli/lib/src/code_assets/code_asset.dart
index 4a65875..d28e4ee 100644
--- a/pkgs/native_assets_cli/lib/src/code_assets/code_asset.dart
+++ b/pkgs/native_assets_cli/lib/src/code_assets/code_asset.dart
@@ -184,6 +184,9 @@
       }..sortOnKey());
 
   static const String type = 'native_code';
+
+  @override
+  String toString() => 'CodeAsset(${encode().encoding})';
 }
 
 extension OSLibraryNaming on OS {
diff --git a/pkgs/native_assets_cli/lib/src/code_assets/config.dart b/pkgs/native_assets_cli/lib/src/code_assets/config.dart
index 832a9e9..30ce604 100644
--- a/pkgs/native_assets_cli/lib/src/code_assets/config.dart
+++ b/pkgs/native_assets_cli/lib/src/code_assets/config.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:pub_semver/pub_semver.dart';
+
 import '../config.dart';
 import '../json_utils.dart';
 import 'architecture.dart';
@@ -52,6 +54,8 @@
   final AndroidCodeConfig? _androidConfig;
   final MacOSCodeConfig? _macOSConfig;
 
+  final DartCApi? dartCApi;
+
   // Should not be made public, class will be replaced as a view on `json`.
   CodeConfig._({
     required Architecture? targetArchitecture,
@@ -61,6 +65,7 @@
     AndroidCodeConfig? androidConfig,
     IOSCodeConfig? iOSConfig,
     MacOSCodeConfig? macOSConfig,
+    this.dartCApi,
   })  : _targetArchitecture = targetArchitecture,
         cCompiler = cCompilerConfig,
         _iOSConfig = iOSConfig,
@@ -98,6 +103,11 @@
     final macOSConfig =
         dryRun || targetOS != OS.macOS ? null : MacOSCodeConfig.fromJson(json);
 
+    final dartCApi = switch (json.code?.optionalMap(_dartCApiKey)) {
+      final Map<String, Object?> map => DartCApi.fromJson(map),
+      null => null
+    };
+
     return CodeConfig._(
       targetArchitecture: targetArchitecture,
       targetOS: targetOS,
@@ -106,6 +116,7 @@
       iOSConfig: iOSConfig,
       androidConfig: androidConfig,
       macOSConfig: macOSConfig,
+      dartCApi: dartCApi,
     );
   }
 
@@ -282,6 +293,7 @@
     AndroidCodeConfig? android,
     IOSCodeConfig? iOS,
     MacOSCodeConfig? macOS,
+    DartCApi? dartCApi,
   }) {
     if (targetArchitecture != null) {
       json[_targetArchitectureKey] = targetArchitecture.toString();
@@ -298,6 +310,9 @@
       json[_compilerKey] = cCompiler.toJson(deprecatedTopLevel: true);
       json.setNested([_configKey, _codeKey, _compilerKey], cCompiler.toJson());
     }
+    if (dartCApi != null) {
+      json.setNested([_configKey, _codeKey, _dartCApiKey], dartCApi.toJson());
+    }
 
     // Note, using ?. instead of !. makes missing data be a semantic error
     // rather than a syntactic error to be caught in the validation.
@@ -346,6 +361,46 @@
       .toList();
 }
 
+/// Access to the C API for this version of Dart.
+///
+/// Only `dart_api_dl.h` should be used. `dart_api_dl.c` shoud be compiled into
+/// your `CodeAsset`.
+///
+/// Note: If you're precompiling code assets, you must check that [version] is
+/// compatible in your hook.
+class DartCApi {
+  /// The include directory from the Dart SDK.
+  ///
+  /// See the documentation in the following files on how to use the Dart C API.
+  /// - dart_api_dl.c
+  /// - dart_api_dl.h
+  /// - dart_version.h
+  final Uri includeDirectory;
+
+  /// The version of `dart_api_dl.h` in [includeDirectory].
+  ///
+  /// Note that only the major and minor version are used. The patch version is
+  /// always 0.
+  final Version version;
+
+  Uri get dartApiDlC => includeDirectory.resolve('dart_api_dl.c');
+
+  DartCApi({
+    required this.includeDirectory,
+    required this.version,
+  });
+
+  factory DartCApi.fromJson(Map<String, Object?> json) => DartCApi(
+        includeDirectory: json.path(_includeDirectoryKey),
+        version: Version.parse(json.string(_versionKey)),
+      );
+
+  Map<String, Object?> toJson() => {
+        _includeDirectoryKey: includeDirectory.toFilePath(),
+        _versionKey: version.toString(),
+      };
+}
+
 const String _compilerKey = 'c_compiler';
 const String _linkModePreferenceKey = 'link_mode_preference';
 const String _targetNdkApiKey = 'target_ndk_api';
@@ -366,6 +421,10 @@
 const _iosKey = 'ios';
 const _macosKey = 'macos';
 
+const _dartCApiKey = 'dart_api';
+const _includeDirectoryKey = 'include_directory';
+const _versionKey = 'version';
+
 extension on Map<String, Object?> {
   Map<String, Object?>? get code =>
       optionalMap(_configKey)?.optionalMap(_codeKey);
diff --git a/pkgs/native_assets_cli/test/code_assets/config_test.dart b/pkgs/native_assets_cli/test/code_assets/config_test.dart
index 26cd8ad..b4b1af1 100644
--- a/pkgs/native_assets_cli/test/code_assets/config_test.dart
+++ b/pkgs/native_assets_cli/test/code_assets/config_test.dart
@@ -6,6 +6,7 @@
 
 import 'package:native_assets_cli/code_assets_builder.dart';
 import 'package:native_assets_cli/src/config.dart' show latestVersion;
+import 'package:pub_semver/pub_semver.dart';
 import 'package:test/test.dart';
 
 void main() async {
@@ -19,6 +20,7 @@
   late Uri fakeAr;
   late List<EncodedAsset> assets;
   late Uri fakeVcVars;
+  late Uri dartCApiIncludeDirectory;
 
   setUp(() async {
     final tempUri = Directory.systemTemp.uri;
@@ -31,6 +33,7 @@
     fakeLd = tempUri.resolve('fake_ld');
     fakeAr = tempUri.resolve('fake_ar');
     fakeVcVars = tempUri.resolve('vcvarsall.bat');
+    dartCApiIncludeDirectory = tempUri.resolve('include/');
 
     assets = [
       CodeAsset(
@@ -102,6 +105,10 @@
                 },
               },
             },
+            'dart_api': {
+              'include_directory': dartCApiIncludeDirectory.toFilePath(),
+              'version': '2.5.0',
+            },
             if (targetOS == OS.android) 'android': {'target_ndk_api': 30},
             if (targetOS == OS.macOS) 'macos': {'target_version': 13},
             if (targetOS == OS.iOS)
@@ -211,6 +218,10 @@
             ),
           ),
         ),
+        dartCApi: DartCApi(
+          includeDirectory: dartCApiIncludeDirectory,
+          version: Version(2, 5, 0),
+        ),
       );
     final input = BuildInput(inputBuilder.json);
     expect(input.json, inputJson());
@@ -265,6 +276,10 @@
             ),
           ),
         ),
+        dartCApi: DartCApi(
+          includeDirectory: dartCApiIncludeDirectory,
+          version: Version(2, 5, 0),
+        ),
       );
     final input = LinkInput(inputBuilder.json);
     expect(input.json, inputJson(hookType: 'link'));