FFI: основе, Foreign Function Interface и интеграција са C

Аутор: IT Sectr Објављено: 2026-06-05 Време читања: 10 мин

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) — механизам директног позивања C функција из Dart-а
  • dart:ffi пружа API за учитавање библиотека и декларисање потписа
  • Перформансе позива преко FFI су 50–100 пута веће него преко Method Channel-а
  • Типизација FFI подржава примитивне типове, структуре и показиваче C
  • Flutter користи FFI за интеграцију са изворним библиотекама: OpenCV, SQLite, FFmpeg

Шта је FFI?

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 такође подржава рад са меморијом: издвајање, ослобађање и управљање показивачима.

FFI vs Method Channel: принципијелна разлика

Method Channel ради асинхроно: Dart шаље поруку изворном коду, изворни код је обрађује и шаље резултат назад. Сваки позив захтева серијализацију аргумената у Map, пренос кроз ред и десеријализацију. То траје 0.5–5 ms по позиву. FFI ради синхроно и без серијализације — позив C функције траје 0.01–0.1 μs. Разлика од 50–500 пута, што је критично за високофреквентне операције.

Како ради dart:ffi?

Рад са 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.

Основни пример: позивање C функције из Dart-а

dart
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 функцију која се може позивати као обична.

Типови података у FFI

dart:ffi пружа скуп типова који одговарају C типовима. Сваки тип има фиксну величину и правила конверзије између Dart-а и C-а. Разумевање подударања типова је критично за исправан рад FFI-ја — грешка у величини или знаку типа може довести до пада апликације.

C типdart:ffi типDart типВеличина (бајтова)
intInt32int4
longInt64int8
floatFloatdouble4
doubleDoubledouble8
char*Pointer<Int8>Pointer8 (показивач)
void*Pointer<Void>Pointer8 (показивач)
structPointer<T> (Struct)Pointerзависи од поља

За рад са C низовима (char*) dart:ffi користи Pointer<Int8>. Конверзија из Dart String у C char* и обрнуто врши се преко метода toNativeUtf8 (из пакета ffi) и fromUtf8. Важно је ослобађати C низове након употребе преко calloc.free да би се избегло цурење меморије.

Структуре (Struct)

dart:ffi подржава декларисање C структура као Dart класа које наслеђују Struct. Поља структуре се декларишу са анотацијама @Int32(), @Float(), @Array() и другим. Величина и померај поља се израчунавају аутоматски у складу са ABI платформе. Pointer<Point> може се добити из C функције која враћа показивач на структуру или издвојити у Dart-у преко calloc.

dart
// 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 пружа приступ пољима структуре преко гетера и сетера.

Практични примери FFI

Размотримо сложенији пример — интеграцију са C библиотеком за израчунавање хеша SHA256. Ово је типичан задатак где FFI даје значајну предност у перформансама у односу на Method Channel.

Интеграција са OpenSSL-ом преко FFI

Библиотека OpenSSL пружа функцију SHA256 која израчунава хеш ниске. Преко dart:ffi-ја можемо је позвати директно, без писања Java или Swift омотача. Ово је пример како FFI омогућава поновну употребу постојећих C библиотека у Flutter-у.

dart
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-а.

dart
// 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 разликује од Method Channel-а?

FFI позива C функције директно, синхроно и без серијализације — кашњење 0.01–0.1 μs. Method Channel ради асинхроно кроз JSON серијализацију са кашњењем 0.5–5 ms. FFI је погодан за високоперформансне операције, Method Channel — за једноставне позиве платформских API-ја.

Могу ли се C++ функције позивати преко FFI-ја?

Директно — не, dart:ffi подржава само C функције. За позивање C++ потребно је креирати C омотач са extern "C" (улазне тачке које се извозе као C симболи). C++ класе захтевају додатни слој који претвара позиве метода у C функције.

Како обрадити грешке у C функцијама?

FFI не подржава изузетке — ако C функција враћа код грешке, потребно га је ручно проверити. Препоручује се облагање FFI позива у try-catch у Dart-у и провера повратних кодова C функција. Критичне грешке (segfault) се не могу ухватити.

Које библиотеке се не могу користити преко FFI-ја?

FFI не ради са библиотекама које захтевају сложену иницијализацију Java (JNI) или Objective-C (Message Dispatch). На пример, UIKit и Android Views нису доступни преко FFI-ја. Ограничење је повезано са тим што FFI ради на нивоу C ABI, а ови API-ји захтевају специфична извршна окружења.

Да ли треба компилирати C библиотеке за сваку платформу посебно?

Да, C библиотеке се компилирају за сваку циљану платформу посебно. За Android се гради .so за различите ABI-је (armeabi-v7a, arm64-v8a, x86_64). За iOS — универзални .dylib (arm64). За Windows — .dll. Flutter аутоматски пакује исправну верзију библиотеке при компилацији.

Закључак

  • FFI (Foreign Function Interface) — механизам директног позивања C функција из Dart-а преко dart:ffi
  • Перформансе FFI позива су 50–500 пута веће него преко Method Channel-а
  • Архитектура укључује учитавање библиотеке, декларисање потписа и позивање функција
  • Типови података dart:ffi подржава Int32, Float, Double, Pointer, Struct и друге C типове
  • Меморија на C страни се управља ручно преко calloc/free или Arena
  • Ограничења FFI: нема провере типова у runtime, нема директне подршке за C++, блокира UI нит
  • Користите FFI за високоперформансну интеграцију са изворним библиотекама у Flutter-у

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође