Skip to main content

เนื้อหาภาษาไทย / Thai content

System guide Content reviewed

การใช้ FFI (Foreign Function Interface) ใน Flutter

บทความนี้แนะนำ FFI (Foreign Function Interface) ซึ่งเป็นเครื่องมือที่ช่วยให้แอป Flutter สามารถเรียกใช้โค้ด Native ที่เขียนด้วยภาษาอื่น ๆ เช่น C/C++ ได้ ทำให้สาม...

การใช้ FFI (Foreign Function Interface) ใน Flutter

ในบทความก่อนหน้า เราได้เรียนรู้เกี่ยวกับการใช้ Isolate ใน Flutter เพื่อรันโค้ดที่ใช้เวลานานใน Background thread เพื่อป้องกัน UI Blocking ในบทความนี้ เราจะสำรวจอีกหนึ่งเครื่องมืออันทรงพลังที่ช่วยให้ Flutter สามารถทำงานร่วมกับโค้ดที่เขียนด้วยภาษาอื่นได้ นั่นคือ FFI หรือ Foreign Function Interface

FFI ช่วยให้แอป Flutter สามารถเรียกใช้ฟังก์ชันที่เขียนด้วยภาษา C/C++ (หรือภาษาอื่นๆ ที่คอมไพล์เป็น Native code) ได้ ทำให้เราสามารถใช้ประโยชน์จากไลบรารีที่มีอยู่แล้ว, เพิ่มประสิทธิภาพในส่วนที่ต้องการการคำนวณสูง หรือเข้าถึงคุณสมบัติเฉพาะของแพลตฟอร์มที่ Flutter เองยังไม่รองรับ

dart:ffi คืออะไร?

dart:ffi คือไลบรารี Dart ที่อนุญาตให้เราทำการ Binding กับ Native code ได้อย่างปลอดภัยและมีประสิทธิภาพ โดย dart:ffi จะจัดการเรื่องการแปลงข้อมูลระหว่าง Dart types และ Native types, การจัดการ Memory และการเรียกฟังก์ชัน Native อย่างถูกต้อง

เหตุผลที่ Flutter เลือกใช้ FFI แทนวิธีการอื่นๆ (เช่น Platform Channels) สำหรับการทำงานร่วมกับ Native code คือ:

  • ประสิทธิภาพ: FFI มี Overhead น้อยกว่า Platform Channels มาก ทำให้การเรียกใช้ Native code รวดเร็วและมีประสิทธิภาพมากกว่า
  • ความยืดหยุ่น: FFI สามารถใช้ได้กับ Native code ที่เขียนด้วยภาษาต่างๆ ที่คอมไพล์เป็น Native code (เช่น C, C++, Rust, Go)
  • ความปลอดภัย: dart:ffi มี Type safety ที่เข้มงวด ช่วยลดโอกาสเกิดข้อผิดพลาดที่เกิดจากการแปลงข้อมูลผิดพลาด

ขั้นตอนการใช้งาน FFI

โดยทั่วไป ขั้นตอนการใช้งาน FFI ใน Flutter มีดังนี้:

  1. เขียน Native Code: สร้างไลบรารี Native ที่มีฟังก์ชันที่เราต้องการเรียกใช้จาก Flutter
  2. สร้าง Dart Bindings: สร้างไฟล์ Dart ที่กำหนด Function signature ของ Native functions ที่เราต้องการเรียกใช้
  3. โหลด Native Library: โหลด Native library เข้ามาในแอป Flutter
  4. เรียกใช้ Native Functions: เรียกใช้ Native functions ผ่าน Dart bindings ที่เราสร้างไว้

ตัวอย่าง: การเรียกใช้ฟังก์ชัน C จาก Flutter

เพื่อให้เห็นภาพชัดเจน เราจะสร้างตัวอย่างง่ายๆ โดยจะเขียนฟังก์ชัน C ที่บวกเลขสองจำนวน แล้วเรียกใช้ฟังก์ชันนี้จาก Flutter

1. เขียน Native Code (C)

สร้างไฟล์ native_lib.c:

#include <stdio.h>

int add(int a, int b) {
  return a + b;
}

คอมไพล์ไฟล์ C เป็น Shared Library (.so สำหรับ Linux/Android, .dylib สำหรับ macOS, .dll สำหรับ Windows) โดยใช้ Compiler เช่น GCC หรือ Clang

ตัวอย่างคำสั่งคอมไพล์ (Linux/macOS):

gcc -shared -o libnative.so native_lib.c

2. สร้าง Dart Bindings

สร้างไฟล์ Dart ที่กำหนด Function signature ของฟังก์ชัน add ใน native_lib.dart:

import 'dart:ffi' as ffi;
import 'dart:io' show Platform;

// Define the function signature for the 'add' function in the native library.
typedef NativeAddFunc = ffi.Int32 Function(ffi.Int32 a, ffi.Int32 b);
typedef DartAddFunc = int Function(int a, int b);

class NativeLibrary {
  static late ffi.DynamicLibrary _nativeLib;

