FFI (Foreign Function Interface), dart:ffi paketi tarafından sağlanan Dart dilinin bir mekanizmasıdır ve Kotlin, Swift veya Java'da ara katmanlar olmadan doğrudan yerel C kütüphanelerinden fonksiyon çağırmaya olanak tanır. Geliştirici dinamik bir kütüphane (Android'de .so, iOS'ta .dylib, Windows'ta .dll) yükler, C fonksiyon imzalarını bildirir ve bunları normal Dart fonksiyonları gibi çağırır. Dart API Referansı (2025)'na göre, FFI diller arası çağrıların ek yükünü 0,1 µs'ye düşürür ve bu, Method Channel'dan onlarca kat daha hızlıdır.
Anahtar Noktalar
FFI (Foreign Function Interface), bir programlama dilinin diğer dillerde yazılmış fonksiyonları çağırmasına olanak tanıyan bir mekanizmadır. Dart ve Flutter bağlamında FFI, Java (Android) veya Swift/Objective-C (iOS) ile platforma özgü kod yazmaya gerek kalmadan doğrudan Dart kodundan C/C++ kütüphanelerinin fonksiyonlarını çağırma yeteneği anlamına gelir.
dart:ffi paketi Dart 2.12'de (2021) ortaya çıktı ve o zamandan beri Flutter'ı yerel kodla entegre etmek için önemli bir araç haline geldi. dart:ffi'den önce, Dart'tan bir C fonksiyonu çağırmanın tek yolu Method Channel'dı — Dart ve yerel taraf arasında JSON serileştirme yoluyla mesajları ileten asenkron bir mekanizma. FFI farklı çalışır: Dart kodu, C kütüphane belleğine doğrudan erişir ve serileştirme veya bağlam değiştirme olmadan yerel ABI (Uygulama İkili Arayüzü) aracılığıyla fonksiyonları çağırır.
FFI, özellikle performansın kritik olduğu senaryolarda talep edilir: görüntü işleme (OpenCV), ses (FFmpeg), şifreleme (OpenSSL), makine öğrenimi (TensorFlow Lite) ve veritabanları (SQLite). Tüm bu durumlarda, Method Channel kabul edilemez gecikmeler yaratırken, FFI yerel C/C++ koduna benzer performans sağlar. dart:ffi kütüphanesi ayrıca bellek yönetimini de destekler: ayırma, serbest bırakma ve işaretçi işlemleri.
Method Channel asenkron çalışır: Dart yerel koda bir mesaj gönderir, yerel kod onu işler ve sonucu geri gönderir. Her çağrı, argümanların bir Map'e serileştirilmesini, bir kuyruktan geçirilmesini ve deserileştirmeyi gerektirir. Bu, çağrı başına 0,5–5 ms sürer. FFI senkron ve serileştirme olmadan çalışır — bir C fonksiyon çağrısı 0,01–0,1 µs sürer. 50–500 kat fark, yüksek frekanslı işlemler için kritiktir.
dart:ffi ile çalışmak üç aşamadan oluşur: kütüphaneyi yükleme, imzaları bildirme ve fonksiyonları çağırma. Her aşama, Dart'ın katı tiplemesini kullanarak çalışma zamanı hatalarını en aza indirir.
İlk aşamada, dinamik kütüphane DynamicLibrary sınıfı aracılığıyla yüklenir. Kütüphane adla (libxyz.so, libxyz.dylib, xyz.dll) veya tam yolla yüklenebilir. Dart, kütüphaneyi sistemin standart yollarında otomatik olarak arar. DynamicLibrary, sembol adına göre bir Dart fonksiyonunu bir C fonksiyonuna bağlayan lookupFunction yöntemini sağlar.
İkinci aşamada, C imzasına karşılık gelen tür ek açıklamalarıyla bir Dart fonksiyonu bildirilir. dart:ffi'den özel türler kullanılır: Int32, Float, Double, Pointer, NativeFunction, Handle ve diğerleri. lookupFunction ek açıklaması iki genel parametre alır: Dart fonksiyon türü (Dart'ta nasıl görüneceği) ve yerel C fonksiyon türü (C'de nasıl bildirildiği).
Üçüncü aşamada, oluşturulan Dart fonksiyonu normal bir fonksiyon gibi çağrılır. Argümanlar doğrudan iletilir, sonuç hemen döndürülür. C fonksiyonu işaretçiler aracılığıyla belleği değiştirirse, Dart bu değişiklikleri Pointer sınıfı aracılığıyla okuyabilir. C tarafındaki bellek yönetimi geliştiricinin sorumluluğundadır — dart:ffi, C'de malloc tarafından ayrılan belleği yönetmez.
import 'dart:ffi'
import 'package:ffi/ffi.dart'
// C fonksiyon bildirimi: 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 örnekte add, iki int alan ve bir int döndüren bir C fonksiyonudur. AddNative typedef, dart:ffi türleriyle C imzasını tanımlarken, AddDart bu fonksiyonun Dart'ta nasıl görüneceğini tanımlar. lookupFunction bunları bağlar ve normal bir şekilde çağrılabilen bir Dart fonksiyonu döndürür.
dart:ffi, C türlerine karşılık gelen bir dizi tür sağlar. Her türün sabit bir boyutu ve Dart ile C arasında dönüştürme kuralları vardır. FFI'nin doğru çalışması için tür eşlemesini anlamak kritik öneme sahiptir — türün boyutunda veya işaretinde bir hata uygulamanın çökmesine neden olabilir.
| C türü | dart:ffi türü | Dart türü | Boyut (bayt) |
|---|---|---|---|
| int | Int32 | int | 4 |
| long | Int64 | int | 8 |
| float | Float | double | 4 |
| double | Double | double | 8 |
| char* | Pointer<Int8> | Pointer | 8 (işaretçi) |
| void* | Pointer<Void> | Pointer | 8 (işaretçi) |
| struct | Pointer<T> (Struct) | Pointer | alanlara bağlı |
C dizeleriyle (char*) çalışmak için dart:ffi, Pointer<Int8> kullanır. Dart String'den C char*'a ve geri dönüşüm, toNativeUtf8 (ffi paketinden) ve fromUtf8 aracılığıyla yapılır. Bellek sızıntısını önlemek için kullanımdan sonra C dizelerini calloc.free aracılığıyla serbest bırakmak önemlidir.
dart:ffi, C yapılarını Struct'u genişleten Dart sınıfları olarak bildirmeyi destekler. Yapı alanları @Int32(), @Float(), @Array() ve diğer ek açıklamalarla bildirilir. Alanların boyutu ve ofseti, platformun ABI'sine göre otomatik olarak hesaplanır. Pointer<Point>, bir yapıya işaretçi döndüren bir C fonksiyonundan alınabilir veya Dart'ta calloc aracılığıyla ayrılabilir.
// C yapısı: typedef struct { int x; int y; } Point;
final class Point extends Struct {
@Int32()
external int x
@Int32()
external int y
}
// Point* döndüren C fonksiyonunu çağırma
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) // belleği serbest bırak
Point sınıfı Struct'u genişletir ve @Int32() ek açıklamalarıyla x ve y alanlarını bildirir. Oluşturulan C kodu tamamen aynı bellek düzenine sahip olacaktır. Pointer.ref, getter ve setter'lar aracılığıyla yapı alanlarına erişim sağlar.
Daha karmaşık bir örneğe bakalım — SHA256 hash'ini hesaplamak için bir C kütüphanesiyle entegrasyon. Bu, FFI'nin Method Channel'a kıyasla önemli performans avantajı sağladığı tipik bir görevdir.
OpenSSL kütüphanesi, bir dizenin hash'ini hesaplayan SHA256 fonksiyonunu sağlar. dart:ffi aracılığıyla, Java veya Swift sarmalayıcıları yazmadan doğrudan çağırabiliriz. Bu, FFI'nin Flutter'da mevcut C kütüphanelerini yeniden kullanmaya nasıl izin verdiğinin bir örneğidir.
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 örnekte, sha256 işlevi libcrypto.so kütüphanesini yükler, SHA256 sembolünü bulur ve onu girdi ve çıktı verilerine işaretçilerle çağırır. toNativeUtf8, Dart String'ini C dizesine dönüştürür (bellek ayırır) ve asTypedList, sonuç bayt dizisini okumaya izin verir. Kullanımdan sonra bellek serbest bırakılır — bu, sızıntıları önlemek için zorunlu bir adımdır.
ffi paketi, C uyumlu bellek ayırmak için calloc işlevini sağlar. Ayrılan bellek calloc.free aracılığıyla serbest bırakılmalıdır, aksi takdirde sızıntı oluşur. Otomatik bellek yönetimi için, ffi paketinden Arena sınıfını kullanabilirsiniz; arena.release() çağrıldığında içinde ayrılan tüm belleği serbest bırakır. Bu, çok sayıda geçici ayırma için özellikle kullanışlıdır.
FFI'nin gücüne rağmen, Flutter uygulaması mimarisi tasarlanırken dikkate alınması gereken sınırlamaları vardır. Ana sınırlamalar tür güvenliği, bellek yönetimi ve platform uyumluluğuyla ilgilidir.
FFI, çalışma zamanında tür denetimi yapmaz. Bir C fonksiyonu bir işaretçi bekler ancak bir sayı alırsa, uygulama segmentasyon hatasıyla çöker. C başlık dosyalarına (.h) dayalı tür güvenli Dart sarmalayıcıları oluşturan bir araç olan FFIgen'in kullanılması önerilir. FFIgen, C fonksiyon bildirimlerini analiz eder ve doğru türlerle Dart kodu oluşturarak kod yazma aşamasındaki hataları ortadan kaldırır.
Dinamik kütüphanelerin adları ve yolları platformlar arasında farklılık gösterir: Android/Linux'ta libxyz.so, iOS/macOS'te libxyz.dylib, Windows'ta xyz.dll. Platformlar arası kütüphaneler için, dart:io (Platform.isAndroid, Platform.isIOS) veya package:ffi gibi soyutlamalar aracılığıyla koşullu derleme kullanılır. Geçerli platform için doğru kütüphaneyi döndüren bir fabrika yöntemi oluşturulması önerilir.
FFI, C tarafındaki belleği yönetmez. Bir C fonksiyonu malloc aracılığıyla bellek ayırırsa, free aracılığıyla serbest bırakılmalıdır, aksi takdirde sızıntı oluşur. Dart'ta C belleği için çöp toplayıcı yoktur. Öneri: belleği her zaman ayrıldığı yöntemde serbest bırakın veya grup serbest bırakma için Arena kullanın.
FFI çağrıları, Dart koduyla aynı iplikte yürütülür. Uzun senkron işlemler (10 ms'den fazla) UI ipliğini bloke eder ve kare düşüşlerine neden olur. Uzun işlemler için, C fonksiyonu bir Isolate'de çağrılmalı veya C fonksiyonunun işi bir arka plan ipliğinde çalıştırdığından ve Port veya geri arama aracılığıyla Dart'a bildirdiğinden emin olunmalıdır.
// Uzun işlemler için isolate'de 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ısı
sendPort.send(result)
}, port.sendPort)
return await port.first as String
}
FFI çağrılarını bir isolate'e taşımak, UI ipliğinin bloke edilmemesini sağlar. Ancak, isolates arasında büyük miktarlarda veri aktarımının bellek kopyalama gerektirdiği unutulmamalıdır. Büyük arabellekler (>10 MB) için, SharedMemory veya bellekle eşlenmiş dosyaların kullanılması tercih edilir.
Sıkça Sorulan Sorular
FFI, C fonksiyonlarını doğrudan, senkron ve serileştirme olmadan çağırır — gecikme 0,01–0,1 µs. Method Channel, JSON serileştirme aracılığıyla asenkron olarak 0,5–5 ms gecikmeyle çalışır. FFI yüksek performanslı işlemler için uygundur, Method Channel basit platform API çağrıları için.
Doğrudan — hayır, dart:ffi yalnızca C fonksiyonlarını destekler. C++ çağırmak için, extern “C” ile bir C sarmalayıcısı oluşturmanız gerekir (C sembolleri olarak dışa aktarılan giriş noktaları). C++ sınıfları, yöntem çağrılarını C fonksiyonlarına dönüştüren ek bir katman gerektirir.
FFI istisnaları desteklemez — bir C fonksiyonu bir hata kodu döndürürse, manuel olarak kontrol edilmelidir. Dart'ta FFI çağrılarını try-catch içine sarmak ve C fonksiyonlarının dönüş kodlarını kontrol etmek önerilir. Kritik hatalar (segfault) yakalanamaz.
FFI, karmaşık Java (JNI) veya Objective-C (Message Dispatch) başlatması gerektiren kütüphanelerle çalışmaz. Örneğin, UIKit ve Android Views, FFI aracılığıyla erişilemez. Sınırlama, FFI'nin C ABI düzeyinde çalışması, bu API'lerin ise belirli çalışma zamanları gerektirmesiyle ilgilidir.
Evet, C kütüphaneleri her hedef platform için ayrı ayrı derlenir. Android için .so, farklı ABI'ler (armeabi-v7a, arm64-v8a, x86_64) için oluşturulur. iOS için — evrensel bir .dylib (arm64). Windows için — .dll. Flutter, derleme sırasında otomatik olarak doğru kütüphane sürümünü paketler.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.