FFI: əsaslar, Foreign Function Interface və C ilə inteqrasiya

Müəllif: IT Sectr Dərc olunub: 2026-06-05 Oxuma vaxtı: 10 dəq

FFI (Foreign Function Interface) — dart:ffi paketi ilə təmin edilən Dart dil mexanizmidir və əlavə Kotlin, Swift və ya Java təbəqələri olmadan birbaşa C kitabxanalarından funksiyaları çağırmağa imkan verir. Tərtibatçı dinamik kitabxananı yükləyir (.so Android-də, .dylib iOS-da, .dll Windows-da), C funksiyalarının imzalarını bəyan edir və onları adi Dart funksiyaları kimi çağırır. Dart API Reference (2025)-ə görə, FFI dillərarası çağırışlarda yükü 0.1 μs-ə qədər azaldır, bu da Method Channel-dan on dəfələrlə sürətlidir.

Əsas məqamlar

  • FFI (Foreign Function Interface) — C funksiyalarını birbaşa Dart-dan çağırma mexanizmi
  • dart:ffi kitabxanaların yüklənməsi və imzaların bəyan edilməsi üçün API təmin edir
  • Performans FFI vasitəsilə çağırışlar Method Channel-dan 50–100 dəfə yüksəkdir
  • Tip sistemi FFI primitiv tipləri, strukturları və C göstəricilərini dəstəkləyir
  • Flutter FFI-dən OpenCV, SQLite, FFmpeg kimi yerli kitabxanalarla inteqrasiya üçün istifadə edir

FFI nədir?

FFI (Foreign Function Interface) — proqramlaşdırma dilinə digər dillərdə yazılmış funksiyaları çağırmağa imkan verən mexanizmdir. Dart və Flutter kontekstində FFI, Java (Android) və ya Swift/Objective-C (iOS) platforma kodunu yazmadan birbaşa Dart kodundan C/C++ kitabxanalarından funksiyaları çağırmaq imkanı deməkdir.

dart:ffi paketi Dart 2.12-də (2021) ortaya çıxdı və o vaxtdan bəri Flutter-in yerli kodla inteqrasiyası üçün əsas vasitəyə çevrildi. dart:ffi yaranmazdan əvvəl Dart-dan C funksiyasını çağırmağın yeganə yolu Method Channel idi — Dart və yerli tərəf arasında mesajları JSON serializasiyası ilə ötürən asinxron mexanizm. FFI fərqli işləyir: Dart kodu birbaşa C kitabxanasının yaddaşına müraciət edir, funksiyaları yerli ABI (Application Binary Interface) vasitəsilə serializasiya və kontekst dəyişmədən çağırır.

FFI xüsusilə performansın kritik olduğu ssenarilərdə tələb olunur: şəkil emalı (OpenCV), audio (FFmpeg), kriptoqrafiya (OpenSSL), maşın öyrənməsi (TensorFlow Lite) və verilənlər bazaları (SQLite). Bütün bu hallarda Method Channel qəbuledilməz gecikmələr yaradır, FFI isə yerli C/C++ kodu ilə müqayisə edilə bilən performans təmin edir. dart:ffi kitabxanası həmçinin yaddaşla işi dəstəkləyir: ayırma, boşaltma və göstəricilərin idarə edilməsi.

FFI vs Method Channel: prinsipial fərq

Method Channel asinxron işləyir: Dart yerli koda mesaj göndərir, yerli kod onu emal edir və nəticəni geri qaytarır. Hər çağırış arqumentlərin Map-ə serializasiyasını, növbə ilə ötürülməsini və deserializasiyanı tələb edir. Bu, hər çağırış üçün 0.5–5 ms çəkir. FFI sinxron və serializasiyasız işləyir — C funksiyasının çağırışı 0.01–0.1 μs çəkir. 50–500 dəfə fərq, bu yüksək tezlikli əməliyyatlar üçün kritikdir.

dart:ffi necə işləyir?

dart:ffi ilə iş üç mərhələdən ibarətdir: kitabxananın yüklənməsi, imzaların bəyan edilməsi və funksiyaların çağırılması. Hər mərhələ Dart-ın ciddi tipləməsindən istifadə edir ki, bu da icra zamanı səhvləri minimuma endirir.

Birinci mərhələdə dinamik kitabxana DynamicLibrary sinfi vasitəsilə yüklənir. Kitabxana adı ilə (libxyz.so, libxyz.dylib, xyz.dll) və ya tam yol ilə yüklənə bilər. Dart avtomatik olaraq kitabxananı sistemin standart yollarında axtarır. DynamicLibrary Dart funksiyasını C funksiyası ilə simvol adına görə birləşdirən lookupFunction metodunu təmin edir.

İkinci mərhələdə C imzasına uyğun tip annotasiyaları ilə Dart funksiyası bəyan edilir. Bunun üçün dart:ffi-dən xüsusi tiplər istifadə olunur: Int32, Float, Double, Pointer, NativeFunction, Handle və digərləri. lookupFunction annotasiyası iki generik parametr qəbul edir: Dart funksiyasının tipi (Dart-da necə görünəcəyi) və yerli C funksiyasının tipi (C-də necə bəyan edildiyi).

Üçüncü mərhələdə yaradılan Dart funksiyası adi funksiya kimi çağırılır. Arqumentlər birbaşa ötürülür, nəticə dərhal qaytarılır. Əgər C funksiyası göstəricilər vasitəsilə yaddaşı dəyişirsə, Dart bu dəyişiklikləri Pointer sinfi vasitəsilə oxuya bilər. Yaddaşın idarə edilməsi C tərəfində tərtibatçının məsuliyyətində qalır — dart:ffi C-də malloc ilə ayrılmış yaddaşı idarə etmir.

Əsas nümunə: Dart-dan C funksiyasını çağırmaq

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

// C funksiyasının bəyannaməsi: 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
}

Bu nümunədə add — iki int qəbul edən və int qaytaran C funksiyasıdır. typedef AddNative dart:ffi tipləri ilə C imzasını təsvir edir, AddDart isə bu funksiyanın Dart-da necə görünəcəyini göstərir. lookupFunction onları birləşdirir və adi kimi çağırıla bilən Dart funksiyasını qaytarır.

FFI-də məlumat tipləri

dart:ffi C tiplərinə uyğun tiplər dəsti təqdim edir. Hər tip sabit ölçüyə və Dart ilə C arasında çevirmə qaydalarına malikdir. Tiplərin uyğunluğunu başa düşmək FFI-nin düzgün işləməsi üçün kritik əhəmiyyət daşıyır — tipin ölçüsündə və ya işarəsində səhv tətbiqin çökməsinə səbəb ola bilər.

C tipidart:ffi tipiDart tipiÖlçü (bayt)
intInt32int4
longInt64int8
floatFloatdouble4
doubleDoubledouble8
char*Pointer<Int8>Pointer8 (göstərici)
void*Pointer<Void>Pointer8 (göstərici)
structPointer<T> (Struct)Pointersahələrdən asılıdır

C sətirləri (char*) ilə işləmək üçün dart:ffi Pointer<Int8> istifadə edir. Dart String-dən C char*-a və əksinə çevirmə toNativeUtf8 (ffi paketindən) və fromUtf8 metodları vasitəsilə yerinə yetirilir. Yaddaş sızmasının qarşısını almaq üçün istifadədən sonra C sətirlərini calloc.free ilə boşaltmaq vacibdir.

Strukturlar (Struct)

dart:ffi C strukturlarını Struct-dan miras alan Dart sinifləri kimi bəyan etməyi dəstəkləyir. Struktur sahələri @Int32(), @Float(), @Array() və digər annotasiyalarla bəyan edilir. Sahələrin ölçüsü və yerdəyişməsi platformanın ABI-sına uyğun avtomatik hesablanır. Pointer<Point> C funksiyasından struktur göstəricisini qaytarmaqla və ya Dart-da calloc vasitəsilə ayrıla bilər.

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

    @Int32()
    external int y
}

// Point* qaytaran C funksiyasını çağırmaq
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) // yaddaşı boşalt

Point sinfi Struct-dan miras alır və x və y sahələrini @Int32() annotasiyaları ilə bəyan edir. Yaradılan C kodu yaddaşda tam eyni sahə düzülüşünə malik olacaq. Pointer.ref strukturun sahələrinə getter və setterlər vasitəsilə giriş təmin edir.

FFI praktik nümunələri

Daha mürəkkəb nümunəni nəzərdən keçirək — SHA256 hashinin hesablanması üçün C kitabxanası ilə inteqrasiya. Bu, FFI-nin Method Channel ilə müqayisədə əhəmiyyətli performans üstünlüyü verdiyi tipik bir tapşırıqdır.

OpenSSL ilə FFI vasitəsilə inteqrasiya

OpenSSL kitabxanası sətirin hashini hesablayan SHA256 funksiyasını təmin edir. dart:ffi vasitəsilə biz onu Java və ya Swift wrapperları yazmadan birbaşa çağıra bilərik. Bu, FFI-nin Flutter-də mövcud C kitabxanalarını təkrar istifadə etməyə necə imkan verdiyinin nümunəsidir.

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

// İmza: 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 bayt

    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
}

Bu nümunədə sha256 funksiyası libcrypto.so kitabxanasını yükləyir, SHA256 simvolunu tapır və onu giriş-çıxış məlumat göstəriciləri ilə çağırır. toNativeUtf8 Dart String-i C sətirinə çevirir (yaddaş ayırır), asTypedList isə nəticə bayt massivini oxumağa imkan verir. Yaddaş istifadədən sonra boşaldılır — bu, sızmaların qarşısını almaq üçün məcburi addımdır.

Yaddaşın ayrılması və boşaldılması

ffi paketi C ilə uyğun yaddaşı ayırmaq üçün calloc funksiyasını təmin edir. Ayrılmış yaddaş calloc.free ilə boşaldılmalıdır, əks halda sızma baş verəcək. Yaddaşın avtomatik idarə edilməsi üçün ffi paketindən Arena sinfindən istifadə etmək olar, o, arena.release() çağırışında bütün ayrılmış yaddaşı boşaldır. Bu, çox sayda müvəqqəti ayırmalar zamanı xüsusilə rahatdır.

Məhdudiyyətlər və ən yaxşı təcrübələr

FFI-nin gücünə baxmayaraq, Flutter tətbiqinin arxitekturasını layihələndirərkən nəzərə alınmalı məhdudiyyətləri var. Əsas məhdudiyyətlər tip təhlükəsizliyi, yaddaş idarəetməsi və platforma uyğunluğu ilə bağlıdır.

Təhlükəsizlik

FFI icra zamanı tipləri yoxlamır. Əgər C funksiyası göstərici gözləyirsə, amma nömrə ötürülürsə, tətbiq segmentation fault ilə çökəcək. C başlıqları (.h faylları) əsasında tip təhlükəsiz Dart wrapperları yaradan FFIgen alətindən istifadə etmək tövsiyə olunur. FFIgen C funksiyalarının bəyannamələrini təhlil edir və düzgün tiplərlə Dart kodu yaradır ki, bu da kod yazma mərhələsində səhvləri aradan qaldırır.

Platforma uyğunluğu

Dinamik kitabxanaların adları və yolları müxtəlif platformalarda fərqlənir: libxyz.so Android/Linux-da, libxyz.dylib iOS/macOS-da, xyz.dll Windows-da. Çoxplatformalı kitabxanalar üçün dart:io (Platform.isAndroid, Platform.isIOS) vasitəsilə şərti kompilyasiya və ya package:ffi kimi abstraksiyalar istifadə olunur. Cari platforma üçün düzgün kitabxananı qaytaran fabrik metodu yaratmaq tövsiyə olunur.

Yaddaşın idarə edilməsi

FFI C tərəfində yaddaşı idarə etmir. Əgər C funksiyası malloc vasitəsilə yaddaş ayırırsa, free ilə boşaldılmalıdır, əks halda sızma baş verəcək. Dart-da C yaddaşı üçün zibil toplayıcı yoxdur. Tövsiyə: yaddaşı həmişə ayrıldığı eyni metoddda boşaltmaq və ya qrup halında boşaltma üçün Arena istifadə etmək.

Performans və ipliklər

FFI çağırışları Dart kodu ilə eyni iplikdə yerinə yetirilir. Uzunmüddətli sinxron əməliyyatlar (10 ms-dən çox) UI ipliyini bloklayır və kadrların atlanmasına səbəb olur. Uzun əməliyyatlar üçün C funksiyasını izolyatda (Isolate) çağırmaq və ya C funksiyasının fon ipliyində işə düşdüyünə əmin olub Dart-a Port və ya callback vasitəsilə xəbər vermək lazımdır.

dart
// Uzun əməliyyatlar üçün izolyatda FFI
import 'dart:isolate'

Future<String> computeHash(String input) async {
    final port = ReceivePort()
    await Isolate.spawn((SendPort sendPort) {
        final result = sha256(input) // FFI çağırışı
        sendPort.send(result)
    }, port.sendPort)

    return await port.first as String
}

FFI çağırışını izolyata çıxarmaq UI ipliyinin bloklanmamasına zəmanət verir. Lakin izolyatlar arasında böyük həcmli məlumatların ötürülməsi yaddaşın kopyalanmasını tələb edir. Böyük buferlər (>10 MB) üçün ShareMemory və ya yaddaşla əlaqələndirilmiş fayllardan istifadə etmək daha məqsədəuyğundur.

Tez-tez verilən suallar

FFI Method Channel-dan nə ilə fərqlənir?

FFI C funksiyalarını birbaşa, sinxron və serializasiyasız çağırır — gecikmə 0.01–0.1 μs. Method Channel asinxron işləyir, JSON serializasiyası ilə gecikmə 0.5–5 ms. FFI yüksək performanslı əməliyyatlar üçün uyğundur, Method Channel isə sadə platforma API çağırışları üçün.

C++ funksiyalarını FFI vasitəsilə çağırmaq olar?

Birbaşa — yox, dart:ffi yalnız C funksiyalarını dəstəkləyir. C++ çağırmaq üçün extern "C" ilə C wrapperi yaratmaq lazımdır (C simvolları kimi ixrac edilən giriş nöqtələri). C++ sinifləri metod çağırışlarını C funksiyalarına çevirən əlavə təbəqə tələb edir.

C funksiyalarında səhvləri necə idarə etmək olar?

FFI istisnaları dəstəkləmir — əgər C funksiyası səhv kodu qaytarırsa, onu əl ilə yoxlamaq lazımdır. FFI çağırışlarını Dart-da try-catch ilə əhatə etmək və C funksiyalarının qaytardığı kodları yoxlamaq tövsiyə olunur. Kritik səhvlər (segfault) tutula bilməz.

Hansı kitabxanaları FFI vasitəsilə istifadə etmək olmaz?

FFI mürəkkəb Java (JNI) və ya Objective-C (Message Dispatch) inicializasiyası tələb edən kitabxanalarla işləmir. Məsələn, UIKit və Android Views FFI vasitəsilə əlçatan deyil. Məhdudiyyət FFI-nin C ABI səviyyəsində işləməsi ilə bağlıdır, bu API-lər isə xüsusi icra mühitləri tələb edir.

C kitabxanalarını hər platforma üçün ayrıca kompilyasiya etmək lazımdır?

Bəli, C kitabxanaları hər hədəf platforma üçün ayrıca kompilyasiya olunur. Android üçün .so müxtəlif ABI-lar üçün (armeabi-v7a, arm64-v8a, x86_64) yığılır. iOS üçün — universal .dylib (arm64). Windows üçün — .dll. Flutter avtomatik olaraq kitabxananın düzgün versiyasını qablaşdırır.

Xülasə

  • FFI (Foreign Function Interface) — dart:ffi vasitəsilə Dart-dan C funksiyalarını birbaşa çağırma mexanizmi
  • Performans FFI çağırışları Method Channel-dan 50–500 dəfə yüksəkdir
  • Arxitektura kitabxananın yüklənməsi, imzaların bəyan edilməsi və funksiyaların çağırılmasını əhatə edir
  • Məlumat tipləri dart:ffi Int32, Float, Double, Pointer, Struct və digər C tiplərini dəstəkləyir
  • Yaddaş C tərəfində calloc/free və ya Arena ilə əl ilə idarə olunur
  • Məhdudiyyətlər FFI: runtime tip yoxlaması yox, birbaşa C++ dəstəyi yox, UI ipliyini bloklayır
  • İstifadə edin FFI-ni Flutter-də yerli kitabxanalarla yüksək performanslı inteqrasiya üçün

Açar təslim mobil tətbiq hazırlayacağıq

IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.

Layihəni müzakirə et

Həm də oxuyun