FFI: Grundlagen, Foreign Function Interface und C-Integration

Autor: IT Sectr Veröffentlicht: 2026-06-05 Lesezeit: 10 Min.

FFI (Foreign Function Interface) ist ein Mechanismus der Sprache Dart, bereitgestellt durch das Paket dart:ffi, der es ermöglicht, Funktionen aus nativen C-Bibliotheken direkt aufzurufen, ohne Zwischenschichten in Kotlin, Swift oder Java. Der Entwickler lädt eine dynamische Bibliothek (.so unter Android, .dylib unter iOS, .dll unter Windows), deklariert die Signaturen der C-Funktionen und ruft sie wie normale Dart-Funktionen auf. Laut Dart API Reference (2025) reduziert FFI den Overhead sprachübergreifender Aufrufe auf 0,1 µs, was dutzende Male schneller ist als über Method Channel.

Wichtigste Erkenntnisse

  • FFI (Foreign Function Interface) — ein Mechanismus zum direkten Aufruf von C-Funktionen aus Dart
  • dart:ffi bietet eine API zum Laden von Bibliotheken und Deklarieren von Signaturen
  • Leistung von FFI-Aufrufen ist 50–100 Mal höher als über Method Channel
  • Typisierung FFI unterstützt primitive Typen, Strukturen und C-Zeiger
  • Flutter verwendet FFI zur Integration mit nativen Bibliotheken: OpenCV, SQLite, FFmpeg

Was ist FFI?

FFI (Foreign Function Interface) ist ein Mechanismus, der es einer Programmiersprache ermöglicht, Funktionen aufzurufen, die in anderen Sprachen geschrieben sind. Im Kontext von Dart und Flutter bedeutet FFI die Möglichkeit, Funktionen aus C/C++-Bibliotheken direkt aus Dart-Code aufzurufen, ohne plattformspezifischen Code in Java (Android) oder Swift/Objective-C (iOS) schreiben zu müssen.

Das Paket dart:ffi erschien in Dart 2.12 (2021) und ist seitdem ein zentrales Werkzeug zur Integration von Flutter mit nativem Code geworden. Vor dart:ffi war der einzige Weg, eine C-Funktion aus Dart aufzurufen, der Method Channel — ein asynchroner Mechanismus, der Nachrichten über JSON-Serialisierung zwischen Dart und der nativen Seite übermittelte. FFI funktioniert anders: Dart-Code greift direkt auf den Speicher der C-Bibliothek zu und ruft Funktionen über die native ABI (Application Binary Interface) ohne Serialisierung oder Kontextwechsel auf.

FFI ist besonders in Szenarien gefragt, in denen die Leistung entscheidend ist: Bildverarbeitung (OpenCV), Audio (FFmpeg), Kryptographie (OpenSSL), maschinelles Lernen (TensorFlow Lite) und Datenbanken (SQLite). In all diesen Fällen erzeugt der Method Channel inakzeptable Verzögerungen, während FFI eine Leistung bietet, die mit nativem C/C++-Code vergleichbar ist. Die dart:ffi-Bibliothek unterstützt auch die Speicherverwaltung: Zuweisung, Freigabe und Zeigeroperationen.

FFI vs Method Channel: grundlegender Unterschied

Der Method Channel arbeitet asynchron: Dart sendet eine Nachricht an den nativen Code, der native Code verarbeitet sie und sendet das Ergebnis zurück. Jeder Aufruf erfordert die Serialisierung der Argumente in eine Map, die Übergabe über eine Warteschlange und die Deserialisierung. Dies dauert 0,5–5 ms pro Aufruf. FFI arbeitet synchron und ohne Serialisierung — ein C-Funktionsaufruf dauert 0,01–0,1 µs. Ein Unterschied von 50–500 Mal, der für hochfrequente Operationen entscheidend ist.

Wie funktioniert dart:ffi?

Die Arbeit mit dart:ffi besteht aus drei Schritten: Laden der Bibliothek, Deklarieren der Signaturen und Aufrufen der Funktionen. Jeder Schritt verwendet die strenge Typisierung von Dart, wodurch Laufzeitfehler minimiert werden.

Im ersten Schritt wird die dynamische Bibliothek über die Klasse DynamicLibrary geladen. Die Bibliothek kann nach Namen (libxyz.so, libxyz.dylib, xyz.dll) oder vollständigem Pfad geladen werden. Dart sucht automatisch in den Standardpfaden des Systems nach der Bibliothek. DynamicLibrary stellt die Methode lookupFunction bereit, die eine Dart-Funktion an eine C-Funktion per Symbolnamen bindet.