  static void load() {
    // Determine the correct library name based on the platform.
    String libraryPath;
    if (Platform.isAndroid) {
      libraryPath = 'libnative.so';
    } else if (Platform.isIOS) {
      libraryPath = 'libnative.dylib'; // This might require more complex setup
    } else if (Platform.isMacOS) {
      libraryPath = 'libnative.dylib';
    } else if (Platform.isWindows) {
      libraryPath = 'native.dll';
    } else {
      throw UnsupportedError('Unsupported platform: ${Platform.operatingSystem}');
    }

    // Load the native library.
    _nativeLib = ffi.DynamicLibrary.open(libraryPath);
  }

  static int add(int a, int b) {
    // Look up the 'add' function in the native library.
    final addFunc = _nativeLib.lookupFunction<NativeAddFunc, DartAddFunc>('add');
    // Call the native function and return the result.
    return addFunc(a, b);
  }
}

คำอธิบายโค้ด:

  • typedef NativeAddFunc = ffi.Int32 Function(ffi.Int32 a, ffi.Int32 b);: กำหนด Function signature ของฟังก์ชัน add ในรูปแบบ Native ที่รับค่า Int32 สองตัวและคืนค่า Int32
  • typedef DartAddFunc = int Function(int a, int b);: กำหนด Function signature ของฟังก์ชัน add ในรูปแบบ Dart ที่รับค่า int สองตัวและคืนค่า int
  • ffi.DynamicLibrary.open('libnative.so');: โหลด Native library เข้ามาในแอป
  • _nativeLib.lookupFunction<NativeAddFunc, DartAddFunc>('add');: ค้นหาฟังก์ชัน add ใน Native library และแปลงเป็น Dart function ที่สามารถเรียกใช้ได้

3. เรียกใช้ Native Functions จาก Flutter

ในไฟล์ Flutter ของคุณ (เช่น main.dart):

import 'package:flutter/material.dart';
import 'native_lib.dart';

void main() {
  NativeLibrary.load(); // Load the native library.
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FFI Example',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('FFI Example'),
        ),
        body: Center(
          child: Text('Result: ${NativeLibrary.add(5, 3)}'),
        ),
      ),
    );
  }
}

คำอธิบายโค้ด:

  • NativeLibrary.load();: โหลด Native library ก่อนที่จะเรียกใช้ฟังก์ชันใดๆ
  • NativeLibrary.add(5, 3): เรียกใช้ฟังก์ชัน add ที่อยู่ใน Native library

เมื่อรันแอป Flutter คุณจะเห็นผลลัพธ์ของการบวกเลขที่คำนวณโดย Native code แสดงบนหน้าจอ

การจัดการ Memory

การจัดการ Memory เป็นสิ่งสำคัญอย่างยิ่งเมื่อใช้ FFI เนื่องจากเราต้องมั่นใจว่า Memory ที่ถูก Allocate ใน Native code จะถูก Deallocate อย่างถูกต้อง เพื่อป้องกัน Memory leaks

dart:ffi มีเครื่องมือหลายอย่างที่ช่วยในการจัดการ Memory เช่น:

  • Pointer: ใช้แทน Pointer ใน Native code
  • allocate: ใช้ Allocate Memory ใน Native heap
  • free: ใช้ Deallocate Memory ที่ถูก Allocate ด้วย allocate

ข้อควรระวัง: การจัดการ Memory ผิดพลาดอาจทำให้เกิด crashes หรือ Memory leaks ในแอปพลิเคชัน ดังนั้นควรระมัดระวังและทดสอบโค้ดอย่างละเอียด

ข้อดีและข้อเสียของการใช้ FFI

ข้อดี:

  • ประสิทธิภาพ: สามารถเพิ่มประสิทธิภาพในส่วนที่ต้องการการคำนวณสูง
  • การใช้ไลบรารีที่มีอยู่แล้ว: สามารถใช้ประโยชน์จากไลบรารี Native ที่มีอยู่แล้ว
  • การเข้าถึงคุณสมบัติเฉพาะของแพลตฟอร์ม: สามารถเข้าถึงคุณสมบัติเฉพาะของแพลตฟอร์มที่ Flutter ยังไม่รองรับ

ข้อเสีย:

  • ความซับซ้อน: การใช้งาน FFI ค่อนข้างซับซ้อนและต้องมีความเข้าใจเกี่ยวกับ Native code
  • ความเสี่ยง: การจัดการ Memory ผิดพลาดอาจทำให้เกิด crashes หรือ Memory leaks
  • ความยากในการ Debug: การ Debug ปัญหาที่เกิดจาก Native code อาจทำได้ยาก

สรุป

FFI เป็นเครื่องมือที่มีประสิทธิภาพที่ช่วยให้ Flutter สามารถทำงานร่วมกับ Native code ได้ ทำให้เราสามารถสร้างแอปพลิเคชันที่ทรงพลังและยืดหยุ่นมากขึ้น อย่างไรก็ตาม การใช้งาน FFI ต้องใช้ความระมัดระวังและความเข้าใจเกี่ยวกับ Native code เพื่อหลีกเลี่ยงปัญหาที่อาจเกิดขึ้น

ในบทความถัดไป เราจะสำรวจการใช้ Machine Learning ใน Flutter ด้วย TensorFlow Lite

Office.in.th Custom Service

Want to build or adapt a system from this structure?

Office.in.th designs and develops enterprise web applications, API integrations, and public- and private-sector management systems, with long-term support.

See our software development service