FFI: basisbeginselen, Foreign Function Interface en integratie met C

Auteur: IT Sectr Gepubliceerd: 2026-06-05 Leestijd: 10 min

FFI (Foreign Function Interface) — is een mechanisme van de Dart-taal, geleverd door het dart:ffi-pakket, waarmee functies uit native C-bibliotheken direct kunnen worden aangeroepen, zonder tussenliggende lagen in Kotlin, Swift of Java. De ontwikkelaar laadt een dynamische bibliotheek (.so op Android, .dylib op iOS, .dll op Windows), declareert de handtekeningen van C-functies en roept ze aan als gewone Dart-functies. Volgens Dart API Reference (2025) vermindert FFI de overhead van inter-language calls tot 0.1 μs, wat tientallen keren sneller is dan via Method Channel.

Belangrijkste punten

  • FFI (Foreign Function Interface) — mechanisme voor direct aanroepen van C-functies vanuit Dart
  • dart:ffi biedt API voor het laden van bibliotheken en declareren van handtekeningen
  • Prestaties van aanroepen via FFI zijn 50–100 keer hoger dan via Method Channel
  • Typering FFI ondersteunt primitieve typen, structuren en C-pointers
  • Flutter gebruikt FFI voor integratie met native bibliotheken: OpenCV, SQLite, FFmpeg

Wat is FFI?

FFI (Foreign Function Interface) — is een mechanisme waarmee een programmeertaal functies kan aanroepen die in andere talen zijn geschreven. In de context van Dart en Flutter betekent FFI de mogelijkheid om functies uit C/C++-bibliotheken rechtstreeks vanuit Dart-code aan te roepen, zonder platformcode te hoeven schrijven in Java (Android) of Swift/Objective-C (iOS).

Het dart:ffi-pakket verscheen in Dart 2.12 (2021) en is sindsdien een belangrijk hulpmiddel geworden voor integratie van Flutter met native code. Vóór dart:ffi was de enige manier om een C-functie vanuit Dart aan te roepen Method Channel — een asynchroon mechanisme dat berichten via JSON-serialisatie tussen Dart en de native kant verzond. FFI werkt anders: Dart-code heeft rechtstreeks toegang tot het geheugen van de C-bibliotheek en roept functies aan via native ABI (Application Binary Interface) zonder serialisatie en zonder contextwisseling.

FFI is vooral gewild in scenario's waar prestaties kritisch zijn: beeldverwerking (OpenCV), audio (FFmpeg), cryptografie (OpenSSL), machine learning (TensorFlow Lite) en databases (SQLite). In al deze gevallen creëert Method Channel onaanvaardbare vertragingen, terwijl FFI prestaties biedt die vergelijkbaar zijn met native C/C++-code. De bibliotheek dart:ffi ondersteunt ook het werken met geheugen: toewijzing, vrijgave en beheer van pointers.

FFI vs Method Channel: fundamenteel verschil

Method Channel werkt asynchroon: Dart stuurt een bericht naar de native code, de native code verwerkt het en stuurt het resultaat terug. Elke aanroep vereist serialisatie van argumenten naar Map, overdracht via een wachtrij en deserialisatie. Dit duurt 0.5–5 ms per aanroep. FFI werkt synchroon en zonder serialisatie — een C-functieaanroep duurt 0.01–0.1 μs. Verschil van 50–500 keer, wat cruciaal is voor hoogfrequente bewerkingen.

Hoe werkt dart:ffi?

Werken met dart:ffi bestaat uit drie fasen: laden van de bibliotheek, declareren van handtekeningen en aanroepen van functies. Elke fase gebruikt de strikte typering van Dart, wat fouten tijdens runtime minimaliseert.

In de eerste fase wordt de dynamische bibliotheek geladen via de klasse DynamicLibrary. De bibliotheek kan op naam worden geladen (libxyz.so, libxyz.dylib, xyz.dll) of via een volledig pad. Dart zoekt automatisch naar de bibliotheek in de standaard systeempaden. DynamicLibrary biedt de methode lookupFunction, die een Dart-functie koppelt aan een C-functie op basis van de symboolnaam.

In de tweede fase wordt een Dart-functie gedeclareerd met typeannotaties die overeenkomen met de C-handtekening. Hiervoor worden speciale typen uit dart:ffi gebruikt: Int32, Float, Double, Pointer, NativeFunction, Handle en andere. De annotatie lookupFunction accepteert twee generieke parameters: het type van de Dart-functie (hoe deze er in Dart uitziet) en het type van de native C-functie (zoals gedeclareerd in C).

In de derde fase wordt de gegenereerde Dart-functie aangeroepen als een gewone functie. Argumenten worden rechtstreeks doorgegeven, het resultaat wordt onmiddellijk geretourneerd. Als de C-functie het geheugen via pointers wijzigt, kan Dart deze wijzigingen lezen via de klasse Pointer. Geheugenbeheer aan de C-kant blijft de verantwoordelijkheid van de ontwikkelaar — dart:ffi beheert geen geheugen dat is toegewezen door malloc in C.