Im zweiten Schritt wird eine Dart-Funktion mit Typannotationen deklariert, die der C-Signatur entsprechen. Es werden spezielle Typen aus dart:ffi verwendet: Int32, Float, Double, Pointer, NativeFunction, Handle und andere. Die Annotation lookupFunction nimmt zwei generische Parameter entgegen: den Dart-Funktionstyp (wie sie in Dart aussehen wird) und den nativen C-Funktionstyp (wie sie in C deklariert ist).

Im dritten Schritt wird die erzeugte Dart-Funktion wie eine normale Funktion aufgerufen. Argumente werden direkt übergeben, das Ergebnis wird sofort zurückgegeben. Wenn die C-Funktion Speicher über Zeiger verändert, kann Dart diese Änderungen über die Pointer-Klasse lesen. Die Speicherverwaltung auf der C-Seite bleibt in der Verantwortung des Entwicklers — dart:ffi verwaltet nicht den mit malloc in C zugewiesenen Speicher.

Basissbeispiel: Aufruf einer C-Funktion aus Dart

dart
import 'dart:ffi'
import 'package:ffi/ffi.dart'

// C-Funktionsdeklaration: int add(int a, int b)
typedef AddNative = Int32 Function(Int32, Int32)
typedef AddDart = int Function(int, int)

void main() {
    final lib = DynamicLibrary.open('libcalculator.so')
    final AddDart add = lib
        .lookupFunction<AddNative, AddDart>('add')

    print(add(5, 3)) // 8
}

In diesem Beispiel ist add eine C-Funktion, die zwei ints nimmt und einen int zurückgibt. Der typedef AddNative beschreibt die C-Signatur mit dart:ffi-Typen, während AddDart beschreibt, wie diese Funktion in Dart aussehen wird. lookupFunction bindet sie und gibt eine Dart-Funktion zurück, die wie eine normale aufgerufen werden kann.

Datentypen in FFI

dart:ffi bietet eine Reihe von Typen, die den C-Typen entsprechen. Jeder Typ hat eine feste Größe und Konvertierungsregeln zwischen Dart und C. Das Verständnis der Typzuordnung ist für die korrekte Funktion von FFI entscheidend — ein Fehler in der Größe oder im Vorzeichen eines Typs kann zum Absturz der Anwendung führen.

C-Typdart:ffi-TypDart-TypGröße (Bytes)
intInt32int4
longInt64int8
floatFloatdouble4
doubleDoubledouble8
char*Pointer<Int8>Pointer8 (Zeiger)
void*Pointer<Void>Pointer8 (Zeiger)
structPointer<T> (Struct)Pointerabhängig von Feldern

Für die Arbeit mit C-Strings (char*) verwendet dart:ffi Pointer<Int8>. Die Konvertierung von Dart String in C char* und zurück erfolgt über toNativeUtf8 (aus dem Paket ffi) und fromUtf8. Es ist wichtig, C-Strings nach der Verwendung über calloc.free freizugeben, um Speicherlecks zu vermeiden.

Strukturen (Struct)

dart:ffi unterstützt die Deklaration von C-Strukturen als Dart-Klassen, die Struct erweitern. Die Felder der Struktur werden mit Annotationen @Int32(), @Float(), @Array() und anderen deklariert. Die Größe und der Offset der Felder werden automatisch entsprechend der ABI der Plattform berechnet. Pointer<Point> kann von einer C-Funktion bezogen werden, die einen Zeiger auf eine Struktur zurückgibt, oder in Dart über calloc zugewiesen werden.

dart
// C-Struct: typedef struct { int x; int y; } Point;
final class Point extends Struct {
    @Int32()
    external int x

    @Int32()
    external int y
}

// C-Funktion aufrufen, die Point* zurückgibt
typedef CreatePointNative = Pointer<Point> Function(Int32, Int32)
typedef CreatePointDart = Pointer<Point> Function(int, int)

final Pointer<Point> p = createPoint(10, 20)
print('x: ${p.ref.x}, y: ${p.ref.y}')
calloc.free(p) // Speicher freigeben

Die Klasse Point erweitert Struct und deklariert die Felder x und y mit @Int32()-Annotationen. Der generierte C-Code wird exakt dasselbe Speicherlayout haben. Pointer.ref bietet Zugriff auf die Strukturfelder über Getter und Setter.

Praktische FFI-Beispiele

Betrachten wir ein komplexeres Beispiel — die Integration mit einer C-Bibliothek zur Berechnung des SHA256-Hashs. Dies ist eine typische Aufgabe, bei der FFI im Vergleich zu Method Channel einen erheblichen Leistungsgewinn bietet.

Integration mit OpenSSL über FFI

Die OpenSSL-Bibliothek stellt die Funktion SHA256 bereit, die den Hash eines Strings berechnet. Über dart:ffi können wir sie direkt aufrufen, ohne Java- oder Swift-Wrapper schreiben zu müssen. Dies ist ein Beispiel dafür, wie FFI die Wiederverwendung vorhandener C-Bibliotheken in Flutter ermöglicht.

dart
import 'dart:ffi'
import 'package:ffi/ffi.dart'

// Signatur: unsigned char* SHA256(
//   const unsigned char *d, size_t n, unsigned char *md)
typedef Sha256Native = Pointer<Uint8> Function(
    Pointer<Uint8>, Size, Pointer<Uint8>)
typedef Sha256Dart = Pointer<Uint8> Function(
    Pointer<Uint8>, int, Pointer<Uint8>)

String sha256(String input) {
    final lib = DynamicLibrary.open('libcrypto.so')
    final Sha256Dart sha256Fn = lib
        .lookupFunction<Sha256Native, Sha256Dart>('SHA256')

    final inputPtr = input.toNativeUtf8()
    final outputPtr = calloc(Uint8)(32) // SHA256 = 32 Bytes

    sha256Fn(inputPtr, input.length, outputPtr)

    final digest = outputPtr.asTypedList(32)
    final hex = digest.map((b) => b.toRadixString(16)
        .padLeft(2, '0')).join()

    calloc.free(inputPtr)
    calloc.free(outputPtr)

    return hex
}

In diesem Beispiel lädt die Funktion sha256 die Bibliothek libcrypto.so, findet das Symbol SHA256 und ruft es mit Zeigern auf Eingabe- und Ausgabedaten auf. toNativeUtf8 konvertiert einen Dart String in einen C-String (weist Speicher zu), und asTypedList ermöglicht das Lesen des Ergebnis-Byte-Arrays. Der Speicher wird nach der Verwendung freigegeben — dies ist ein obligatorischer Schritt zur Verhinderung von Lecks.

Speicherzuweisung und -freigabe

Das Paket ffi bietet die Funktion calloc zur Zuweisung von C-kompatiblem Speicher. Zugewiesener Speicher muss über calloc.free freigegeben werden, andernfalls tritt ein Leck auf. Für die automatische Speicherverwaltung können Sie die Klasse Arena aus dem Paket ffi verwenden, die den gesamten darin zugewiesenen Speicher freigibt, wenn arena.release() aufgerufen wird. Dies ist besonders bei einer großen Anzahl temporärer Zuweisungen praktisch.

Einschränkungen und Best Practices

Trotz der Leistungsfähigkeit von FFI gibt es Einschränkungen, die bei der Gestaltung der Architektur einer Flutter-Anwendung berücksichtigt werden müssen. Die wichtigsten Einschränkungen betreffen die Typsicherheit, die Speicherverwaltung und die Plattformkompatibilität.

Sicherheit

FFI führt keine Laufzeittypüberprüfung durch. Wenn eine C-Funktion einen Zeiger erwartet, aber eine Zahl erhält, stürzt die Anwendung mit einem Segmentierungsfehler ab. Es wird empfohlen, FFIgen zu verwenden — ein Tool, das typsichere Dart-Wrapper auf Basis von C-Header-Dateien (.h) generiert. FFIgen analysiert C-Funktionsdeklarationen und erstellt Dart-Code mit korrekten Typen, wodurch Fehler in der Code-Erstellungsphase ausgeschlossen werden.

Plattformkompatibilität

Die Namen und Pfade dynamischer Bibliotheken unterscheiden sich je nach Plattform: libxyz.so unter Android/Linux, libxyz.dylib unter iOS/macOS, xyz.dll unter Windows. Für plattformübergreifende Bibliotheken wird bedingte Kompilierung über dart:io (Platform.isAndroid, Platform.isIOS) oder Abstraktionen wie package:ffi verwendet. Es wird empfohlen, eine Factory-Methode zu erstellen, die die richtige Bibliothek für die aktuelle Plattform zurückgibt.

Speicherverwaltung

