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 অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।