FFI (Foreign Function Interface) — ay isang mekanismo ng wikang Dart, na ibinigay ng package na dart:ffi, na nagpapahintulot sa direktang pagtawag ng mga function mula sa native na C library, nang walang mga intermediate na layer sa Kotlin, Swift o Java. Naglo-load ang developer ng dynamic na library (.so sa Android, .dylib sa iOS, .dll sa Windows), nagdedeklara ng mga signature ng C function at tinatawag ang mga ito tulad ng ordinaryong Dart function. Ayon sa Dart API Reference (2025), binabawasan ng FFI ang overhead ng mga inter-language na tawag sa 0.1 μs, na sampung beses na mas mabilis kaysa sa pamamagitan ng Method Channel.
Mga pangunahing punto
FFI (Foreign Function Interface) — ay isang mekanismo na nagpapahintulot sa isang programming language na tumawag ng mga function na nakasulat sa ibang mga wika. Sa konteksto ng Dart at Flutter, ang FFI ay nangangahulugang kakayahang tumawag ng mga function mula sa C/C++ library nang direkta mula sa Dart code, nang hindi kinakailangang magsulat ng platform code sa Java (Android) o Swift/Objective-C (iOS).
Ang package na dart:ffi ay lumitaw sa Dart 2.12 (2021) at mula noon ay naging pangunahing kasangkapan para sa pagsasama ng Flutter sa native code. Bago ang pagdating ng dart:ffi, ang tanging paraan upang tumawag ng C function mula sa Dart ay ang Method Channel — isang asynchronous na mekanismo na nagpapadala ng mga mensahe sa pamamagitan ng JSON serialization sa pagitan ng Dart at ng native na bahagi. Ang FFI ay gumagana nang iba: ang Dart code ay direktang umaaccess sa memorya ng C library, tumatawag ng mga function sa pamamagitan ng native ABI (Application Binary Interface) nang walang serialization at walang pagpapalit ng konteksto.
Ang FFI ay lalong kailangan sa mga sitwasyon kung saan kritikal ang pagganap: pagproseso ng imahe (OpenCV), audio (FFmpeg), cryptography (OpenSSL), machine learning (TensorFlow Lite) at mga database (SQLite). Sa lahat ng kasong ito, ang Method Channel ay lumilikha ng hindi katanggap-tanggap na latency, habang ang FFI ay nagbibigay ng pagganap na maihahambing sa native na C/C++ code. Ang library dart:ffi ay sumusuporta rin sa pagtatrabaho sa memorya: paglalaan, pagpapalaya at pamamahala ng mga pointer.
Ang Method Channel ay gumagana nang asynchronous: nagpapadala ang Dart ng mensahe sa native code, pinoproseso ito ng native code at ibinabalik ang resulta. Ang bawat tawag ay nangangailangan ng serialization ng mga argumento sa Map, pagpapadala sa pamamagitan ng pila at deserialization. Ito ay tumatagal ng 0.5–5 ms bawat tawag. Ang FFI ay gumagana nang synchronous at walang serialization — ang tawag sa C function ay tumatagal ng 0.01–0.1 μs. Pagkakaiba ng 50–500 beses, na kritikal para sa mataas na dalas ng mga operasyon.
Ang pagtatrabaho sa dart:ffi ay binubuo ng tatlong yugto: pag-load ng library, pagdedeklara ng mga signature at pagtawag ng mga function. Ang bawat yugto ay gumagamit ng mahigpit na tipifikasyon ng Dart, na nagpapaliit ng mga error sa runtime.
Sa unang yugto, ang dynamic na library ay nilo-load sa pamamagitan ng klase na DynamicLibrary. Ang library ay maaaring i-load sa pamamagitan ng pangalan (libxyz.so, libxyz.dylib, xyz.dll) o sa pamamagitan ng buong landas. Awtomatikong hinahanap ng Dart ang library sa mga karaniwang landas ng system. DynamicLibrary ay nagbibigay ng pamamaraang lookupFunction, na nag-uugnay ng Dart function sa C function batay sa pangalan ng simbolo.
Sa ikalawang yugto, isang Dart function ay idineklara na may mga type annotation na tumutugma sa C signature. Para dito ginagamit ang mga espesyal na uri mula sa dart:ffi: Int32, Float, Double, Pointer, NativeFunction, Handle at iba pa. Ang annotation na lookupFunction ay tumatanggap ng dalawang generic na parameter: ang uri ng Dart function (kung paano ito magmumukha sa Dart) at ang uri ng native na C function (kung paano ito idineklara sa C).
Sa ikatlong yugto, ang nabuong Dart function ay tinatawag tulad ng ordinaryong function. Ang mga argumento ay direktang ipinapasa, ang resulta ay agad na ibinabalik. Kung ang C function ay nagbabago ng memorya sa pamamagitan ng mga pointer, maaaring basahin ng Dart ang mga pagbabagong ito sa pamamagitan ng klase na Pointer. Ang pamamahala ng memorya sa C side ay nananatiling responsibilidad ng developer — hindi pinamamahalaan ng dart:ffi ang memorya na inilaan ng malloc sa C.
import 'dart:ffi'
import 'package:ffi/ffi.dart'
// Deklarasyon ng C function: 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
}
Sa halimbawang ito, ang add ay isang C function na tumatanggap ng dalawang int at nagbabalik ng int. Inilalarawan ng typedef AddNative ang C signature na may mga uri ng dart:ffi, at ang AddDart — kung paano magmumukha ang function na ito sa Dart. Pinag-uugnay sila ng lookupFunction at nagbabalik ng Dart function na maaaring tawagin tulad ng ordinaryong function.
Ang dart:ffi ay nagbibigay ng isang set ng mga uri na tumutugma sa mga uri ng C. Ang bawat uri ay may nakapirming laki at mga panuntunan sa conversion sa pagitan ng Dart at C. Ang pag-unawa sa kaukulang uri ay kritikal para sa tamang paggana ng FFI — ang error sa laki o tanda ng uri ay maaaring maging sanhi ng pag-crash ng application.
| C uri | dart:ffi uri | Dart uri | Laki (byte) |
|---|---|---|---|
| int | Int32 | int | 4 |
| long | Int64 | int | 8 |
| float | Float | double | 4 |
| double | Double | double | 8 |
| char* | Pointer<Int8> | Pointer | 8 (pointer) |
| void* | Pointer<Void> | Pointer | 8 (pointer) |
| struct | Pointer<T> (Struct) | Pointer | depende sa mga field |
Para sa pagtatrabaho sa C string (char*) gumagamit ang dart:ffi ng Pointer<Int8>. Ang conversion mula sa Dart String patungong C char* at vice versa ay isinasagawa sa pamamagitan ng mga pamamaraang toNativeUtf8 (mula sa package na ffi) at fromUtf8. Mahalagang palayain ang C string pagkatapos gamitin sa pamamagitan ng calloc.free upang maiwasan ang pagtagas ng memorya.
Sinusuportahan ng dart:ffi ang pagdedeklara ng C structures bilang mga Dart class na nagmamana ng Struct. Ang mga field ng istraktura ay idineklara na may mga annotation na @Int32(), @Float(), @Array() at iba pa. Ang laki at offset ng mga field ay awtomatikong kinakalkula ayon sa ABI ng platform. Pointer<Point> ay maaaring makuha mula sa C function na nagbabalik ng pointer sa istraktura o ilaan sa Dart sa pamamagitan ng calloc.
// C structure: typedef struct { int x; int y; } Point;
final class Point extends Struct {
@Int32()
external int x
@Int32()
external int y
}
// Pagtawag ng C function na nagbabalik ng 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) // palayain ang memorya
Ang klase na Point ay nagmamana ng Struct at nagdedeklara ng mga field na x at y na may mga annotation na @Int32(). Ang nabuong C code ay magkakaroon ng eksaktong parehong layout ng mga field sa memorya. Pointer.ref ay nagbibigay ng access sa mga field ng istraktura sa pamamagitan ng mga getter at setter.
Tingnan natin ang isang mas kumplikadong halimbawa — pagsasama sa C library para sa pagkalkula ng SHA256 hash. Ito ay isang tipikal na gawain kung saan ang FFI ay nagbibigay ng makabuluhang kalamangan sa pagganap kumpara sa Method Channel.
Ang library na OpenSSL ay nagbibigay ng function na SHA256 na nagkakalkula ng hash ng isang string. Sa pamamagitan ng dart:ffi maaari natin itong tawagan nang direkta, nang walang pagsusulat ng Java o Swift wrapper. Ito ay isang halimbawa kung paano pinapayagan ng FFI ang muling paggamit ng mga umiiral na C library sa Flutter.
import 'dart:ffi'
import 'package:ffi/ffi.dart'
// Signature: 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 byte
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
}
Sa halimbawang ito, ang function na sha256 ay naglo-load ng library na libcrypto.so, hinahanap ang simbolong SHA256 at tinatawag ito gamit ang mga pointer sa input at output data. toNativeUtf8 ay nagko-convert ng Dart String sa C string (naglalaan ng memorya), at ang asTypedList ay nagpapahintulot sa pagbasa ng byte array ng resulta. Ang memorya ay pinalalaya pagkatapos gamitin — ito ay isang mandatoryong hakbang upang maiwasan ang pagtagas.
Ang package na ffi ay nagbibigay ng function na calloc para sa paglalaan ng memorya na compatible sa C. Ang inilaang memorya ay dapat palayain sa pamamagitan ng calloc.free, kung hindi ay magkakaroon ng pagtagas. Para sa awtomatikong pamamahala ng memorya, maaaring gamitin ang klase na Arena mula sa package na ffi, na nagpapalaya ng lahat ng memorya na inilaan dito kapag tinawag ang arena.release(). Ito ay lalong maginhawa kapag mayroong maraming pansamantalang paglalaan.
Sa kabila ng kapangyarihan ng FFI, mayroon itong mga limitasyon na dapat isaalang-alang kapag nagdidisenyo ng arkitektura ng Flutter application. Ang mga pangunahing limitasyon ay nauugnay sa kaligtasan ng uri, pamamahala ng memorya at compatibility ng platform.
Hindi sinusuri ng FFI ang mga uri sa runtime. Kung ang C function ay umaasa ng pointer ngunit isang numero ang ipinasa, ang application ay mag-crash na may segmentation fault. Inirerekomenda ang paggamit ng FFIgen — isang tool na bumubuo ng type-safe na Dart wrapper batay sa C header (.h file). FFIgen ay nagsusuri ng mga deklarasyon ng C function at lumilikha ng Dart code na may tamang mga uri, na nag-aalis ng mga error sa yugto ng pagsusulat ng code.
Ang mga pangalan at landas patungo sa dynamic na library ay naiiba sa iba't ibang platform: libxyz.so sa Android/Linux, libxyz.dylib sa iOS/macOS, xyz.dll sa Windows. Para sa cross-platform na library ginagamit ang conditional compilation sa pamamagitan ng dart:io (Platform.isAndroid, Platform.isIOS) o abstraction tulad ng package:ffi. Inirerekomenda na lumikha ng factory method na nagbabalik ng tamang library para sa kasalukuyang platform.
Hindi pinamamahalaan ng FFI ang memorya sa C side. Kung ang C function ay naglalaan ng memorya sa pamamagitan ng malloc, dapat itong palayain sa pamamagitan ng free, kung hindi ay magkakaroon ng pagtagas. Sa Dart walang garbage collector para sa C memorya. Rekomendasyon: palaging palayain ang memorya sa parehong pamamaraan kung saan ito inilaan o gumamit ng Arena para sa group na pagpapalaya.
Ang mga tawag sa FFI ay isinasagawa sa parehong thread gaya ng Dart code. Ang mahahabang synchronous na operasyon (higit sa 10 ms) ay humaharang sa UI thread at nagdudulot ng pagbagsak ng frame. Para sa mahahabang operasyon, ang C function ay dapat tawagin sa isang isolate o tiyakin na ang C function ay magsisimula ng trabaho sa background thread at magbibigay-alam sa Dart sa pamamagitan ng Port o callback.
// FFI sa isolate para sa mahabang operasyon
import 'dart:isolate'
Future<String> computeHash(String input) async {
final port = ReceivePort()
await Isolate.spawn((SendPort sendPort) {
final result = sha256(input) // Tawag sa FFI
sendPort.send(result)
}, port.sendPort)
return await port.first as String
}
Ang paglipat ng FFI call sa isang isolate ay ginagarantiyahan na ang UI thread ay hindi haharangin. Gayunpaman, ang paglilipat ng malalaking volume ng data sa pagitan ng mga isolate ay nangangailangan ng pagkopya ng memorya. Para sa malalaking buffer (>10 MB) mas mainam na gumamit ng ShareMemory o memory-mapped na mga file.
Mga madalas itanong
Ang FFI ay tumatawag ng C function nang direkta, synchronous at walang serialization — latency 0.01–0.1 μs. Ang Method Channel ay gumagana nang asynchronous sa pamamagitan ng JSON serialization na may latency 0.5–5 ms. Ang FFI ay angkop para sa mataas na pagganap na operasyon, ang Method Channel para sa simpleng platform API tawag.
Direkta — hindi, ang dart:ffi ay sumusuporta lamang sa C function. Para tumawag ng C++ kailangan lumikha ng C wrapper na may extern "C" (mga entry point na ini-export bilang C simbolo). Ang mga C++ class ay nangangailangan ng karagdagang layer na nagko-convert ng mga tawag sa pamamaraan sa C function.
Hindi sinusuportahan ng FFI ang mga exception — kung ang C function ay nagbabalik ng error code, dapat itong suriin nang manu-mano. Inirerekomenda na balutin ang mga tawag sa FFI sa try-catch sa Dart at suriin ang mga ibinalik na code ng C function. Ang mga kritikal na error (segfault) ay hindi mahuhuli.
Ang FFI ay hindi gumagana sa mga library na nangangailangan ng kumplikadong initialization ng Java (JNI) o Objective-C (Message Dispatch). Halimbawa, ang UIKit at Android Views ay hindi accessible sa pamamagitan ng FFI. Ang limitasyon ay nauugnay sa katotohanan na ang FFI ay gumagana sa antas ng C ABI, habang ang mga API na ito ay nangangailangan ng mga tiyak na runtime.
Oo, ang C library ay ini-compile nang hiwalay para sa bawat target na platform. Para sa Android, ang .so ay binuo para sa iba't ibang ABI (armeabi-v7a, arm64-v8a, x86_64). Para sa iOS — isang unibersal na .dylib (arm64). Para sa Windows — .dll. Ang Flutter ay awtomatikong nagpa-package ng tamang bersyon ng library kapag nagbu-build.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din