การเรียกใช้ไลบรารี C ด้วย dart:ffi
เชื่อมกับไลบรารีร่วมแบบเนทีฟ และจัดรูปแบบโครงสร้างกับพอยน์เตอร์ผ่าน dart:ffi
การเรียกใช้ไลบรารี C ด้วย dart:ffi เป็นบทเรียน Flutter Mobile Development ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Flutter Mobile Development และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Flutter Mobile Development มีบทเรียนทั้งหมด 4 บทเรียน
บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ
Why dart:ffi?
dart:ffi is Dart's Foreign Function Interface. It lets your Flutter app call functions in native C shared libraries (.so, .dylib, .dll, or the iOS process image) directly, with no platform-channel round-trip.
- Synchronous by default and very low overhead, unlike
MethodChannelwhich serializes messages across an async boundary. - Ideal for CPU-heavy code, existing C/C++/Rust libraries, and OS-level APIs (sqlite, libsodium, image codecs).
- You bind a C signature to a Dart signature, then call it like an ordinary function.
The cost: you manage memory and types yourself. Get a pointer or a struct layout wrong and you crash the whole process.
Opening a DynamicLibrary
Everything starts with a DynamicLibrary. It is the handle to the loaded native code from which you look up symbols.
DynamicLibrary.open(path)loads a shared library by file name. On Android use'libfoo.so'; on iOS/macOS code is usually statically linked, so useDynamicLibrary.process()orDynamicLibrary.executable().- Pick the right name per platform with
Platform.isAndroid/Platform.isIOS.
import 'dart:ffi';
import 'dart:io' show Platform;
DynamicLibrary openNativeLib() {
if (Platform.isAndroid) {
return DynamicLibrary.open('libnative_math.so');
}
if (Platform.isIOS || Platform.isMacOS) {
// Symbols are linked into the app process on iOS.
return DynamicLibrary.process();
}
if (Platform.isWindows) {
return DynamicLibrary.open('native_math.dll');
}
return DynamicLibrary.open('libnative_math.so');
}Native types vs Dart types
FFI uses two type universes. The native type describes the C ABI; the Dart type is what your Dart code actually sees.
Int32,Int64,Uint8,Double,Floatare native marker types — you never instantiate them, they map to Dartint/double.Pointer<T>is a native address.Voidmarks no value.- The C function type is written with
Functionusing native types; the Dart-facing type uses plain Dart types.
Example: C int32_t add(int32_t, int32_t) becomes native Int32 Function(Int32, Int32) and Dart int Function(int, int).
Looking up and calling a function
Use lookupFunction to bind a C symbol to a Dart function in one call. It takes two generic parameters: the native signature and the Dart signature.
- The first type argument must use native types (
Int32,Double, …). - The second is the callable Dart type returned to you.
Below, a pure-Dart simulation shows the call shape that FFI mirrors at runtime.
// Conceptually, FFI does this:
// typedef NativeAdd = Int32 Function(Int32, Int32);
// typedef DartAdd = int Function(int, int);
// final add = lib.lookupFunction<NativeAdd, DartAdd>('add');
// Pure-Dart stand-in so the call site is identical in shape:
int Function(int, int) bindAdd() {
return (int a, int b) => a + b; // native impl returns a + b
}
void main() {
final add = bindAdd();
print('add(20, 22) = ${add(20, 22)}');
}typedef for clean bindings
Real bindings declare the two signatures as typedefs. This keeps lookupFunction readable and lets you reuse signatures.
- Native typedef uses native marker types and the suffix convention
...Native. - Dart typedef uses Dart types.
- The string passed to
lookupFunctionis the exact exported C symbol name.
import 'dart:ffi';
// C: double native_pow(double base, int32_t exp);
typedef NativePowNative = Double Function(Double, Int32);
typedef NativePow = double Function(double, int);
class MathBindings {
final DynamicLibrary lib;
late final NativePow pow;
MathBindings(this.lib) {
pow = lib.lookupFunction<NativePowNative, NativePow>('native_pow');
}
}Allocating native memory
To pass pointers you must allocate native (off-heap) memory. The package:ffi library provides malloc (a calloc variant also exists) plus extensions for strings.
malloc<Int32>()returns aPointer<Int32>; use.valueto read/write.malloc<Int32>(n)allocates an array ofnelements; index withptr[i]orptr.elementAt(i).- You must free what you allocate with
malloc.free(ptr)— the GC does not track native memory.
import 'dart:ffi';
import 'package:ffi/ffi.dart';
void usePointer() {
final ptr = malloc<Int32>(3); // array of 3 int32
try {
ptr[0] = 10;
ptr[1] = 20;
ptr[2] = 12;
var sum = 0;
for (var i = 0; i < 3; i++) {
sum += ptr[i];
}
print('sum = $sum');
} finally {
malloc.free(ptr); // always free
}
}Marshalling strings
C strings are null-terminated char*, represented as Pointer<Utf8> (from package:ffi). Conversion goes both ways:
- Dart → C:
myString.toNativeUtf8()allocates a native buffer (free it later). - C → Dart:
ptr.toDartString()copies the bytes into a DartString.
If the native function returns a pointer it allocated, you typically must call its matching free export — never malloc.free memory you did not allocate with malloc.
import 'dart:ffi';
import 'package:ffi/ffi.dart';
// C: int32_t count_chars(const char* text);
typedef CountNative = Int32 Function(Pointer<Utf8>);
typedef Count = int Function(Pointer<Utf8>);
int countChars(Count nativeCount, String text) {
final cStr = text.toNativeUtf8();
try {
return nativeCount(cStr);
} finally {
malloc.free(cStr);
}
}Defining a Struct
To marshal C structs, declare a Dart class extending Struct. Each field is annotated with its native type so the FFI runtime computes the exact memory layout/offsets.
- Scalar fields get annotations like
@Int32(),@Double(). - Field order and types must match the C struct exactly, including padding/alignment rules.
- You never construct a
Structwithnew; you obtain one via aPointer<T>.refbacked by native memory.
import 'dart:ffi';
// C:
// typedef struct { double x; double y; } Point;
final class Point extends Struct {
@Double()
external double x;
@Double()
external double y;
}Passing structs by pointer
Most C APIs take a Point*. Allocate the struct, fill it through .ref, pass the pointer, then read results back.
malloc<Point>()gives aPointer<Point>sized correctly for the layout.ptr.refis a view onto that native memory; writingptr.ref.x = 3.0mutates the C struct in place.- The native function reads/writes the same memory — this is how you get values out by reference.
import 'dart:ffi';
import 'package:ffi/ffi.dart';
// C: void translate(Point* p, double dx, double dy);
typedef TranslateNative = Void Function(Pointer<Point>, Double, Double);
typedef Translate = void Function(Pointer<Point>, double, double);
final class Point extends Struct {
@Double()
external double x;
@Double()
external double y;
}
void moveOrigin(Translate translate) {
final p = malloc<Point>();
try {
p.ref.x = 0;
p.ref.y = 0;
translate(p, 4.0, 5.0);
print('moved to (${p.ref.x}, ${p.ref.y})');
} finally {
malloc.free(p);
}
}Don't block the UI thread
FFI calls are synchronous: they run on the calling isolate's thread. A long native computation called from the main isolate freezes Flutter's UI.
- For heavy work, run the FFI call inside an
Isolate(e.g.Isolate.runon modern Dart) or a worker isolate. - Note: a
DynamicLibraryhandle and native pointers can be passed between isolates as addresses, but each isolate must re-open or share carefully — treat pointers as plain integers across boundaries. - Native code that calls back into Dart must use
NativeCallable/ send ports, not arbitrary threads.
import 'dart:isolate';
// Simulates offloading a heavy native FFI computation off the UI thread.
int _heavyNativeWork(int n) {
var acc = 0;
for (var i = 0; i < n; i++) {
acc = (acc + i) % 1000003;
}
return acc;
}
Future<void> main() async {
final result = await Isolate.run(() => _heavyNativeWork(5000000));
print('result = $result');
}Memory safety and ownership
FFI bugs are process crashes, not exceptions. Discipline matters:
- Ownership: whoever allocates must free. Memory from
malloc→malloc.free. Memory from a C library → that library's destructor export. - Wrap allocate/use/free in
try/finallyso you free even on error. - For long-lived native objects, attach a
NativeFinalizerso the destructor runs when the Dart wrapper is GC'd. - Never read
.ref/.valueon a pointer after it is freed — that is a use-after-free.
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class SafeBuffer {
final Pointer<Uint8> ptr;
final int length;
SafeBuffer(this.length) : ptr = malloc<Uint8>(length);
void dispose() => malloc.free(ptr);
}
void main() {
final buf = SafeBuffer(16);
try {
buf.ptr[0] = 255;
print('first byte = ${buf.ptr[0]}');
} finally {
buf.dispose();
}
}Quick Check
Answer based on dart:ffi struct and memory rules.
Recap
You can now bind to native C libraries from Flutter with dart:ffi:
- Load code with
DynamicLibrary.open/.process(), choosing the path per platform. - Bind symbols with
lookupFunction<Native, Dart>, declaring native vs Darttypedefs. - Allocate off-heap memory with
malloc, marshal strings viatoNativeUtf8/toDartString, and pass arrays as pointers. - Define structs by extending
Structwith native-type annotations; pass them by pointer and read results through.ref. - Keep heavy calls off the UI isolate, and enforce strict ownership: free what you allocate, use
try/finallyandNativeFinalizer, and never touch freed pointers.
FFI trades safety for speed and reach — correct types, layout, and lifetimes are entirely your responsibility.
คำถามที่พบบ่อย
บทเรียน “การเรียกใช้ไลบรารี C ด้วย dart:ffi” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การเรียกใช้ไลบรารี C ด้วย dart:ffi” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Flutter Mobile Development ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Flutter Mobile Development มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การเรียกใช้ไลบรารี C ด้วย dart:ffi”
เชื่อมกับไลบรารีร่วมแบบเนทีฟ และจัดรูปแบบโครงสร้างกับพอยน์เตอร์ผ่าน dart:ffi คุณปฏิบัติ Flutter Mobile Development ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Flutter Mobile Development หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Flutter Mobile Development บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “การเรียกใช้ไลบรารี C ด้วย dart:ffi” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Flutter Mobile Development นี้ได้ไหม
ได้ บทเรียน Flutter Mobile Development ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การเรียกใช้ไลบรารี C ด้วย dart:ffi
- ช่องทางแพลตฟอร์มที่ปลอดภัยตามชนิดข้อมูลด้วย Pigeon
- การเขียนปลั๊กอินแพลตฟอร์มแบบกำหนดเองสำหรับ iOS และ Android
- ไอโซเลตเบื้องหลังและการจัดการหน่วยความจำเนทีฟ