FFI — Dart에서 C/C++ 라이브러리 직접 호출

FFI(Foreign Function Interface)를 사용하면 MethodChannel 없이 C/C++ 함수를 직접 호출할 수 있습니다. MethodChannel보다 오버헤드가 적어 성능이 중요한 경우에 유용합니다.


FFI vs MethodChannel

비교FFIMethodChannel
호출 대상C/C++ 함수플랫폼 코드 (Kotlin/Swift)
오버헤드매우 적음직렬화/역직렬화 필요
동기/비동기동기 가능비동기만
설정 복잡도중간낮음
플랫폼 API직접 불가가능

기본 예제

C 코드 작성

C
// native/math_utils.c
#include <math.h>

// 두 수의 합
int add(int a, int b) {
    return a + b;
}

// 제곱근
double sqrt_value(double x) {
    return sqrt(x);
}

// 피보나치 (재귀)
int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

Dart에서 호출

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

// C 함수 시그니처 (native 측)
typedef AddNative = Int32 Function(Int32 a, Int32 b);
typedef SqrtNative = Double Function(Double x);
typedef FibonacciNative = Int32 Function(Int32 n);

// Dart 함수 시그니처
typedef AddDart = int Function(int a, int b);
typedef SqrtDart = double Function(double x);
typedef FibonacciDart = int Function(int n);

class MathUtils {
  late final DynamicLibrary _lib;
  late final AddDart add;
  late final SqrtDart sqrtValue;
  late final FibonacciDart fibonacci;

  MathUtils() {
    // 플랫폼별 라이브러리 로드
    _lib = _loadLibrary();

    // 함수 바인딩
    add = _lib
        .lookupFunction<AddNative, AddDart>('add');

    sqrtValue = _lib
        .lookupFunction<SqrtNative, SqrtDart>('sqrt_value');

    fibonacci = _lib
        .lookupFunction<FibonacciNative, FibonacciDart>('fibonacci');
  }

  DynamicLibrary _loadLibrary() {
    if (Platform.isAndroid) {
      return DynamicLibrary.open('libmath_utils.so');
    } else if (Platform.isIOS) {
      return DynamicLibrary.process();
    } else if (Platform.isMacOS) {
      return DynamicLibrary.open('libmath_utils.dylib');
    } else if (Platform.isWindows) {
      return DynamicLibrary.open('math_utils.dll');
    } else if (Platform.isLinux) {
      return DynamicLibrary.open('libmath_utils.so');
    }
    throw UnsupportedError('지원하지 않는 플랫폼');
  }
}

// 사용
void main() {
  final math = MathUtils();
  print(math.add(3, 5));           // 8
  print(math.sqrtValue(16.0));     // 4.0
  print(math.fibonacci(10));       // 55
}

Flutter 프로젝트에 통합

CMakeLists.txt (Android)

CMAKE
# android/app/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(math_utils)

add_library(math_utils SHARED
    ../../native/math_utils.c
)

build.gradle 수정

GROOVY
// android/app/build.gradle
android {
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

문자열 처리

C에서 문자열은 포인터로 전달됩니다.

C
// C 코드
#include <string.h>
#include <stdlib.h>

const char* greet(const char* name) {
    static char buffer[256];
    snprintf(buffer, sizeof(buffer), "안녕하세요, %s님!", name);
    return buffer;
}
DART
// C 함수 시그니처
typedef GreetNative = Pointer<Utf8> Function(Pointer<Utf8> name);
typedef GreetDart = Pointer<Utf8> Function(Pointer<Utf8> name);

// 사용
final greet = lib.lookupFunction<GreetNative, GreetDart>('greet');

// 문자열 변환
final namePtr = '심정훈'.toNativeUtf8();
final resultPtr = greet(namePtr);
final result = resultPtr.toDartString();
print(result);  // 안녕하세요, 심정훈님!

// 메모리 해제
calloc.free(namePtr);

구조체 사용

C
// C 구조체
typedef struct {
    double x;
    double y;
} Point;

double distance(Point* a, Point* b) {
    double dx = a->x - b->x;
    double dy = a->y - b->y;
    return sqrt(dx * dx + dy * dy);
}
DART
// Dart에서 구조체 정의
final class Point extends Struct {
  @Double()
  external double x;

  @Double()
  external double y;
}

typedef DistanceNative = Double Function(Pointer<Point> a, Pointer<Point> b);
typedef DistanceDart = double Function(Pointer<Point> a, Pointer<Point> b);

ffigen — 바인딩 자동 생성

수동으로 바인딩을 작성하는 것은 번거롭습니다. ffigen이 C 헤더 파일에서 Dart 바인딩을 자동 생성합니다.

YAML
# pubspec.yaml
dev_dependencies:
  ffigen: ^11.0.0
YAML
# ffigen.yaml
name: MathBindings
description: Math utils bindings
output: lib/generated/math_bindings.dart
headers:
  entry-points:
    - native/math_utils.h
BASH
dart run ffigen

비동기 FFI (Isolate 활용)

FFI 호출은 기본적으로 동기입니다. 무거운 계산은 Isolate에서 실행하세요.

DART
Future<int> computeFibonacci(int n) async {
  return await Isolate.run(() {
    final math = MathUtils();
    return math.fibonacci(n);
  });
}

정리

  • FFI는 C/C++ 함수를 직접 호출하여 MethodChannel보다 오버헤드가 적습니다
  • DynamicLibrary로 네이티브 라이브러리를 로드하고 lookupFunction으로 함수를 바인딩합니다
  • 문자열은 Pointer<Utf8>로 변환하고, 사용 후 메모리를 해제해야 합니다
  • ffigen으로 C 헤더에서 Dart 바인딩을 자동 생성할 수 있습니다
  • 무거운 FFI 작업은 Isolate에서 실행하여 UI 블로킹을 방지하세요
댓글 로딩 중...