FFI verwaltet den Speicher auf der C-Seite nicht. Wenn eine C-Funktion Speicher über malloc zuweist, muss er über free freigegeben werden, andernfalls tritt ein Leck auf. Dart hat keinen Garbage Collector für C-Speicher. Empfehlung: Speicher immer in derselben Methode freigeben, in der er zugewiesen wurde, oder Arena zur Gruppenfreigabe verwenden.

Leistung und Threads

FFI-Aufrufe werden im selben Thread wie der Dart-Code ausgeführt. Lange synchrone Operationen (über 10 ms) blockieren den UI-Thread und verursachen Frame-Drops. Für lange Operationen sollte die C-Funktion in einem Isolate aufgerufen werden oder sichergestellt werden, dass die C-Funktion die Arbeit in einem Hintergrundthread ausführt und Dart über Port oder Callback benachrichtigt.

dart
// FFI im Isolate für lange Operationen
import 'dart:isolate'

Future<String> computeHash(String input) async {
    final port = ReceivePort()
    await Isolate.spawn((SendPort sendPort) {
        final result = sha256(input) // FFI-Aufruf
        sendPort.send(result)
    }, port.sendPort)

    return await port.first as String
}

Das Auslagern von FFI-Aufrufen in ein Isolate stellt sicher, dass der UI-Thread nicht blockiert wird. Es ist jedoch zu beachten, dass die Übertragung großer Datenmengen zwischen Isolates Speicherkopien erfordert. Für große Puffer (>10 MB) ist die Verwendung von SharedMemory oder speicherzugeordneten Dateien vorzuziehen.

Häufig gestellte Fragen

Wie unterscheidet sich FFI vom Method Channel?

FFI ruft C-Funktionen direkt, synchron und ohne Serialisierung auf — Latenz 0,01–0,1 µs. Der Method Channel arbeitet asynchron über JSON-Serialisierung mit einer Latenz von 0,5–5 ms. FFI eignet sich für leistungsintensive Operationen, der Method Channel für einfache Plattform-API-Aufrufe.

Kann man C++-Funktionen über FFI aufrufen?

Direkt — nein, dart:ffi unterstützt nur C-Funktionen. Um C++ aufzurufen, muss ein C-Wrapper mit extern „C“ (Einstiegspunkte, die als C-Symbole exportiert werden) erstellt werden. C++-Klassen benötigen eine zusätzliche Schicht, die Methodenaufrufe in C-Funktionen übersetzt.

Wie behandelt man Fehler in C-Funktionen?

FFI unterstützt keine Ausnahmen — wenn eine C-Funktion einen Fehlercode zurückgibt, muss dieser manuell überprüft werden. Es wird empfohlen, FFI-Aufrufe in Dart in try-catch zu verpacken und die Rückgabecodes der C-Funktionen zu überprüfen. Kritische Fehler (Segfault) können nicht abgefangen werden.

Welche Bibliotheken können nicht über FFI verwendet werden?

FFI funktioniert nicht mit Bibliotheken, die eine komplexe Initialisierung von Java (JNI) oder Objective-C (Message Dispatch) erfordern. Beispielsweise sind UIKit und Android Views nicht über FFI zugänglich. Die Einschränkung liegt darin begründet, dass FFI auf C-ABI-Ebene arbeitet, während diese APIs spezifische Laufzeiten benötigen.

Müssen C-Bibliotheken für jede Plattform kompiliert werden?

Ja, C-Bibliotheken werden für jede Zielplattform separat kompiliert. Für Android wird .so für verschiedene ABIs (armeabi-v7a, arm64-v8a, x86_64) erstellt. Für iOS — eine universelle .dylib (arm64). Für Windows — .dll. Flutter verpackt während des Builds automatisch die richtige Version der Bibliothek.

Zusammenfassung

  • FFI (Foreign Function Interface) — Mechanismus zum direkten Aufruf von C-Funktionen aus Dart über dart:ffi
  • Leistung von FFI-Aufrufen ist 50–500 Mal höher als über Method Channel
  • Architektur umfasst Bibliotheksladung, Signaturdeklaration und Funktionsaufrufe
  • Datentypen dart:ffi unterstützt Int32, Float, Double, Pointer, Struct und andere C-Typen
  • Speicher auf der C-Seite wird manuell über calloc/free oder Arena verwaltet
  • Einschränkungen von FFI: keine Laufzeittypüberprüfung, keine direkte C++-Unterstützung, blockiert den UI-Thread
  • Verwenden Sie FFI für leistungsstarke Integration mit nativen Bibliotheken in Flutter

Wir entwickeln eine mobile Applikation schlüsselfertig

IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.

Projekt besprechen

Lesen Sie auch