Mobilutveckling med Flutter · Lektion

Anropa C-bibliotek med dart:ffi

Anslut till inbyggda delade bibliotek och överför strukturer och pekare via dart:ffi.

Lektion 1 av 413 steg

Anropa C-bibliotek med dart:ffi är en gratis lektion i Mobilutveckling med Flutter på CoddyKit. Detta är lektion 1 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Mobilutveckling med Flutter, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Mobilutveckling med Flutter innehåller totalt 4 lektioner.

Varför dart:ffi?

dart:ffi är Darts Foreign Function Interface. Det låter Flutter-appen anropa funktioner direkt i inbyggda delade C-bibliotek (.so, .dylib, .dll eller iOS-processavbildningen), utan någon tur och retur via en plattformskanal.

  • Synkront som standard och med mycket liten overhead, till skillnad från MethodChannel, som serialiserar meddelanden över en asynkron gräns.
  • Idealiskt för CPU-intensiv kod, befintliga C/C++/Rust-bibliotek och API:er på OS-nivå (sqlite, libsodium, bildkodekar).
  • Ni kopplar en C-signatur till en Dart-signatur och anropar den sedan som en vanlig funktion.

Kostnaden är att ni själva måste hantera minne och typer. Om en pekare eller en struct-layout blir fel kraschar hela processen.

Öppna ett DynamicLibrary

Allt börjar med ett DynamicLibrary. Det är handtaget till den inlästa inbyggda koden, där ni slår upp symboler.

  • DynamicLibrary.open(path) läser in ett delat bibliotek utifrån filnamnet. På Android använder ni 'libfoo.so'; på iOS/macOS är koden vanligtvis statiskt länkad, så använd DynamicLibrary.process() eller DynamicLibrary.executable().
  • Välj rätt namn för varje plattform med Platform.isAndroid / Platform.isIOS.
import 'dart:ffi';
import 'dart:io' show Platform;

DynamicLibrary openNativeLib() {
  if (Platform.isAndroid) {
    return DynamicLibrary.open('libnative_math.so');
  }
  if (Platform.isIOS || Platform.isMacOS) {
    // Symbols are linked into the app process on iOS.
    return DynamicLibrary.process();
  }
  if (Platform.isWindows) {
    return DynamicLibrary.open('native_math.dll');
  }
  return DynamicLibrary.open('libnative_math.so');
}

Inbyggda typer kontra Dart-typer

FFI använder två typvärldar. Den inbyggda typen beskriver C-ABI:t, medan Dart-typen är den som Dart-koden faktiskt ser.

  • Int32, Int64, Uint8, Double, Float är inbyggda markörtyper — ni instansierar dem aldrig; de mappas till Dart-typerna int/double.
  • Pointer<T> är en inbyggd adress. Void markerar att inget värde finns.
  • C-funktionstypen skrivs med Function och använder inbyggda typer; typen som exponeras mot Dart använder vanliga Dart-typer.

Exempel: C int32_t add(int32_t, int32_t) blir den inbyggda typen Int32 Function(Int32, Int32) och Dart-typen int Function(int, int).

Slå upp och anropa en funktion

Använd lookupFunction för att binda en C-symbol till en Dart-funktion i ett enda anrop. Den tar två generiska parametrar: den inbyggda signaturen och Dart-signaturen.

  • Det första typargumentet måste använda inbyggda typer (Int32, Double, …).
  • Det andra är den anropbara Dart-typ som returneras till er.

Nedanstående simulering i ren Dart visar den anropsform som FFI speglar vid körning.

// Conceptually, FFI does this:
//   typedef NativeAdd = Int32 Function(Int32, Int32);
//   typedef DartAdd   = int Function(int, int);
//   final add = lib.lookupFunction<NativeAdd, DartAdd>('add');

// Pure-Dart stand-in so the call site is identical in shape:
int Function(int, int) bindAdd() {
  return (int a, int b) => a + b; // native impl returns a + b
}

void main() {
  final add = bindAdd();
  print('add(20, 22) = ${add(20, 22)}');
}

typedef för tydliga bindningar

I riktiga bindningar deklarerar ni de två signaturerna som typedefs. Det gör lookupFunction lättare att läsa och låter er återanvända signaturerna.

  • Den inbyggda typedefen använder inbyggda markörtyper och suffixkonventionen ...Native.
  • Dart-typedefen använder Dart-typer.
  • Strängen som skickas till lookupFunction är det exakta namnet på den exporterade C-symbolen.
