FFI: temeller, Foreign Function Interface ve C entegrasyonu

Yazar: IT Sectr Yayınlanma: 2026-06-05 Okuma süresi: 10 dk

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) — Dart'tan doğrudan C fonksiyonu çağırma mekanizması
  • dart:ffi kütüphane yükleme ve imza bildirimi için API sağlar
  • Performans FFI çağrıları Method Channel'dan 50–100 kat daha yüksektir
  • Tipleme FFI ilkel türleri, yapıları ve C işaretçilerini destekler
  • Flutter yerel kütüphanelerle entegrasyon için FFI kullanır: OpenCV, SQLite, FFmpeg

FFI nedir?

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.

FFI vs Method Channel: temel fark

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 nasıl çalışır?

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.

Temel örnek: Dart'tan C fonksiyonu çağırma

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

FFI'da veri türleri

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)
intInt32int4
longInt64int8
floatFloatdouble4
doubleDoubledouble8
char*Pointer<Int8>Pointer8 (işaretçi)
void*Pointer<Void>Pointer8 (işaretçi)
structPointer<T> (Struct)Pointeralanlara 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.

Yapılar (Struct)

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.

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

FFI pratik örnekleri

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.

FFI aracılığıyla OpenSSL entegrasyonu

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.

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 ö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.

Bellek ayırma ve serbest bırakma

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.

Sınırlamalar ve en iyi uygulamalar

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.

Güvenlik

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.

Platform uyumluluğu

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.

Bellek yönetimi

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.

Performans ve iplikler

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.

dart
// 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, Method Channel'dan nasıl farklıdır?

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.

FFI aracılığıyla C++ fonksiyonları çağrılabilir mi?

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.

C fonksiyonlarındaki hatalar nasıl ele alınır?

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 aracılığıyla hangi kütüphaneler kullanılamaz?

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.

C kütüphaneleri her platform için derlenmeli mi?

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

  • FFI (Foreign Function Interface) — dart:ffi aracılığıyla Dart'tan doğrudan C fonksiyonu çağırma mekanizması
  • Performans FFI çağrıları Method Channel'dan 50–500 kat daha yüksektir
  • Mimari kütüphane yükleme, imza bildirimi ve fonksiyon çağrısını içerir
  • Veri türleri dart:ffi Int32, Float, Double, Pointer, Struct ve diğer C türlerini destekler
  • Bellek C tarafında calloc/free veya Arena aracılığıyla manuel olarak yönetilir
  • Sınırlamalar FFI: çalışma zamanı tür denetimi yok, doğrudan C++ desteği yok, UI ipliğini bloke eder
  • Kullanın Flutter'da yerel kütüphanelerle yüksek performanslı entegrasyon için FFI'yı

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.

Projeyi tartış

Ayrıca okuyun