FFI (Foreign Function Interface) — је механизам језика Dart, који пружа пакет dart:ffi, који омогућава директно позивање функција из изворних C библиотека, без посредних слојева у Kotlin, Swift или Java. Програмер учитава динамичку библиотеку (.so на Android-у, .dylib на iOS-у, .dll на Windows-у), декларише потписе C функција и позива их као обичне Dart функције. Према Dart API Reference (2025), FFI смањује додатно оптерећење међујезичких позива на 0.1 μs, што је десетинама пута брже него преко Method Channel-а.
Главно
FFI (Foreign Function Interface) — је механизам који омогућава програмском језику да позива функције написане на другим језицима. У контексту Dart-а и Flutter-а, FFI значи могућност позивања функција из C/C++ библиотека директно из Dart кода, без потребе за писањем платформског кода у Java (Android) или Swift/Objective-C (iOS).
Пакет dart:ffi се појавио у Dart 2.12 (2021) и од тада је постао кључни алат за интеграцију Flutter-а са изворним кодом. Пре појаве dart:ffi-ја, једини начин да се позове C функција из Dart-а био је Method Channel — асинхрони механизам који је преносио поруке кроз JSON серијализацију између Dart-а и изворне стране. FFI ради другачије: Dart код директно приступа меморији C библиотеке, позивајући функције кроз изворни ABI (Application Binary Interface) без серијализације и без пребацивања контекста.
FFI је посебно тражен у сценаријима где су перформансе критичне: обрада слика (OpenCV), аудио (FFmpeg), криптографија (OpenSSL), машинско учење (TensorFlow Lite) и базе података (SQLite). У свим овим случајевима, Method Channel ствара неприхватљива кашњења, а FFI обезбеђује перформансе упоредиве са изворним C/C++ кодом. Библиотека dart:ffi такође подржава рад са меморијом: издвајање, ослобађање и управљање показивачима.
Method Channel ради асинхроно: Dart шаље поруку изворном коду, изворни код је обрађује и шаље резултат назад. Сваки позив захтева серијализацију аргумената у Map, пренос кроз ред и десеријализацију. То траје 0.5–5 ms по позиву. FFI ради синхроно и без серијализације — позив C функције траје 0.01–0.1 μs. Разлика од 50–500 пута, што је критично за високофреквентне операције.
Рад са dart:ffi-јем састоји се из три фазе: учитавање библиотеке, декларисање потписа и позивање функција. Свака фаза користи строгу типизацију Dart-а, што минимизира грешке у време извршавања.
У првој фази, динамичка библиотека се учитава преко класе DynamicLibrary. Библиотека се може учитати по имену (libxyz.so, libxyz.dylib, xyz.dll) или по пуној путањи. Dart аутоматски тражи библиотеку на стандардним путањама система. DynamicLibrary пружа метод lookupFunction, који повезује Dart функцију са C функцијом по имену симбола.
У другој фази, декларише се Dart функција са анотацијама типова које одговарају C потпису. За то се користе специјални типови из dart:ffi-ја: Int32, Float, Double, Pointer, NativeFunction, Handle и други. Анотација lookupFunction прима два генеричка параметра: тип Dart функције (како ће изгледати у Dart-у) и тип изворне C функције (како је декларисана у C).
У трећој фази, генерисана Dart функција се позива као обична функција. Аргументи се прослеђују директно, резултат се враћа одмах. Ако C функција мења меморију преко показивача, Dart може читати те промене преко класе Pointer. Управљање меморијом на C страни остаје одговорност програмера — dart:ffi не управља меморијом издвојеном путем malloc у C.
import 'dart:ffi'
import 'package:ffi/ffi.dart'
// Декларација C функције: 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
}
У овом примеру, add је C функција која прима два int и враћа int. typedef AddNative описује C потпис са типовима dart:ffi, а AddDart — како ће ова функција изгледати у Dart-у. lookupFunction их повезује и враћа Dart функцију која се може позивати као обична.
dart:ffi пружа скуп типова који одговарају C типовима. Сваки тип има фиксну величину и правила конверзије између Dart-а и C-а. Разумевање подударања типова је критично за исправан рад FFI-ја — грешка у величини или знаку типа може довести до пада апликације.
| C тип | dart:ffi тип | Dart тип | Величина (бајтова) |
|---|---|---|---|
| int | Int32 | int | 4 |
| long | Int64 | int | 8 |
| float | Float | double | 4 |
| double | Double | double | 8 |
| char* | Pointer<Int8> | Pointer | 8 (показивач) |
| void* | Pointer<Void> | Pointer | 8 (показивач) |
| struct | Pointer<T> (Struct) | Pointer | зависи од поља |
За рад са C низовима (char*) dart:ffi користи Pointer<Int8>. Конверзија из Dart String у C char* и обрнуто врши се преко метода toNativeUtf8 (из пакета ffi) и fromUtf8. Важно је ослобађати C низове након употребе преко calloc.free да би се избегло цурење меморије.
dart:ffi подржава декларисање C структура као Dart класа које наслеђују Struct. Поља структуре се декларишу са анотацијама @Int32(), @Float(), @Array() и другим. Величина и померај поља се израчунавају аутоматски у складу са ABI платформе. Pointer<Point> може се добити из C функције која враћа показивач на структуру или издвојити у Dart-у преко calloc.
// C структура: typedef struct { int x; int y; } Point;
final class Point extends Struct {
@Int32()
external int x
@Int32()
external int y
}
// Позивање C функције која враћа Point*
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) // ослободи меморију
Класа Point наслеђује Struct и декларише поља x и y са анотацијама @Int32(). Генерисани C код ће имати потпуно исти распоред поља у меморији. Pointer.ref пружа приступ пољима структуре преко гетера и сетера.
Размотримо сложенији пример — интеграцију са C библиотеком за израчунавање хеша SHA256. Ово је типичан задатак где FFI даје значајну предност у перформансама у односу на Method Channel.
Библиотека OpenSSL пружа функцију SHA256 која израчунава хеш ниске. Преко dart:ffi-ја можемо је позвати директно, без писања Java или Swift омотача. Ово је пример како FFI омогућава поновну употребу постојећих C библиотека у Flutter-у.
import 'dart:ffi'
import 'package:ffi/ffi.dart'
// Потпис: 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 бајтова
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
}
У овом примеру, функција sha256 учитава библиотеку libcrypto.so, проналази симбол SHA256 и позива га са показивачима на улазне и излазне податке. toNativeUtf8 конвертује Dart String у C низ (издваја меморију), а asTypedList омогућава читање низа бајтова резултата. Меморија се ослобађа након употребе — ово је обавезан корак за спречавање цурења.
Пакет ffi пружа функцију calloc за издвајање меморије компатибилне са C. Издвојена меморија мора бити ослобођена преко calloc.free, иначе ће доћи до цурења. За аутоматско управљање меморијом може се користити класа Arena из пакета ffi, која ослобађа сву меморију издвојену у њој при позиву arena.release(). Ово је посебно згодно када постоји велики број привремених издвајања.
Упркос снази FFI-ја, он има ограничења која се морају узети у обзир при пројектовању архитектуре Flutter апликације. Главна ограничења се односе на безбедност типова, управљање меморијом и компатибилност платформи.
FFI не проверава типове у време извршавања. Ако C функција очекује показивач, а проследи се број, апликација ће пасти са segmentation fault. Препоручује се коришћење FFIgen-а — алата који генерише типно безбедне Dart омотаче на основу C заглавља (.h датотека). FFIgen анализира декларације C функција и креира Dart код са исправним типовима, што елиминише грешке у фази писања кода.
Имена и путање до динамичких библиотека разликују се на различитим платформама: libxyz.so на Android/Linux, libxyz.dylib на iOS/macOS, xyz.dll на Windows. За вишeплатформске библиотеке користи се условна компилација преко dart:io (Platform.isAndroid, Platform.isIOS) или апстракције попут package:ffi. Препоручује се креирање фабричког метода који враћа исправну библиотеку за тренутну платформу.
FFI не управља меморијом на C страни. Ако C функција издваја меморију преко malloc, она мора бити ослобођена преко free, иначе ће доћи до цурења. У Dart-у не постоји сакупљач отпада за C меморију. Препорука: увек ослобађајте меморију у истој методи у којој је издвојена или користите Arena за групно ослобађање.
FFI позиви се извршавају у истој нити као и Dart код. Дуге синхроне операције (више од 10 ms) блокирају UI нит и изазивају прескакање оквира. За дуге операције потребно је позвати C функцију у изолату (Isolate) или осигурати да C функција покреће рад у позадинској нити и обавештава Dart преко Port-а или callback-а.
// FFI у изолату за дуге операције
import 'dart:isolate'
Future<String> computeHash(String input) async {
final port = ReceivePort()
await Isolate.spawn((SendPort sendPort) {
final result = sha256(input) // FFI позив
sendPort.send(result)
}, port.sendPort)
return await port.first as String
}
Измештање FFI позива у изолат гарантује да UI нит није блокирана. Међутим, пренос великих количина података између изолата захтева копирање меморије. За велике бафере (>10 MB) пожељније је користити ShareMemory или меморијски мапиране датотеке.
Често постављана питања
FFI позива C функције директно, синхроно и без серијализације — кашњење 0.01–0.1 μs. Method Channel ради асинхроно кроз JSON серијализацију са кашњењем 0.5–5 ms. FFI је погодан за високоперформансне операције, Method Channel — за једноставне позиве платформских API-ја.
Директно — не, dart:ffi подржава само C функције. За позивање C++ потребно је креирати C омотач са extern "C" (улазне тачке које се извозе као C симболи). C++ класе захтевају додатни слој који претвара позиве метода у C функције.
FFI не подржава изузетке — ако C функција враћа код грешке, потребно га је ручно проверити. Препоручује се облагање FFI позива у try-catch у Dart-у и провера повратних кодова C функција. Критичне грешке (segfault) се не могу ухватити.
FFI не ради са библиотекама које захтевају сложену иницијализацију Java (JNI) или Objective-C (Message Dispatch). На пример, UIKit и Android Views нису доступни преко FFI-ја. Ограничење је повезано са тим што FFI ради на нивоу C ABI, а ови API-ји захтевају специфична извршна окружења.
Да, C библиотеке се компилирају за сваку циљану платформу посебно. За Android се гради .so за различите ABI-је (armeabi-v7a, arm64-v8a, x86_64). За iOS — универзални .dylib (arm64). За Windows — .dll. Flutter аутоматски пакује исправну верзију библиотеке при компилацији.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.