import 'dart:ffi';

// C: double native_pow(double base, int32_t exp);
typedef NativePowNative = Double Function(Double, Int32);
typedef NativePow = double Function(double, int);

class MathBindings {
  final DynamicLibrary lib;
  late final NativePow pow;

  MathBindings(this.lib) {
    pow = lib.lookupFunction<NativePowNative, NativePow>('native_pow');
  }
}

Allokera inbyggt minne

För att skicka pekare måste ni allokera inbyggt minne (utanför heapen). Biblioteket package:ffi tillhandahåller malloc (det finns även en variant som heter calloc) samt tillägg för strängar.

  • malloc<Int32>() returnerar en Pointer<Int32>; använd .value för att läsa och skriva.
  • malloc<Int32>(n) allokerar en array med n element; indexera med ptr[i] eller ptr.elementAt(i).
  • Ni måste frigöra det ni allokerar med malloc.free(ptr) — GC spårar inte inbyggt minne.
import 'dart:ffi';
import 'package:ffi/ffi.dart';

void usePointer() {
  final ptr = malloc<Int32>(3); // array of 3 int32
  try {
    ptr[0] = 10;
    ptr[1] = 20;
    ptr[2] = 12;
    var sum = 0;
    for (var i = 0; i < 3; i++) {
      sum += ptr[i];
    }
    print('sum = $sum');
  } finally {
    malloc.free(ptr); // always free
  }
}

Konvertera strängar

C-strängar är nollterminerade char* och representeras som Pointer<Utf8> (från package:ffi). Konverteringen fungerar åt båda hållen:

  • Dart → C: myString.toNativeUtf8() allokerar en inbyggd buffert (frigör den senare).
  • C → Dart: ptr.toDartString() kopierar byten till en Dart-String.

Om den inbyggda funktionen returnerar en pekare som den själv har allokerat måste ni vanligtvis anropa motsvarande exporterade free-funktion — använd aldrig malloc.free på minne som ni inte har allokerat med malloc.

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

// C: int32_t count_chars(const char* text);
typedef CountNative = Int32 Function(Pointer<Utf8>);
typedef Count = int Function(Pointer<Utf8>);

int countChars(Count nativeCount, String text) {
  final cStr = text.toNativeUtf8();
  try {
    return nativeCount(cStr);
  } finally {
    malloc.free(cStr);
  }
}

Definiera en Struct

För att konvertera C-structar deklarerar ni en Dart-klass som utökar Struct. Varje fält annoteras med sin inbyggda typ, så att FFI-körmiljön kan beräkna den exakta minneslayouten och offsetarna.

  • Skalära fält får annoteringar som @Int32() och @Double().
  • Fältens ordning och typer måste exakt överensstämma med C-structen, inklusive reglerna för utfyllnad och justering.
  • Ni skapar aldrig en Struct med new; ni hämtar en via en Pointer<T>.ref som stöds av inbyggt minne.
import 'dart:ffi';

// C:
// typedef struct { double x; double y; } Point;
final class Point extends Struct {
  @Double()
  external double x;

  @Double()
  external double y;
}

Skicka structar via pekare

De flesta C-API:er tar emot en Point*. Allokera structen, fyll i den via .ref, skicka pekaren och läs sedan tillbaka resultaten.

  • malloc<Point>() ger en Pointer<Point> med rätt storlek för layouten.
  • ptr.ref är en vy över det inbyggda minnet; när ni skriver ptr.ref.x = 3.0 ändras C-structen direkt i minnet.
  • Den inbyggda funktionen läser och skriver i samma minne — det är så ni får ut värden via referens.
import 'dart:ffi';
import 'package:ffi/ffi.dart';

// C: void translate(Point* p, double dx, double dy);
typedef TranslateNative = Void Function(Pointer<Point>, Double, Double);
typedef Translate = void Function(Pointer<Point>, double, double);

final class Point extends Struct {
  @Double()
  external double x;
  @Double()
  external double y;
}

void moveOrigin(Translate translate) {
  final p = malloc<Point>();
  try {
    p.ref.x = 0;
    p.ref.y = 0;
    translate(p, 4.0, 5.0);
    print('moved to (${p.ref.x}, ${p.ref.y})');
  } finally {
    malloc.free(p);
  }
}

Blockera inte UI-tråden

