Learn Dart - Dart VM & Native Interop
Series/Learn Dart/Episode 16
Episode 16 of 23

Learn Dart - Dart VM & Native Interop

This episode covers the Dart VM and native interop: working with the Dart VM and command-line tools, FFI for calling native libraries, building CLI utilities, and embedding Dart in a host application.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Behind the Dart language is a versatile engine: the Dart VM, which runs code with JIT, produces AOT binaries, and powers the command-line tools. Episode 16 dissects how to work directly with the Dart VM and connect it to the native world.

You'll learn to leverage VM tooling, call C libraries through FFI, build professional CLI utilities, and understand how Dart can be embedded in other host applications.

This interop capability is what lets Dart sit side by side with existing native ecosystems.

Working with the Dart VM and Command-Line Tools

Core VM Commands

The Dart SDK provides a set of commands that run on top of the VM:

Core VM commands
dart run bin/app.dart
dart compile exe bin/app.dart -o bin/app
dart analyze
dart test

dart run executes code with JIT, dart compile exe produces an AOT binary, while dart analyze and dart test leverage the analyzer and test runner. This entire toolchain shares one SDK.

Snapshots for Faster Startup

The VM supports snapshots — a captured state of the program for faster loading:

Creating an AOT snapshot
dart compile aot-snapshot bin/app.dart -o app.aot
dart run app.aot

dart compile aot-snapshot bin/app.dart produces an AOT snapshot that loads faster than source code. Snapshots are often used for internal tools and services that need minimal startup time.

FFI for Interop with Native Libraries

Loading Native Libraries

FFI (Foreign Function Interface) calls C functions from Dart without a wrapper:

FFI calling a C function
import 'dart:ffi';
import 'dart:io';
 
final libc = DynamicLibrary.open('libc.so.6');
 
typedef StrlenNative = IntPtr Function(Pointer<Utf8>);
typedef StrlenDart = int Function(Pointer<Utf8>);
 
void main() {
  final strlen = libc.lookupFunction<StrlenNative, StrlenDart>('strlen');
  final teks = 'halo'.toNativeUtf8();
 
  print('Panjang: ${strlen(teks)}');
  calloc.free(teks);
}

DynamicLibrary.open('libc.so.6') loads a C library, then lookupFunction binds the strlen function. Flutter uses FFI to call native code like SQLite and media players.

Security and Memory Management

When using FFI, you're responsible for memory management: allocations with calloc must be freed, and pointers must not outlive the library's lifetime. Mistakes here cause crashes or memory corruption — test carefully and keep the interop surface as minimal as possible.

Building Command-Line Utilities and Scripts

CLI with Arguments

Dart is well suited for command-line tools. Accept arguments via dart:io:

CLI reading arguments
import 'dart:io';
 
void main(List<String> args) {
  if (args.isEmpty) {
    stderr.writeln('Penggunaan: hello <nama>');
    exitCode = 1;
    return;
  }
 
  stdout.writeln('Halo, ${args.first}!');
}

args.first takes the first argument from the terminal. Returning a non-zero exitCode signals failure — a standard pattern that scripts and pipelines understand.

The args Package for Advanced Parsing

For complex CLIs, use package:args, which handles flags, options, and automatic help:

Adding the args package
dart pub add args

dart pub add args adds the official argument parser. With args, you get typed flags, validation, and --help output without writing it by hand.

Embedding Dart in Host Applications

Interop in Both Directions

Beyond calling native code from Dart, you can also embed Dart into a host application. The main approaches:

  • Flutter engine: mobile apps embed the Flutter engine, which runs Dart code as the UI.
  • VM embedding API: native C++ applications load the Dart VM and communicate with isolates through the official embedding API.

Example: Flutter as a Module

For a native application that wants to adopt Flutter gradually, use add-to-app: Flutter runs as a module inside an existing Android or iOS application. Two-way communication uses platform channels, allowing Flutter adoption without rewriting the entire app.

Conclusion

Key takeaways:

  • dart run, dart compile, and dart analyze are the core toolchain commands that run on the VM.
  • AOT snapshots speed up startup compared to source code.
  • FFI calls C libraries with DynamicLibrary and lookupFunction.
  • Manage manual FFI memory with discipline: allocations must be freed.
  • dart:io reads arguments; package:args handles complex CLI parsing.
  • Dart can be embedded into host apps via the Flutter engine or the VM embedding API.

In the next episode 17, we'll cover multiplatform and shared libraries — sharing code between mobile, web, server, and CLI, modular architecture and reusable packages, cross-platform testing, and integration with Flutter plugins.

Learn Dart - Dart VM & Native Interop | Learn Dart