Basisvoorbeeld: een C-functie aanroepen vanuit Dart

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

// C-functiedeclaratie: 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 dit voorbeeld is add een C-functie die twee int ontvangt en een int retourneert. typedef AddNative beschrijft de C-handtekening met dart:ffi-typen, en AddDart — hoe deze functie er in Dart uitziet. lookupFunction koppelt ze en retourneert een Dart-functie die als een gewone kan worden aangeroepen.

Gegevenstypen in FFI

dart:ffi biedt een reeks typen die overeenkomen met C-typen. Elk type heeft een vaste grootte en conversieregels tussen Dart en C. Inzicht in de typeovereenkomst is cruciaal voor correcte werking van FFI — een fout in de grootte of het teken van een type kan leiden tot een crash van de applicatie.

C-typedart:ffi-typeDart-typeGrootte (bytes)
intInt32int4
longInt64int8
floatFloatdouble4
doubleDoubledouble8
char*Pointer<Int8>Pointer8 (pointer)
void*Pointer<Void>Pointer8 (pointer)
structPointer<T> (Struct)Pointerafhankelijk van velden

Voor het werken met C-strings (char*) gebruikt dart:ffi Pointer<Int8>. Conversie van Dart String naar C char* en omgekeerd wordt uitgevoerd via de methoden toNativeUtf8 (uit het ffi-pakket) en fromUtf8. Het is belangrijk om C-strings na gebruik vrij te geven via calloc.free om geheugenlekken te voorkomen.

Structuren (Struct)

dart:ffi ondersteunt het declareren van C-structuren als Dart-klassen die overerven van Struct. Velden van de structuur worden gedeclareerd met annotaties @Int32(), @Float(), @Array() en andere. De grootte en offset van velden worden automatisch berekend volgens de ABI van het platform. Pointer<Point> kan worden verkregen uit een C-functie die een pointer naar een structuur retourneert of worden toegewezen in Dart via calloc.

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

    @Int32()
    external int y
}

// C-functie aanroepen die Point* retourneert
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) // geheugen vrijmaken

De klasse Point erft van Struct en declareert de velden x en y met annotaties @Int32(). De gegenereerde C-code heeft exact dezelfde veldindeling in het geheugen. Pointer.ref biedt toegang tot de velden van de structuur via getters en setters.

Praktische voorbeelden van FFI

Laten we een complexer voorbeeld bekijken — integratie met een C-bibliotheek voor het berekenen van SHA256-hash. Dit is een typische taak waarbij FFI een aanzienlijk prestatievoordeel biedt ten opzichte van Method Channel.

Integratie met OpenSSL via FFI

De OpenSSL-bibliotheek biedt de functie SHA256 die de hash van een string berekent. Via dart:ffi kunnen we deze direct aanroepen, zonder Java- of Swift-wrappers te schrijven. Dit is een voorbeeld van hoe FFI het mogelijk maakt om bestaande C-bibliotheken in Flutter te hergebruiken.

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

// Handtekening: 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 dit voorbeeld laadt de functie sha256 de bibliotheek libcrypto.so, vindt het symbool SHA256 en roept het aan met pointers naar invoer- en uitvoergegevens. toNativeUtf8 converteert Dart String naar C-string (wijst geheugen toe), en asTypedList maakt het mogelijk de byte-array van het resultaat te lezen. Geheugen wordt na gebruik vrijgegeven — dit is een verplichte stap om lekken te voorkomen.

Geheugentoewijzing en -vrijgave

Het ffi-pakket biedt de functie calloc voor het toewijzen van C-compatibel geheugen. Toegewezen geheugen moet worden vrijgegeven via calloc.free, anders treedt er een lek op. Voor automatisch geheugenbeheer kan de klasse Arena uit het ffi-pakket worden gebruikt, die al het daarin toegewezen geheugen vrijgeeft bij aanroep van arena.release(). Dit is vooral handig bij een groot aantal tijdelijke toewijzingen.

Beperkingen en best practices

Ondanks de kracht van FFI heeft het beperkingen waarmee rekening moet worden gehouden bij het ontwerpen van de architectuur van een Flutter-applicatie. De belangrijkste beperkingen hebben betrekking op typeveiligheid, geheugenbeheer en platformcompatibiliteit.

Veiligheid

FFI controleert typen niet tijdens runtime. Als een C-functie een pointer verwacht en er wordt een getal doorgegeven, crasht de applicatie met een segmentation fault. Het wordt aanbevolen om FFIgen te gebruiken — een tool die typeveilige Dart-wrappers genereert op basis van C-headers (.h-bestanden). FFIgen analyseert declaraties van C-functies en maakt Dart-code met correcte typen, waardoor fouten in de schrijffase worden geëlimineerd.