FFI-anrop är synkrona: de körs på tråden för den isolate som anropar dem. En långvarig inbyggd beräkning som anropas från huvudisolaten fryser Flutter-gränssnittet.

  • För tungt arbete kör ni FFI-anropet i en Isolate (till exempel med Isolate.run i moderna Dart-versioner) eller i en worker-isolate.
  • Observera att ett DynamicLibrary-handtag och inbyggda pekare kan skickas mellan isolater som adresser, men varje isolate måste öppna biblioteket igen eller dela det med försiktighet — behandla pekare som vanliga heltal över gränserna.
  • Inbyggd kod som anropar tillbaka till Dart måste använda NativeCallable / send ports, inte godtyckliga trådar.
import 'dart:isolate';

// Simulates offloading a heavy native FFI computation off the UI thread.
int _heavyNativeWork(int n) {
  var acc = 0;
  for (var i = 0; i < n; i++) {
    acc = (acc + i) % 1000003;
  }
  return acc;
}

Future<void> main() async {
  final result = await Isolate.run(() => _heavyNativeWork(5000000));
  print('result = $result');
}

Minnessäkerhet och ägarskap

FFI-buggar leder till processkrascher, inte undantag. Disciplin är viktigt:

  • Ägarskap: den som allokerar måste frigöra. Minne från malloc → malloc.free. Minne från ett C-bibliotek → destruktorn som exporteras av det biblioteket.
  • Omslut allokering, användning och frigöring i try/finally, så att minnet frigörs även vid fel.
  • För långlivade inbyggda objekt kan ni koppla en NativeFinalizer så att destruktorn körs när Dart-wrappen samlas in av GC.
  • Läs aldrig .ref/.value från en pekare efter att den har frigjorts — det är use-after-free.
import 'dart:ffi';
import 'package:ffi/ffi.dart';

class SafeBuffer {
  final Pointer<Uint8> ptr;
  final int length;
  SafeBuffer(this.length) : ptr = malloc<Uint8>(length);

  void dispose() => malloc.free(ptr);
}

void main() {
  final buf = SafeBuffer(16);
  try {
    buf.ptr[0] = 255;
    print('first byte = ${buf.ptr[0]}');
  } finally {
    buf.dispose();
  }
}

Snabbkontroll

Svara utifrån reglerna för structar och minne i dart:ffi.

Sammanfattning

Ni kan nu binda Flutter till inbyggda C-bibliotek med dart:ffi:

  • Läs in kod med DynamicLibrary.open / .process() och välj sökväg per plattform.
  • Bind symboler med lookupFunction<Native, Dart> och deklarera inbyggda typedefar respektive Dart-typedefar.
  • Allokera minne utanför heapen med malloc, konvertera strängar via toNativeUtf8 / toDartString och skicka arrayer som pekare.
  • Definiera structar genom att utöka Struct med annoteringar för inbyggda typer; skicka dem via pekare och läs resultat via .ref.
  • Håll tunga anrop borta från UI-isolaten och upprätthåll strikt ägarskap: frigör det ni allokerar, använd try/finally och NativeFinalizer, och använd aldrig frigjorda pekare.

FFI byter säkerhet mot snabbhet och räckvidd — korrekta typer, layouter och livslängder är helt och hållet ert ansvar.

Gratis att börja

Lär dig Dart med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
22
Lektioner
88

Vanliga frågor

Är lektionen ”Anropa C-bibliotek med dart:ffi” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Mobilutveckling med Flutter, inklusive ”Anropa C-bibliotek med dart:ffi”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Mobilutveckling med Flutter innehåller totalt 4 lektioner.

Vad lär jag mig i ”Anropa C-bibliotek med dart:ffi”?

Anslut till inbyggda delade bibliotek och överför strukturer och pekare via dart:ffi. Ni övar på Mobilutveckling med Flutter med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Mobilutveckling med Flutter?

Du behöver inga förkunskaper. Utbildningen i Mobilutveckling med Flutter på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 1 av 4.

Hur lång tid tar lektionen ”Anropa C-bibliotek med dart:ffi”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Mobilutveckling med Flutter-lektionen?

Ja. Varje Mobilutveckling med Flutter-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Anropa C-bibliotek med dart:ffi
  2. Typsäkra plattformskanaler med Pigeon
  3. Skriv anpassade plattformspluginer för iOS och Android
  4. Bakgrundsisolate och hantering av inbyggt minne
← Tillbaka till Mobilutveckling med Flutter