dart:ffi를 사용한 C 상호운용(C interop)
dart:ffi를 사용한 C 상호운용(C interop)
Dart 프로그램에서 C 코드를 사용하려면 dart:ffi 라이브러리를 사용해요.
출처: 원문
본문
Dart Native 플랫폼에서 실행되는 Dart 모바일, 명령줄, 서버 앱은 dart:ffi 라이브러리를 사용해 네이티브 C API를 호출하고, 네이티브 메모리를 읽고, 쓰고, 할당하고, 해제할 수 있어요. FFI는 외부 함수 인터페이스(foreign function interface)를 뜻해요. 비슷한 기능을 나타내는 다른 용어로는 네이티브 인터페이스(native interface)와 언어 바인딩(language bindings)이 있어요.
API 문서는 dart:ffi API 참조에서 볼 수 있어요.
예제 파일 다운로드
이 가이드의 예제를 다루려면 전체 ffi 샘플 디렉터리를 다운로드해 주세요. 여기에는 dart:ffi 라이브러리 사용법을 보여주는 다음 예제가 포함되어 있어요.
| 예제 | 설명 |
|---|---|
hello_world |
인자도 반환 값도 없는 C 함수를 호출하는 방법 |
primitives |
인자와 반환 값이 int나 pointer인 C 함수를 호출하는 방법 |
structs |
구조체(struct)를 사용해 문자열을 C와 주고받고, 단순하고 복잡한 C 구조체를 다루는 방법 |
test_utils |
이 모든 예제에 쓰이는 공통 테스트 유틸리티 |
hello_world 예제 살펴보기
hello_world 예제는 C 라이브러리를 호출하는 데 필요한 최소한의 코드를 담고 있어요. 이 예제는 앞서 다운로드한 samples/ffi에서 찾을 수 있어요.
파일들 – hello_world 예제는 다음 파일들을 포함해요.
| 소스 파일 | 설명 |
|---|---|
hello.dart |
C 라이브러리의 hello_world() 함수를 사용하는 Dart 파일 |
pubspec.yaml |
SDK 하한이 3.4인 Dart pubspec 파일 |
hello_library/hello.h |
hello_world() 함수를 선언 |
hello_library/hello.c |
hello.h를 import하고 hello_world() 함수를 정의하는 C 파일 |
hello_library/hello.def |
DLL 빌드 시 사용되는 정보를 지정하는 모듈 정의 파일 |
hello_library/CMakeLists.txt |
C 코드를 동적 라이브러리로 컴파일하는 CMake 빌드 파일 |
C 라이브러리를 빌드하면 libhello.dylib(macOS), libhello.dll(Windows), 또는 libhello.so(Linux)라는 동적 라이브러리 파일을 포함한 여러 파일이 만들어져요.
빌드 및 실행 – 동적 라이브러리를 빌드하고 Dart 앱을 실행하는 명령은 다음과 비슷해요.
$ cd hello_library
$ cmake .
...
$ make
...
$ cd ..
$ dart pub get
$ dart run hello.dart
Hello World
참고: macOS에서 Dart VM(
dart)을 포함한 실행 파일은 서명된 라이브러리만 로드할 수 있어요. 라이브러리 서명에 대해 더 알아보려면 Apple의 코드 서명 가이드(Code Signing Guide)를 참고해 주세요.
dart:ffi 활용하기
dart:ffi 라이브러리를 사용해 C 함수를 호출하는 방법을 배우려면 hello.dart 파일을 살펴봐 주세요. 이 섹션에서는 이 파일의 내용을 설명해 드릴게요.
dart:ffi를 import해 주세요.import 'dart:ffi' as ffi;- 동적 라이브러리의 경로를 저장하는 데 사용할 path 라이브러리를 import해 주세요.
import 'dart:io' show Platform, Directory; import 'package:path/path.dart' as path; - C 함수의 FFI 타입 시그니처로 typedef를 만들어 주세요.
dart:ffi라이브러리에 따라 가장 많이 사용되는 타입에 대해 알아보려면 네이티브 타입과 상호작용(Interfacing with native types)을 참고해 주세요.typedef hello_world_func = ffi.Void Function(); - C 함수를 호출할 때 사용할 변수의 typedef를 만들어 주세요.
typedef HelloWorld = void Function(); - 동적 라이브러리의 경로를 저장할 변수를 만들어 주세요.
final String libraryPath; if (Platform.isMacOS) { libraryPath = path.join( Directory.current.path, 'hello_library', 'libhello.dylib', ); } else if (Platform.isWindows) { libraryPath = path.join( Directory.current.path, 'hello_library', 'Debug', 'hello.dll', ); } else { libraryPath = path.join( Directory.current.path, 'hello_library', 'libhello.so', ); } - C 함수가 들어 있는 동적 라이브러리를 열어 주세요.
final dylib = ffi.DynamicLibrary.open(libraryPath); - C 함수에 대한 참조를 얻어 변수에 넣어 주세요. 이 코드는 2, 3단계의 typedef와 4단계의 동적 라이브러리 변수를 사용해요.
final HelloWorld hello = dylib .lookup<ffi.NativeFunction<hello_world_func>>('hello_world') .asFunction(); - C 함수를 호출해 주세요.
hello();
hello_world 예제를 이해했다면 다른 dart:ffi 예제들도 확인해 주세요.
C 라이브러리 번들 및 로드
네이티브 C 라이브러리를 번들/패키징/배포한 다음 로드하는 방법은 플랫폼과 라이브러리 유형에 따라 달라요. 방법을 배우려면 다음 페이지와 예제를 참고해 주세요.
- Flutter: Android 앱용 dart:ffi
- Flutter: iOS 앱용 dart:ffi
- Flutter: macOS 앱용 dart:ffi
- dart:ffi 예제
네이티브 타입과 상호작용
dart:ffi 라이브러리는 NativeType을 구현하며 C의 네이티브 타입을 나타내는 여러 타입을 제공해요. 일부 네이티브 타입은 인스턴스화할 수 있어요. 다른 일부 네이티브 타입은 타입 시그니처에서 마커(marker)로만 사용할 수 있어요.
인스턴스화할 수 있는 타입 시그니처 마커 – 다음 네이티브 타입은 타입 시그니처에서 마커로 사용할 수 있어요. 이들 또는 그 서브타입은 Dart 코드에서 인스턴스화할 수 있어요.
| Dart 타입 | 설명 |
|---|---|
Array |
고정 크기의 항목 배열. 타입별 배열의 상위 타입 |
Pointer |
네이티브 C 메모리를 가리키는 포인터 |
Struct |
모든 FFI 구조체 타입의 상위 타입 |
Union |
모든 FFI 공용체(union) 타입의 상위 타입 |
타입 시그니처 마커로만 제공되는 타입 – 다음 목록은 타입 시그니처에서 마커 역할을 하는 플랫폼에 구애받지 않는 네이티브 타입들이에요. 이들은 Dart 코드에서 인스턴스화할 수 없어요.
| Dart 타입 | 설명 |
|---|---|
Bool |
C의 네이티브 bool을 나타냄 |
Double |
C의 네이티브 64비트 double을 나타냄 |
Float |
C의 네이티브 32비트 float을 나타냄 |
Int8 |
C의 네이티브 부호 있는 8비트 정수를 나타냄 |
Int16 |
C의 네이티브 부호 있는 16비트 정수를 나타냄 |
Int32 |
C의 네이티브 부호 있는 32비트 정수를 나타냄 |
Int64 |
C의 네이티브 부호 있는 64비트 정수를 나타냄 |
NativeFunction |
C의 함수 타입을 나타냄 |
Opaque |
C의 모든 불투명(opaque) 타입의 상위 타입 |
Uint8 |
C의 네이티브 부호 없는 8비트 정수를 나타냄 |
Uint16 |
C의 네이티브 부호 없는 16비트 정수를 나타냄 |
Uint32 |
C의 네이티브 부호 없는 32비트 정수를 나타냄 |
Uint64 |
C의 네이티브 부호 없는 64비트 정수를 나타냄 |
Void |
C의 void 타입을 나타냄 |
AbiSpecificInteger를 확장하는 ABI 특정 마커 네이티브 타입도 여럿 있어요. 이 타입들이 특정 플랫폼에서 어떻게 매핑되는지 알아보려면 아래 표에 연결된 API 문서를 참고해 주세요.
| Dart 타입 | 설명 |
|---|---|
AbiSpecificInteger |
모든 ABI 특정 정수 타입의 상위 타입 |
Int |
C의 int 타입을 나타냄 |
IntPtr |
C의 intptr_t 타입을 나타냄 |
Long |
C의 long int(long) 타입을 나타냄 |
LongLong |
C의 long long 타입을 나타냄 |
Short |
C의 short 타입을 나타냄 |
SignedChar |
C의 signed char 타입을 나타냄 |
Size |
C의 size_t 타입을 나타냄 |
UintPtr |
C의 uintptr_t 타입을 나타냄 |
UnsignedChar |
C의 unsigned char 타입을 나타냄 |
UnsignedInt |
C의 unsigned int 타입을 나타냄 |
UnsignedLong |
C의 unsigned long int 타입을 나타냄 |
UnsignedLongLong |
C의 unsigned long long 타입을 나타냄 |
UnsignedShort |
C의 unsigned short 타입을 나타냄 |
WChar |
C의 wchar_t 타입을 나타냄 |
package:ffigen으로 FFI 바인딩 생성
큰 API 표면에서는 C 코드와 통합되는 Dart 바인딩을 직접 작성하는 것이 시간이 많이 걸릴 수 있어요. C 헤더 파일에서 FFI 래퍼를 Dart가 만들게 하려면 package:ffigen 바인딩 생성기를 사용해 주세요.
Dart FFI에서 코드 에셋 지원에 대해 알아보려면 Native와 DefaultAsset에 대한 dart:ffi API 참조를 확인해 주세요.
네이티브 코드 빌드 및 번들
Dart 빌드 훅(build hooks, 이전 명칭은 네이티브 에셋/native assets)은 패키지가 네이티브 코드 에셋을 포함하고, 이들이 투명하게 빌드되고 번들되어 런타임에 제공되도록 해요. 자세한 내용은 Hooks를 참고해 주세요.