Platformcompatibiliteit

Namen en paden naar dynamische bibliotheken verschillen per platform: libxyz.so op Android/Linux, libxyz.dylib op iOS/macOS, xyz.dll op Windows. Voor cross-platform bibliotheken wordt conditionele compilatie gebruikt via dart:io (Platform.isAndroid, Platform.isIOS) of abstracties zoals package:ffi. Het wordt aanbevolen om een factory-methode te maken die de juiste bibliotheek voor het huidige platform retourneert.

Geheugenbeheer

FFI beheert geen geheugen aan de C-kant. Als een C-functie geheugen toewijst via malloc, moet dit worden vrijgegeven via free, anders treedt een lek op. In Dart is er geen garbage collector voor C-geheugen. Aanbeveling: geef geheugen altijd vrij in dezelfde methode waar het is toegewezen, of gebruik Arena voor groepsgewijze vrijgave.

Prestaties en threads

FFI-aanroepen worden uitgevoerd in dezelfde thread als de Dart-code. Langdurige synchrone bewerkingen (meer dan 10 ms) blokkeren de UI-thread en veroorzaken framedrops. Voor langdurige bewerkingen moet de C-functie in een isolaat (Isolate) worden aangeroepen of moet ervoor worden gezorgd dat de C-functie werk in een achtergrondthread start en Dart via een Port of callback op de hoogte stelt.

dart
// FFI in isolaat voor lange bewerkingen
import 'dart:isolate'

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

    return await port.first as String
}

Het verplaatsen van een FFI-aanroep naar een isolaat garandeert dat de UI-thread niet wordt geblokkeerd. Het overbrengen van grote hoeveelheden gegevens tussen isolaten vereist echter het kopiëren van geheugen. Voor grote buffers (>10 MB) heeft het gebruik van ShareMemory of memory-mapped files de voorkeur.

Veelgestelde vragen

Hoe verschilt FFI van Method Channel?

FFI roept C-functies direct, synchroon en zonder serialisatie aan — vertraging 0.01–0.1 μs. Method Channel werkt asynchroon via JSON-serialisatie met een vertraging van 0.5–5 ms. FFI is geschikt voor hoogwaardige bewerkingen, Method Channel voor eenvoudige platform-API-aanroepen.

Kunnen C++-functies via FFI worden aangeroepen?

Direct — nee, dart:ffi ondersteunt alleen C-functies. Om C++ aan te roepen moet een C-wrapper met extern "C" worden gemaakt (entry points die als C-symbolen worden geëxporteerd). C++-klassen vereisen een extra laag die methode-aanroepen converteert naar C-functies.

Hoe fouten in C-functies afhandelen?

FFI ondersteunt geen uitzonderingen — als een C-functie een foutcode retourneert, moet deze handmatig worden gecontroleerd. Het wordt aanbevolen FFI-aanroepen in try-catch in Dart te wrappen en de retourcodes van C-functies te controleren. Kritieke fouten (segfault) kunnen niet worden opgevangen.

Welke bibliotheken kunnen niet via FFI worden gebruikt?

FFI werkt niet met bibliotheken die complexe initialisatie van Java (JNI) of Objective-C (Message Dispatch) vereisen. Bijvoorbeeld UIKit en Android Views zijn niet toegankelijk via FFI. Beperking houdt verband met het feit dat FFI werkt op C ABI-niveau, terwijl deze API's specifieke runtimes vereisen.

Moeten C-bibliotheken voor elk platform afzonderlijk worden gecompileerd?

Ja, C-bibliotheken worden voor elk doelplatform afzonderlijk gecompileerd. Voor Android wordt .so gebouwd voor verschillende ABI's (armeabi-v7a, arm64-v8a, x86_64). Voor iOS — een universele .dylib (arm64). Voor Windows — .dll. Flutter verpakt automatisch de juiste versie van de bibliotheek tijdens het bouwen.

Samenvatting

  • FFI (Foreign Function Interface) — mechanisme voor direct aanroepen van C-functies vanuit Dart via dart:ffi
  • Prestaties van FFI-aanroepen zijn 50–500 keer hoger dan via Method Channel
  • Architectuur omvat laden van bibliotheek, declareren van handtekeningen en aanroepen van functies
  • Gegevenstypen dart:ffi ondersteunt Int32, Float, Double, Pointer, Struct en andere C-typen
  • Geheugen aan C-kant wordt handmatig beheerd via calloc/free of Arena
  • Beperkingen FFI: geen runtime typecontrole, geen directe C++-ondersteuning, blokkeert UI-thread
  • Gebruik FFI voor hoogwaardige integratie met native bibliotheken in Flutter

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook