Blog
Jun 2, 2026 - 4 MIN READ
Calling C++ and Go from Dart using FFI

Calling C++ and Go from Dart using FFI

Calling native libraries from Dart with dart:ffi, and which languages work.

Phanith LIM

Phanith LIM

dart:ffi lets Dart call into a shared library (.so or .dylib) directly. No platform channels, no plugin, no Flutter needed.

That is worth having when the code you want already exists in C or C++ and you would rather bind to it than port it, or when a hot loop is faster in a compiled language than in Dart. The cost is that you now have a build step per platform and a pointer or two to look after.

I wired up C++ and Go this way and worked out which other languages can join in. Both are below.

The one rule

Dart can only call functions that expose a C ABI: a plain C-compatible symbol with no name mangling. Every language reaches that differently, but the Dart side never changes: open the library, declare the types, look up the function.

C++

C++ mangles function names, so add becomes something unrecognisable. extern "C" turns that off.

// native/cpp/mylib.cpp
#include <cstdint>

extern "C" {
  int32_t add(int32_t a, int32_t b) {
    return a + b;
  }
}

Build it with CMake:

cmake -S native/cpp -B native/cpp/build -DCMAKE_BUILD_TYPE=Release
cmake --build native/cpp/build

Go

Go exports through CGo. Two details will bite you if you miss them: the file needs package main with an empty func main(), and //export must sit on the line directly above the function, with no blank line between.

// native/go/mylib.go
package main

import "C"

//export multiply
func multiply(a, b int32) int32 { return a * b }

//export fibonacci
func fibonacci(n int32) int64 {
    if n <= 1 { return int64(n) }
    a, b := int64(0), int64(1)
    for i := int32(2); i <= n; i++ { a, b = b, a+b }
    return b
}

func main() {}

Build:

cd native/go && CGO_ENABLED=1 go build -o ../libs/libgolib.so -buildmode=c-shared .

The Dart side

This part is the same no matter which language produced the library. Declare the native signature, declare the Dart signature, then lookupFunction.

// lib/src/go_bindings.dart
import 'dart:ffi';
import 'package:path/path.dart' as p;

typedef _FibonacciNative = Int64 Function(Int32 n);
typedef FibonacciDart = int Function(int n);

class GoLib {
  final DynamicLibrary _lib;
  late final FibonacciDart fibonacci;

  GoLib(String libsDir)
      : _lib = DynamicLibrary.open(p.join(libsDir, 'libgolib.so')) {
    fibonacci = _lib.lookupFunction<_FibonacciNative, FibonacciDart>('fibonacci');
  }
}

Then call it like ordinary Dart:

final go = GoLib(libsDir);
print(go.fibonacci(10)); // 55

Numbers are the easy case. They copy across on their own, and nothing needs cleaning up afterwards.

Strings do not. A Pointer<Utf8> is memory you asked for, so you convert with toNativeUtf8(), pass the pointer, read the reply back with toDartString(), and free what you allocated. Forget the free and you have a leak that no garbage collector will find for you.


Which languages work

Anything that compiles to a shared library with C-compatible symbols.

Works

LanguageExport mechanism
CNative, no wrapper needed
C++extern "C" { ... } block
GoCGo //export + -buildmode=c-shared
Rust#[no_mangle] extern "C" fn + crate-type = ["cdylib"]
Zigexport fn + compile as shared library
Nim{.exportc, dynlib.} pragma
Dextern(C) linkage
Kotlin/Native@CName annotation (not Kotlin/JVM)
Swift@_cdecl("name") on a free function
FortranISO_C_BINDING + bind(C) attribute

Does not work

LanguageReason
PythonNo C ABI export, would need embedding CPython
JavaJVM bytecode only; JNI goes the other direction
Kotlin (JVM)Runs on the JVM, no C ABI
ScalaJVM, no C ABI
C# / .NETCLR managed runtime, no native C ABI export
JavaScript / TypeScriptRuns in a JS engine, no C ABI
RubyInterpreter-based, no C ABI export
PHPInterpreter-based, no C ABI export

The pattern behind both tables: if the language can produce a .so with plain C symbol names, Dart FFI can call it. If it needs its own runtime to be alive first, it cannot.


Type mappings

C and C++

Dart FFI typeC / C++ type
Int8int8_t
Int16int16_t
Int32int32_t
Int64int64_t
Floatfloat
Doubledouble
Boolbool
Voidvoid
Pointer<Utf8>char*
Pointer<T>T*

Go (via CGo)

Dart FFI typeGo / CGo type
Int32int32 / C.int
Int64int64 / C.longlong
Floatfloat32 / C.float
Doublefloat64 / C.double
Boolbool / C.int (0/1)
Pointer<Utf8>*C.char
Pointer<Int32>*C.int

Rust

Dart FFI typeRust type
Int32i32
Int64i64
Floatf32
Doublef64
Boolbool
Pointer<Utf8>*const c_char
Pointer<T>*mut T

Kotlin/Native

Dart FFI typeKotlin type
Int32Int
Int64Long
FloatFloat
DoubleDouble
BoolBoolean
Pointer<Utf8>CPointer<ByteVar>?
Pointer<Int32>CPointer<IntVar>?

Swift

Dart FFI typeSwift type
Int32Int32
Int64Int64
FloatFloat
DoubleDouble
BoolBool
Pointer<Utf8>UnsafePointer<CChar>?
Pointer<Utf8>UnsafeMutablePointer<CChar>? (out)

Source

Source code: phanithlim/dart-native

Everything lives under native/, one directory per language, each with its own README and build steps. Clone it, build one of the libraries, and the Dart side will call straight into it.

© 2026 Phanith LIM