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) — 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.
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 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.
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.
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 tipi | dart:ffi tipi | Dart tipi | Ölçü (bayt) |
|---|---|---|---|
| int | Int32 | int | 4 |
| long | Int64 | int | 8 |
| float | Float | double | 4 |
| double | Double | double | 8 |
| char* | Pointer<Int8> | Pointer | 8 (göstərici) |
| void* | Pointer<Void> | Pointer | 8 (göstərici) |
| struct | Pointer<T> (Struct) | Pointer | sahə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.
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.
// 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.
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 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.
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.
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.
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.
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.
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.
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.
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.
// 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 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.
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.
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.
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.
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ə
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.
Həm də oxuyun