[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-dart-ffi-in-flutter-chiamare-librerie-c-e-c-con-ffigen":3,"comments-article-dart-ffi-in-flutter-chiamare-librerie-c-e-c-con-ffigen":26},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},72,"Dart FFI in Flutter: chiamare librerie C e C++ con ffigen","dart-ffi-in-flutter-chiamare-librerie-c-e-c-con-ffigen","Quando i Platform Channels non bastano, Dart FFI permette di invocare direttamente codice nativo C\u002FC++ senza passare dal thread della piattaforma. Guida pratica a dart:ffi, ffigen, gestione della memoria, callback e packaging di un plugin FFI.","## Perché FFI e non un Platform Channel\n\nI Platform Channels sono lo strumento standard per parlare con il codice nativo, ma hanno un costo: ogni messaggio viene serializzato, attraversa il confine tra isolate Dart e platform thread, e torna indietro in modo **asincrono**. Per operazioni come \"apri la fotocamera\" va benissimo; per chiamare mille volte al secondo una funzione di elaborazione immagini scritta in C è un disastro.\n\n`dart:ffi` (Foreign Function Interface) risolve esattamente questo problema: permette di caricare una libreria nativa e invocarne le funzioni **direttamente**, in modo sincrono, senza serializzazione e senza scrivere una riga di Kotlin, Swift o Objective-C.\n\nI casi d'uso tipici:\n\n- riutilizzare librerie C\u002FC++ mature (SQLite, libwebp, OpenCV, crittografia, codec audio);\n- algoritmi CPU-intensive dove il costo del canale sarebbe dominante;\n- SDK di terze parti distribuiti solo come binari nativi;\n- condividere lo stesso codice C su tutte e sei le piattaforme supportate da Flutter.\n\nQuando invece serve accedere ad API di sistema di alto livello (permessi, intent Android, `UIViewController`), i Platform Channels restano la scelta giusta.\n\n## Come funziona: i tre pezzi del puzzle\n\n1. **Caricare la libreria** con `DynamicLibrary`.\n2. **Dichiarare la firma** della funzione due volte: una in tipi *nativi* (`Int32`, `Pointer\u003CUtf8>`) e una in tipi *Dart* (`int`, `Pointer\u003CUtf8>`).\n3. **Fare il lookup** del simbolo e chiamarlo.\n\nPartiamo da una funzione C banale:\n\n```c\n\u002F\u002F src\u002Fmath_utils.h\n#include \u003Cstdint.h>\n\nint32_t sum_range(int32_t from, int32_t to);\n```\n\nIl binding manuale in Dart:\n\n```dart\nimport 'dart:ffi';\nimport 'dart:io';\n\n\u002F\u002F Firma lato C\ntypedef SumRangeNative = Int32 Function(Int32 from, Int32 to);\n\u002F\u002F Firma lato Dart\ntypedef SumRangeDart = int Function(int from, int to);\n\nfinal DynamicLibrary _lib = _openLibrary();\n\nDynamicLibrary _openLibrary() {\n  if (Platform.isAndroid) return DynamicLibrary.open('libmath_utils.so');\n  if (Platform.isWindows) return DynamicLibrary.open('math_utils.dll');\n  if (Platform.isLinux) return DynamicLibrary.open('libmath_utils.so');\n  \u002F\u002F Su iOS e macOS le librerie del plugin sono linkate staticamente\n  \u002F\u002F dentro il binario dell'app: i simboli sono già nel processo.\n  return DynamicLibrary.process();\n}\n\nfinal sumRange = _lib.lookupFunction\u003CSumRangeNative, SumRangeDart>(\n  'sum_range',\n  isLeaf: true, \u002F\u002F funzione che non richiama Dart e non fa GC: chiamata più veloce\n);\n\nvoid main() {\n  print(sumRange(1, 100)); \u002F\u002F 5050\n}\n```\n\n### Il flag `isLeaf`\n\n`isLeaf: true` dice al compilatore che la funzione nativa **non** chiamerà mai codice Dart e non allocherà memoria gestita. In cambio il runtime salta parte del setup della transizione, riducendo l'overhead della chiamata a poche decine di nanosecondi. Usalo solo per funzioni brevi e \"pure\": se la funzione blocca a lungo, lasciando `isLeaf: true` impedisci al GC di girare.\n\n## Tabella di conversione dei tipi\n\n| Tipo C | Tipo nativo Dart | Tipo Dart |\n| --- | --- | --- |\n| `int8_t`, `char` | `Int8` | `int` |\n| `int32_t`, `int` | `Int32` | `int` |\n| `int64_t` | `Int64` | `int` |\n| `float` | `Float` | `double` |\n| `double` | `Double` | `double` |\n| `void` | `Void` | `void` |\n| `bool` | `Bool` | `bool` |\n| `char*` | `Pointer\u003CUtf8>` | `Pointer\u003CUtf8>` |\n| `struct*` | `Pointer\u003CMyStruct>` | `Pointer\u003CMyStruct>` |\n\nAttenzione: non esiste conversione automatica per `String`. Le stringhe vanno tradotte esplicitamente, come vedremo tra poco.\n\n## Generare i binding automaticamente con ffigen\n\nScrivere a mano i binding per un header con 200 funzioni è impraticabile. **package:ffigen** legge gli header C con libclang e genera il file Dart corrispondente.\n\n```yaml\n# pubspec.yaml\ndev_dependencies:\n  ffigen: ^13.0.0\n\ndependencies:\n  ffi: ^2.1.3\n```\n\nConfigurazione in un file `ffigen.yaml` alla radice del progetto:\n\n```yaml\nname: NativeImageBindings\ndescription: Binding generati per libimage\noutput: 'lib\u002Fsrc\u002Fnative_bindings.g.dart'\nheaders:\n  entry-points:\n    - 'src\u002Fimage_utils.h'\n  include-directives:\n    - '**image_utils.h' # evita di importare mezza libc\nfunctions:\n  include:\n    - 'image_.*'\ncomments:\n  style: any\n  length: full\npreamble: |\n  \u002F\u002F ignore_for_file: type=lint, camel_case_types\n```\n\nGenerazione:\n\n```bash\ndart run ffigen --config ffigen.yaml\n```\n\n> ffigen richiede LLVM installato (`brew install llvm` su macOS, `apt install libclang-dev` su Linux, LLVM release ufficiale su Windows). Se non lo trova, specifica `llvm-path` nel file di configurazione.\n\nIl risultato è una classe che incapsula il lookup dei simboli:\n\n```dart\nfinal bindings = NativeImageBindings(DynamicLibrary.open('libimage.so'));\nfinal width = bindings.image_get_width(handle);\n```\n\nRicorda di **committare** il file generato: non tutti gli ambienti di CI hanno LLVM.\n\n## Gestione della memoria: il punto delicato\n\nDart ha il garbage collector, C no. Tutto ciò che allochi con `malloc`\u002F`calloc` va liberato a mano, altrimenti hai un leak invisibile ai DevTools.\n\n### Stringhe\n\n```dart\nimport 'dart:ffi';\nimport 'package:ffi\u002Fffi.dart';\n\ntypedef GreetNative = Pointer\u003CUtf8> Function(Pointer\u003CUtf8>);\ntypedef GreetDart = Pointer\u003CUtf8> Function(Pointer\u003CUtf8>);\n\nString greet(String name) {\n  final namePtr = name.toNativeUtf8(); \u002F\u002F alloca con malloc\n  try {\n    final resultPtr = _greet(namePtr);\n    return resultPtr.toDartString(); \u002F\u002F copia in una String Dart\n  } finally {\n    malloc.free(namePtr);\n  }\n}\n```\n\nDomanda cruciale: **chi possiede `resultPtr`?** Dipende dal contratto della libreria C. Se la funzione restituisce un buffer allocato con `malloc`, devi liberarlo tu (spesso tramite una `image_free_string()` esposta dalla libreria stessa). Se restituisce un puntatore a memoria statica, liberarlo causa un crash. Leggi sempre la documentazione dell'header.\n\n### Arena: free automatico\n\nPer blocchi con molte allocazioni, `package:ffi` offre l'`Arena`, che libera tutto all'uscita dello scope:\n\n```dart\nimport 'package:ffi\u002Fffi.dart';\n\nList\u003Cint> processPixels(List\u003Cint> input) {\n  return using((Arena arena) {\n    final buffer = arena\u003CUint8>(input.length); \u002F\u002F liberato automaticamente\n    final view = buffer.asTypedList(input.length);\n    view.setAll(0, input);\n\n    _processInPlace(buffer, input.length);\n\n    return view.toList(growable: false);\n  });\n}\n```\n\n`asTypedList` è particolarmente efficiente: restituisce una `Uint8List` che punta **direttamente** alla memoria nativa, senza copie. Attenzione però: la lista diventa dangling nel momento in cui liberi il puntatore.\n\n### NativeFinalizer per gli handle\n\nSe esponi un oggetto nativo con ciclo di vita lungo (es. un `image_handle_t*`), collega la deallocazione al GC di Dart:\n\n```dart\nfinal _finalizer = NativeFinalizer(_lib.lookup\u003CNativeFunction\u003CVoid Function(Pointer\u003CVoid>)>>('image_free'));\n\nclass NativeImage implements Finalizable {\n  NativeImage._(this._handle) {\n    _finalizer.attach(this, _handle.cast(), detach: this);\n  }\n\n  final Pointer\u003CVoid> _handle;\n\n  factory NativeImage.load(String path) {\n    final p = path.toNativeUtf8();\n    try {\n      final handle = _imageLoad(p);\n      if (handle == nullptr) throw StateError('Impossibile caricare $path');\n      return NativeImage._(handle);\n    } finally {\n      malloc.free(p);\n    }\n  }\n\n  void dispose() {\n    _finalizer.detach(this);\n    _imageFree(_handle);\n  }\n}\n```\n\nIl `NativeFinalizer` è una **rete di sicurezza**, non un sostituto di `dispose()`: il GC non ha alcuna garanzia temporale.\n\n## Struct e union\n\nLe struct si dichiarano estendendo `Struct` con annotazioni sui campi (ffigen le genera per te):\n\n```dart\nfinal class ImageInfo extends Struct {\n  @Int32()\n  external int width;\n\n  @Int32()\n  external int height;\n\n  external Pointer\u003CUtf8> format;\n}\n\nvoid readInfo() {\n  final ptr = calloc\u003CImageInfo>();\n  try {\n    _imageGetInfo(handle, ptr);\n    final info = ptr.ref; \u002F\u002F accesso ai campi\n    print('${info.width}x${info.height} ${info.format.toDartString()}');\n  } finally {\n    calloc.free(ptr);\n  }\n}\n```\n\n## Non bloccare la UI: FFI è sincrono\n\nUna chiamata FFI gira sull'isolate chiamante. Se dura 300 ms e la fai sull'isolate principale, hai perso 18 frame. La soluzione è spostarla su un isolate secondario:\n\n```dart\nFuture\u003CUint8List> compressImage(Uint8List bytes, int quality) {\n  return Isolate.run(() {\n    \u002F\u002F Dentro l'isolate: la DynamicLibrary va aperta di nuovo,\n    \u002F\u002F i puntatori NON possono essere passati fra isolate come tali\n    \u002F\u002F (si passa l'indirizzo come int, oppure si rifà il lavoro qui).\n    return _compressSync(bytes, quality);\n  });\n}\n```\n\nPunti da ricordare:\n\n- ogni isolate deve fare il proprio `DynamicLibrary.open` (è economico, la libreria è già in memoria);\n- `Pointer` non è sendable direttamente in modo sicuro: se proprio devi, trasferisci `pointer.address` (un `int`) e ricostruisci con `Pointer.fromAddress`, assumendoti la responsabilità del ciclo di vita;\n- i dati \"grandi\" conviene passarli come `TypedData`, che Dart trasferisce in modo efficiente.\n\n## Callback dal C verso Dart\n\nDa Dart 3.1 la classe `NativeCallable` gestisce anche le callback invocate da **thread nativi diversi** da quello dell'isolate:\n\n```dart\n\u002F\u002F void register_progress_cb(void (*cb)(int32_t));\nvoid _onProgress(int percent) {\n  debugPrint('Avanzamento: $percent%');\n}\n\nlate final NativeCallable\u003CVoid Function(Int32)> _callback;\n\nvoid startJob() {\n  \u002F\u002F .listener: la callback viene accodata sull'event loop dell'isolate.\n  \u002F\u002F Funziona anche se il C chiama da un thread suo.\n  _callback = NativeCallable\u003CVoid Function(Int32)>.listener(_onProgress);\n  _registerProgressCb(_callback.nativeFunction);\n}\n\nvoid stopJob() {\n  _unregisterProgressCb();\n  _callback.close(); \u002F\u002F obbligatorio, altrimenti l'isolate resta vivo\n}\n```\n\nEsiste anche `NativeCallable.isolateLocal`, sincrona ma utilizzabile **solo** se il C richiama dallo stesso thread dell'isolate. Sbagliare variante è una fonte classica di crash difficili da diagnosticare.\n\n## Impacchettare tutto in un plugin FFI\n\nFlutter fornisce un template dedicato che configura CMake (Android\u002FLinux\u002FWindows) e CocoaPods (iOS\u002FmacOS):\n\n```bash\nflutter create --template=plugin_ffi --platforms=android,ios,macos,linux,windows my_native_lib\n```\n\nLa struttura generata:\n\n```\nmy_native_lib\u002F\n├── src\u002F                    # sorgenti C\u002FC++ condivisi\n│   ├── my_native_lib.c\n│   └── my_native_lib.h\n├── android\u002Fbuild.gradle    # punta a src\u002FCMakeLists.txt\n├── ios\u002Fmy_native_lib.podspec\n├── lib\u002F\n│   ├── my_native_lib.dart\n│   └── my_native_lib_bindings_generated.dart\n└── ffigen.yaml\n```\n\nDettagli pratici da tenere a mente:\n\n- su **iOS e macOS** il template usa il linking statico: devi forzare il linker a non scartare i simboli non referenziati da codice nativo. Il podspec generato include già `OTHER_LDFLAGS` con `-force_load`, oppure puoi marcare le funzioni con `__attribute__((visibility(\"default\"))) __attribute__((used))`;\n- su **Android** il file `CMakeLists.txt` produce la `.so` per ogni ABI (`arm64-v8a`, `armeabi-v7a`, `x86_64`), che finisce dentro l'APK\u002FAAB;\n- su **Windows** non dimenticare `__declspec(dllexport)`;\n- il codice C nel plugin viene ricompilato dalla toolchain nativa del progetto: chi usa il tuo package non deve installare nulla.\n\n## Native Assets: il futuro (sperimentale)\n\nIl team Dart sta lavorando ai **native assets** (build hooks tramite `hook\u002Fbuild.dart`), che permettono di dichiarare la dipendenza nativa direttamente in Dart e usare le funzioni con l'annotazione `@Native`, eliminando lookup e boilerplate:\n\n```dart\n@Native\u003CInt32 Function(Int32, Int32)>(symbol: 'sum_range')\nexternal int sumRange(int from, int to);\n```\n\nÈ ancora dietro flag sperimentale (`--enable-experiment=native-assets`) e l'API cambia tra le release: ottimo da sperimentare, prematuro per la produzione.\n\n## Best practice e trappole comuni\n\n1. **Incapsula sempre i binding.** Il file generato da ffigen non deve uscire dal package: esponi un'API Dart idiomatica (`Future\u003CUint8List> compress(...)`), non `Pointer\u003CVoid>`.\n2. **Controlla i puntatori nulli.** `if (ptr == nullptr) throw ...`: dereferenziare `nullptr` non lancia un'eccezione Dart, fa crashare il processo.\n3. **Usa `try\u002Ffinally` per ogni allocazione** o, meglio, `using(Arena)`.\n4. **Attenzione ai tipi a larghezza variabile.** `long` è 32 bit su Windows e 64 bit su Linux\u002FmacOS: usa i tipi di `\u003Cstdint.h>` negli header e `IntPtr`\u002F`Size` quando serve.\n5. **Verifica gli allineamenti delle struct.** Se il C usa `#pragma pack`, ffigen potrebbe generare layout non corrispondenti: aggiungi `@Packed(n)`.\n6. **Testa su tutte le architetture.** Un bug di allineamento può manifestarsi solo su `armeabi-v7a` a 32 bit.\n7. **Misura prima di ottimizzare.** Se una chiamata avviene dieci volte al minuto, un Platform Channel è più semplice da mantenere.\n8. **Ricorda che un crash nativo è un crash dell'app**: nessun `try\u002Fcatch` Dart intercetta un segmentation fault. Valida gli input **prima** di attraversare il confine.\n\n## Conclusioni\n\nDart FFI trasforma Flutter in una piattaforma capace di riutilizzare decenni di codice C e C++ senza mediazione. Con `ffigen` il boilerplate praticamente sparisce, con `Arena` e `NativeFinalizer` la gestione della memoria diventa ragionevolmente sicura, e con `Isolate.run` puoi eseguire lavoro pesante senza far scendere il frame rate.\n\nIl prezzo da pagare è la disciplina: nel mondo nativo non c'è rete di sicurezza. Incapsula, valida gli input, libera ciò che allochi e limita la superficie di contatto tra Dart e C al minimo indispensabile.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F16401497-e777-414a-a93c-3625cd30f9b8.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1771928749886-d006a20c455e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODczMTU4NzR8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Wolfgang Weiser","https:\u002F\u002Funsplash.com\u002F@hamburgmeinefreundin","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fa-modern-cable-stayed-bridge-over-calm-water-YjxlzELV-2k",null,"published","2026-08-20T07:11:41+00:00","Dart FFI in Flutter: librerie C e C++ con ffigen","Guida pratica a Dart FFI in Flutter: caricare librerie C, generare binding con ffigen, gestire memoria, struct, callback e creare un plugin FFI multipiattaforma.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1789120586634]