FFI (Foreign Function Interface) Dart भाषा का एक तंत्र है, जो dart:ffi पैकेज द्वारा प्रदान किया जाता है, जो Kotlin, Swift या Java में मध्यवर्ती परतों के बिना सीधे नेटिव C लाइब्रेरीज़ से फ़ंक्शन कॉल करने की अनुमति देता है। डेवलपर एक डायनामिक लाइब्रेरी (.so Android पर, .dylib iOS पर, .dll Windows पर) लोड करता है, C फ़ंक्शन सिग्नेचर घोषित करता है और उन्हें सामान्य Dart फ़ंक्शन की तरह कॉल करता है। Dart API Reference (2025) के अनुसार, FFI अंतर-भाषा कॉल के ओवरहेड को 0.1 µs तक कम करता है, जो Method Channel की तुलना में दस गुना तेज़ है।
मुख्य बातें
FFI (Foreign Function Interface) एक तंत्र है जो प्रोग्रामिंग भाषा को अन्य भाषाओं में लिखे फ़ंक्शन कॉल करने की अनुमति देता है। Dart और Flutter के संदर्भ में, FFI का अर्थ है Java (Android) या Swift/Objective-C (iOS) में प्लेटफ़ॉर्म-विशिष्ट कोड लिखे बिना सीधे Dart कोड से C/C++ लाइब्रेरीज़ के फ़ंक्शन कॉल करने की क्षमता।
dart:ffi पैकेज Dart 2.12 (2021) में दिखाई दिया और तब से Flutter को नेटिव कोड से जोड़ने के लिए एक महत्वपूर्ण उपकरण बन गया है। dart:ffi से पहले, Dart से C फ़ंक्शन कॉल करने का एकमात्र तरीका Method Channel था — एक अतुल्यकालिक तंत्र जो Dart और नेटिव पक्ष के बीच JSON सीरियलाइज़ेशन के माध्यम से संदेश भेजता था। FFI अलग तरीके से काम करता है: Dart कोड सीधे C लाइब्रेरी मेमोरी तक पहुंचता है, बिना सीरियलाइज़ेशन या संदर्भ स्विचिंग के नेटिव ABI (एप्लिकेशन बाइनरी इंटरफ़ेस) के माध्यम से फ़ंक्शन कॉल करता है।
FFI विशेष रूप से उन परिदृश्यों में मांग में है जहां प्रदर्शन महत्वपूर्ण है: छवि प्रसंस्करण (OpenCV), ऑडियो (FFmpeg), क्रिप्टोग्राफी (OpenSSL), मशीन लर्निंग (TensorFlow Lite) और डेटाबेस (SQLite)। इन सभी मामलों में, Method Channel अस्वीकार्य विलंब पैदा करता है, जबकि FFI नेटिव C/C++ कोड के बराबर प्रदर्शन प्रदान करता है। dart:ffi लाइब्रेरी मेमोरी प्रबंधन का भी समर्थन करती है: आवंटन, मुक्ति और पॉइंटर संचालन।
Method Channel अतुल्यकालिक रूप से काम करता है: Dart नेटिव कोड को एक संदेश भेजता है, नेटिव कोड इसे संसाधित करता है और परिणाम वापस भेजता है। प्रत्येक कॉल में Map में तर्कों के सीरियलाइज़ेशन, कतार के माध्यम से पासिंग और डीसीरियलाइज़ेशन की आवश्यकता होती है। इसमें प्रति कॉल 0.5–5 ms लगते हैं। FFI समकालिक रूप से और बिना सीरियलाइज़ेशन के काम करता है — C फ़ंक्शन कॉल में 0.01–0.1 µs लगते हैं। 50–500 गुना का अंतर, जो उच्च-आवृत्ति संचालन के लिए महत्वपूर्ण है।
dart:ffi के साथ काम करने में तीन चरण होते हैं: लाइब्रेरी लोड करना, सिग्नेचर घोषित करना और फ़ंक्शन कॉल करना। प्रत्येक चरण Dart की सख्त टाइपिंग का उपयोग करता है, जिससे रनटाइम त्रुटियां कम होती हैं।
पहले चरण में, DynamicLibrary वर्ग के माध्यम से डायनामिक लाइब्रेरी लोड की जाती है। लाइब्रेरी को नाम (libxyz.so, libxyz.dylib, xyz.dll) या पूर्ण पथ से लोड किया जा सकता है। Dart स्वचालित रूप से सिस्टम के मानक पथों में लाइब्रेरी खोजता है। DynamicLibrary lookupFunction विधि प्रदान करता है, जो प्रतीक नाम से Dart फ़ंक्शन को C फ़ंक्शन से बांधता है।
दूसरे चरण में, C सिग्नेचर के अनुरूप प्रकार एनोटेशन के साथ एक Dart फ़ंक्शन घोषित किया जाता है। dart:ffi से विशेष प्रकार उपयोग किए जाते हैं: Int32, Float, Double, Pointer, NativeFunction, Handle और अन्य। lookupFunction एनोटेशन दो जेनेरिक पैरामीटर लेता है: Dart फ़ंक्शन प्रकार (यह Dart में कैसा दिखेगा) और नेटिव C फ़ंक्शन प्रकार (यह C में कैसे घोषित है)।
तीसरे चरण में, उत्पन्न Dart फ़ंक्शन को सामान्य फ़ंक्शन की तरह कॉल किया जाता है। तर्क सीधे पास किए जाते हैं, परिणाम तुरंत लौटाया जाता है। यदि C फ़ंक्शन पॉइंटर के माध्यम से मेमोरी को संशोधित करता है, तो Dart Pointer वर्ग के माध्यम से इन परिवर्तनों को पढ़ सकता है। C पक्ष का मेमोरी प्रबंधन डेवलपर की जिम्मेदारी है — dart:ffi C में malloc द्वारा आवंटित मेमोरी का प्रबंधन नहीं करता है।
import 'dart:ffi'
import 'package:ffi/ffi.dart'
// C फ़ंक्शन घोषणा: 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
}
इस उदाहरण में, add एक C फ़ंक्शन है जो दो int लेता है और एक int लौटाता है। AddNative typedef dart:ffi प्रकारों के साथ C सिग्नेचर का वर्णन करता है, जबकि AddDart वर्णन करता है कि यह फ़ंक्शन Dart में कैसा दिखेगा। lookupFunction उन्हें बांधता है और एक Dart फ़ंक्शन लौटाता है जिसे सामान्य की तरह कॉल किया जा सकता है।
dart:ffi C प्रकारों के अनुरूप प्रकारों का एक सेट प्रदान करता है। प्रत्येक प्रकार का एक निश्चित आकार और Dart और C के बीच रूपांतरण नियम होते हैं। FFI के सही संचालन के लिए प्रकार मैपिंग को समझना महत्वपूर्ण है — प्रकार के आकार या चिह्न में त्रुटि एप्लिकेशन को क्रैश कर सकती है।
| C प्रकार | dart:ffi प्रकार | Dart प्रकार | आकार (बाइट्स) |
|---|---|---|---|
| int | Int32 | int | 4 |
| long | Int64 | int | 8 |
| float | Float | double | 4 |
| double | Double | double | 8 |
| char* | Pointer<Int8> | Pointer | 8 (पॉइंटर) |
| void* | Pointer<Void> | Pointer | 8 (पॉइंटर) |
| struct | Pointer<T> (Struct) | Pointer | फ़ील्ड पर निर्भर करता है |
C स्ट्रिंग (char*) के साथ काम करने के लिए, dart:ffi Pointer<Int8> का उपयोग करता है। Dart String से C char* और वापस रूपांतरण toNativeUtf8 (ffi पैकेज से) और fromUtf8 के माध्यम से किया जाता है। मेमोरी लीक से बचने के लिए उपयोग के बाद C स्ट्रिंग को calloc.free के माध्यम से मुक्त करना महत्वपूर्ण है।
dart:ffi C संरचनाओं को Dart वर्गों के रूप में घोषित करने का समर्थन करता है जो Struct का विस्तार करते हैं। संरचना फ़ील्ड @Int32(), @Float(), @Array() और अन्य एनोटेशन के साथ घोषित किए जाते हैं। फ़ील्ड का आकार और ऑफ़सेट प्लेटफ़ॉर्म के ABI के अनुसार स्वचालित रूप से गणना की जाती है। Pointer<Point> C फ़ंक्शन से प्राप्त किया जा सकता है जो संरचना पर पॉइंटर लौटाता है, या Dart में calloc के माध्यम से आवंटित किया जा सकता है।
// C संरचना: typedef struct { int x; int y; } Point;
final class Point extends Struct {
@Int32()
external int x
@Int32()
external int y
}
// C फ़ंक्शन कॉल करना जो 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) // मेमोरी मुक्त करें
Point वर्ग Struct का विस्तार करता है और @Int32() एनोटेशन के साथ x और y फ़ील्ड घोषित करता है। उत्पन्न C कोड में बिल्कुल समान मेमोरी लेआउट होगा। Pointer.ref गेटर्स और सेटर्स के माध्यम से संरचना फ़ील्ड तक पहुंच प्रदान करता है।
आइए एक अधिक जटिल उदाहरण देखें — SHA256 हैश की गणना के लिए C लाइब्रेरी के साथ एकीकरण। यह एक विशिष्ट कार्य है जहां FFI Method Channel की तुलना में महत्वपूर्ण प्रदर्शन लाभ प्रदान करता है।
OpenSSL लाइब्रेरी SHA256 फ़ंक्शन प्रदान करती है, जो स्ट्रिंग का हैश गणना करती है। dart:ffi के माध्यम से, हम Java या Swift रैपर लिखे बिना इसे सीधे कॉल कर सकते हैं। यह एक उदाहरण है कि FFI Flutter में मौजूदा C लाइब्रेरीज़ के पुन: उपयोग की अनुमति कैसे देता है।
import 'dart:ffi'
import 'package:ffi/ffi.dart'
// हस्ताक्षर: 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 बाइट्स
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
}
इस उदाहरण में, sha256 फ़ंक्शन libcrypto.so लाइब्रेरी लोड करता है, SHA256 प्रतीक ढूंढता है और इनपुट और आउटपुट डेटा के पॉइंटर के साथ इसे कॉल करता है। toNativeUtf8 Dart String को C स्ट्रिंग में बदलता है (मेमोरी आवंटित करता है), और asTypedList परिणाम बाइट सरणी पढ़ने की अनुमति देता है। उपयोग के बाद मेमोरी मुक्त की जाती है — लीक को रोकने के लिए यह अनिवार्य कदम है।
ffi पैकेज C-संगत मेमोरी आवंटित करने के लिए calloc फ़ंक्शन प्रदान करता है। आवंटित मेमोरी को calloc.free के माध्यम से मुक्त किया जाना चाहिए, अन्यथा लीक होगा। स्वचालित मेमोरी प्रबंधन के लिए, आप ffi पैकेज से Arena वर्ग का उपयोग कर सकते हैं, जो arena.release() कॉल करने पर इसमें आवंटित सभी मेमोरी मुक्त कर देता है। यह विशेष रूप से बड़ी संख्या में अस्थायी आवंटन के लिए सुविधाजनक है।
FFI की शक्ति के बावजूद, इसकी सीमाएं हैं जिन्हें Flutter एप्लिकेशन आर्किटेक्चर डिजाइन करते समय ध्यान में रखा जाना चाहिए। मुख्य सीमाएं प्रकार सुरक्षा, मेमोरी प्रबंधन और प्लेटफ़ॉर्म संगतता से संबंधित हैं।
FFI रनटाइम पर प्रकारों की जांच नहीं करता है। यदि C फ़ंक्शन पॉइंटर की अपेक्षा करता है लेकिन एक संख्या प्राप्त करता है, तो एप्लिकेशन सेग्मेंटेशन फॉल्ट के साथ क्रैश हो जाएगा। FFIgen का उपयोग करने की अनुशंसा की जाती है — एक उपकरण जो C हेडर फ़ाइलों (.h) के आधार पर प्रकार-सुरक्षित Dart रैपर उत्पन्न करता है। FFIgen C फ़ंक्शन घोषणाओं का विश्लेषण करता है और सही प्रकारों के साथ Dart कोड बनाता है, कोड लिखने के चरण में त्रुटियों को समाप्त करता है।
डायनामिक लाइब्रेरीज़ के नाम और पथ विभिन्न प्लेटफ़ॉर्म पर भिन्न होते हैं: Android/Linux पर libxyz.so, iOS/macOS पर libxyz.dylib, Windows पर xyz.dll। क्रॉस-प्लेटफ़ॉर्म लाइब्रेरीज़ के लिए, dart:io (Platform.isAndroid, Platform.isIOS) या package:ffi जैसे एब्स्ट्रैक्शन के माध्यम से सशर्त संकलन का उपयोग किया जाता है। एक फ़ैक्टरी विधि बनाने की अनुशंसा की जाती है जो वर्तमान प्लेटफ़ॉर्म के लिए सही लाइब्रेरी लौटाती है।
FFI C पक्ष की मेमोरी का प्रबंधन नहीं करता है। यदि C फ़ंक्शन malloc के माध्यम से मेमोरी आवंटित करता है, तो इसे free के माध्यम से मुक्त किया जाना चाहिए, अन्यथा लीक होगा। Dart में C मेमोरी के लिए कोई कचरा संग्रहक नहीं है। अनुशंसा: हमेशा उसी विधि में मेमोरी मुक्त करें जहां इसे आवंटित किया गया था, या समूह मुक्ति के लिए Arena का उपयोग करें।
FFI कॉल Dart कोड के समान थ्रेड में निष्पादित होते हैं। लंबे समकालिक संचालन (10 ms से अधिक) UI थ्रेड को ब्लॉक करते हैं और फ्रेम ड्रॉप का कारण बनते हैं। लंबे संचालन के लिए, C फ़ंक्शन को Isolate में कॉल करना चाहिए या सुनिश्चित करना चाहिए कि C फ़ंक्शन पृष्ठभूमि थ्रेड में काम चलाता है और Port या कॉलबैक के माध्यम से Dart को सूचित करता है।
// लंबे संचालन के लिए isolate में FFI
import 'dart:isolate'
Future<String> computeHash(String input) async {
final port = ReceivePort()
await Isolate.spawn((SendPort sendPort) {
final result = sha256(input) // FFI कॉल
sendPort.send(result)
}, port.sendPort)
return await port.first as String
}
FFI कॉल को isolate में ले जाना सुनिश्चित करता है कि UI थ्रेड ब्लॉक न हो। हालांकि, यह ध्यान रखना चाहिए कि isolates के बीच बड़ी मात्रा में डेटा स्थानांतरित करने के लिए मेमोरी कॉपी करने की आवश्यकता होती है। बड़े बफ़र्स (>10 MB) के लिए, SharedMemory या मेमोरी-मैप्ड फ़ाइलों का उपयोग करना बेहतर है।
अक्सर पूछे जाने वाले प्रश्न
FFI C फ़ंक्शन को सीधे, समकालिक रूप से और बिना सीरियलाइज़ेशन के कॉल करता है — विलंब 0.01–0.1 µs। Method Channel JSON सीरियलाइज़ेशन के माध्यम से अतुल्यकालिक रूप से 0.5–5 ms विलंब के साथ काम करता है। FFI उच्च-प्रदर्शन संचालन के लिए उपयुक्त है, Method Channel सरल प्लेटफ़ॉर्म API कॉल के लिए।
सीधे — नहीं, dart:ffi केवल C फ़ंक्शन का समर्थन करता है। C++ कॉल करने के लिए, आपको extern “C” के साथ C रैपर बनाना होगा (प्रवेश बिंदु जो C प्रतीकों के रूप में निर्यात किए जाते हैं)। C++ वर्ग एक अतिरिक्त परत की आवश्यकता होती है जो विधि कॉल को C फ़ंक्शन में अनुवादित करती है।
FFI अपवादों का समर्थन नहीं करता है — यदि C फ़ंक्शन त्रुटि कोड लौटाता है, तो इसे मैन्युअल रूप से जांचा जाना चाहिए। Dart में FFI कॉल को try-catch में लपेटने और C फ़ंक्शन के रिटर्न कोड की जांच करने की अनुशंसा की जाती है। गंभीर त्रुटियां (segfault) पकड़ी नहीं जा सकतीं।
FFI उन लाइब्रेरीज़ के साथ काम नहीं करता है जिन्हें जटिल Java (JNI) या Objective-C (Message Dispatch) आरंभीकरण की आवश्यकता होती है। उदाहरण के लिए, UIKit और Android Views FFI के माध्यम से सुलभ नहीं हैं। सीमा इस तथ्य से संबंधित है कि FFI C ABI स्तर पर काम करता है, जबकि इन API को विशिष्ट रनटाइम की आवश्यकता होती है।
हां, C लाइब्रेरीज़ प्रत्येक लक्ष्य प्लेटफ़ॉर्म के लिए अलग-अलग संकलित की जाती हैं। Android के लिए, .so विभिन्न ABI (armeabi-v7a, arm64-v8a, x86_64) के लिए बनाया जाता है। iOS के लिए — एक सार्वभौमिक .dylib (arm64)। Windows के लिए — .dll। Flutter बिल्ड के दौरान स्वचालित रूप से लाइब्रेरी का सही संस्करण पैकेज करता है।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।