Dart Isn't an Island
Calling Native C/C++ Code with dart:ffi
A minimal working example — calling a C function from Dart:
First, the C code (`hello.c`)
#include <stdio.h>
// Export with C linkage so dart:ffi can find it
__attribute__((visibility("default"))) __attribute__((used))
void hello_world() {
printf("Hello from C!\n");
}
Compile it
# macOS
clang -dynamiclib -o libhello.dylib hello.c
# Linux
gcc -shared -fPIC -o libhello.so hello.c
Now call it from Dart
import 'dart:ffi' as ffi;
import 'dart:io' show Platform;
import 'package:path/path.dart' as path;
// Step 1: Typedef — C function signature
typedef HelloWorldFunc = ffi.Void Function();
typedef HelloWorld = void Function();
void main() {
// Step 2: Determine platform-specific library path
final String libraryPath;
if (Platform.isMacOS) {
libraryPath = path.join(Directory.current.path, 'libhello.dylib');
} else {
libraryPath = path.join(Directory.current.path, 'libhello.so');
}
// Step 3: Load the dynamic library
final dylib = ffi.DynamicLibrary.open(libraryPath);
// Step 4: Look up the function
final HelloWorld hello = dylib
.lookup<ffi.NativeFunction<HelloWorldFunc>>('hello_world')
.asFunction();
// Step 5: Call it
hello(); // prints: Hello from C!
}
The FFI library can only bind against C symbols — so in C++, mark your functions `extern “C”` to prevent name mangling.
Pro tip
Dart and JavaScript Interop in 2027
For web targets, Dart compiles to both JavaScript and WebAssembly. The interop story has evolved significantly.
The old way (deprecated): `dart:html` and `package:js` — these are being retired as of the 2025–2026 Flutter roadmap.
The new way: The `dart:js_interop` library, which is Wasm-safe and future-proof.
import 'dart:js_interop';
// Declare a JS object type
@JS('window')
external JSObject get window;
// Declare a JS function
@JS('alert')
external void alert(JSString message);
void main() {
alert('Hello from Dart!'.toJS);
}
The Flutter team is also retiring legacy JS interop (dart:html, package:js) and pushing developers toward the new Wasm-safe interop model. If you have existing Flutter web projects, start migrating now rather than later.
This new model works for both JS compilation *and* WebAssembly — which matters because WebAssembly is on track to become the default Flutter web target in 2026.
Communicating Between Dart Isolates and Platform Code
- Spin up an isolate
- Call native code from that isolate
- Send results back to the main isolate
import 'dart:isolate';
import 'dart:ffi' as ffi;
void nativeWorker(SendPort sendPort) {
// Load the native library inside the isolate
final dylib = ffi.DynamicLibrary.open('libcompute.so');
// ... call native function ...
final result = 42; // simplified
sendPort.send(result);
}
Future<int> runNativeInIsolate() async {
final receivePort = ReceivePort();
await Isolate.spawn(nativeWorker, receivePort.sendPort);
return await receivePort.first as int;
}
Building Platform-Specific Plugins
# Create a new FFI-based plugin (recommended for pure C/C++ libraries)
flutter create --platforms=android,ios --template=plugin_ffi my_native_lib
- `src/` — your C/C++ source files
- `lib/` — the Dart wrapper
- `android/` and `ios/` — platform build files
// lib/my_native_lib.dart — generated Dart bindings
import 'dart:ffi';
import 'package:my_native_lib/my_native_lib_bindings_generated.dart';
final _bindings = MyNativeLibBindings(DynamicLibrary.process());
int addNumbers(int a, int b) => _bindings.nativeAdd(a, b);
When to Use Interop vs. Pure Dart
| Scenario | Use |
|---|---|
| Existing C/C++ library (OpenCV, SQLite, etc.) | dart:ffi |
| CPU-intensive computation (crypto, image processing) | dart:ffi + Isolate |
| Platform API (camera, BLE, sensors) | Plugin with MethodChannel or dart:ffi |
| Web JS library integration | dart:js_interop |
| Everything else | Pure Dart — simpler, safer, portable |
The `dart:ffi` library is mostly used for
- Lower-level access that Dart can't provide natively
- Performance-critical code paths
- Existing external libraries like TensorFlow, OpenCV, or SQLite
Don't reach for FFI unless you need it
Pure Dart is portable, safe, and requires no native build infrastructure. If your use case is purely computational (sorting, parsing, transforming), isolates + pure Dart is often enough.
Dart’s interoperability story in 2027 is genuinely mature. FFI for native code, `dart:js_interop` for web, isolates for concurrency, and plugins for platform APIs — every bridge you need is built. The skill is knowing which one to cross.
Dart's interoperability story in 2027 is genuinely mature. FFI for native code, `dart:js_interop` for web, isolates for concurrency, and plugins for platform APIs — every bridge you need is built. The skill is knowing which one to cross.
Explore project snapshots or discuss custom web solutions.
Programs must be written for people to read, and only incidentally for machines to execute.
Thank You for Spending Your Valuable Time
I truly appreciate you taking the time to read blog. Your valuable time means a lot to me, and I hope you found the content insightful and engaging!
Frequently Asked Questions
Yes — for Flutter mobile and desktop apps, release builds automatically use AOT. For Dart CLI tools, use `dart compile exe` to produce an AOT binary. The JIT is only for development iteration speed.
Open DevTools Memory tab and watch heap size over time. Normal behavior: sawtooth pattern (rises, GC drops it). A leak: steady upward slope that never fully recovers after GC events.
It genuinely improves performance. `const` objects are canonicalized — identical `const` instances share the same memory address. It also allows the compiler to make additional optimizations around immutability.
It depends on the operation. `Set`/`Map` for O(1) lookups, `List` for index-based access and iteration, `Queue` (from `dart:collection`) for FIFO operations. Avoid `List.contains()` on large lists.
No. Emulators don't represent real device performance — they use your Mac or PC's CPU and don't simulate ARM constraints. Always profile on a physical device in `--profile` mode for meaningful data.
Comments are closed