2020. 9. 25. 11:38ㆍProgramming/Flutter
flutter의 ffi 라이브러리의 사용
ffi 라이브러리를 사용하면 C/C++로 작성된 코드를 사용할 수 있다. 아래와 같이 ffi_test 프로젝트를 생성해보자.
flutter create ffi_test
lib/main.dart
에 플로팅 버튼을 누르면 카운트가 1씩 증가하는 샘플 코드가 작성되어있을 것이다. 이제 C의 malloc을 사용해서 int의 사이즈만큼 메모리를 할당한 뒤, 값을 변경하여 카운트를 증가시키도록 수정해보자.
ffi 패키지는 기본으로 포함되어있지만, ffi 라이브러리는 pubspec.yaml
파일에 의존성을 추가해줘야한다. 아이러니하게도 ffi만 있으면 C 함수와 연동은 가능하지만, 포인터에 직접 메모리를 할당하는 동작을 할 수 없다.
pubspec.yaml
의 dependencies
항목에 아래와 같이 ffi를 추가해주자.
dependencies:
flutter:
sdk: flutter
ffi: ^0.1.3
이후 flutter pub get을 실행하면 ffi 라이브러리가 설치되며, ffi 라이브러리를 import
한 뒤 allocate()
를 사용하여 메모리 할당이 가능해진다.
import 'package:ffi/ffi.dart';
명심하자, 위의 ffi 라이브러리를 설치하지 않으면 allocate()
를 사용할 수 없다.
flutter 문서의 ffi를 통한 C/C++ 연동 튜토리얼 페이지에서는 별도 라이브러리의 언급이 없었는데, dart:ffi와 package:ffi가 이름은 같고 내용이 달라서 많이 헤맸었다.
C함수의 작성
ios/Classes/native_calc.cpp
라는 파일을 생성한 뒤, 아래의 내용을 작성해주자. 포인터를 받아서, 1을 더해주는 코드이다. C코드임에도 불구하고 굳이 확장자를 CPP로 작성한 이유는, CPP로 작성했을때만 안드로이드 스튜디오에서 빌드했을 때 링크가 걸려서 디버깅이 가능하기 때문이다.
#include <cstdio>
#include <cstdlib>
extern "C" __attribute__((visibility("default"))) __attribute__((used))
void addPointer(int32_t* original) {
(*original) += 1;
}
CMakeLists.txt의 작성 (Android)
안드로이드에서 C로 작성된 라이브러리를 사용하기 위해, 다음과 같이 android/CMakeLists.txt를 작성하도록 하자.
cmake_minimum_required(VERSION 3.4.1) # for example
add_library( nativeCalc
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
../ios/Classes/native_calc.cpp )
딱 보면 감이 착 오겠지만 플러터의 C/C++ 연동 튜토리얼 페이지에서 그대로 가져온 뒤, 라이브러리 이름과 파일만 변경해줬다. iOS는 ios/Classes
경로에 작성해주면, 자동으로 불러오는 듯 하다.
Dynamic Library를 불러와서 C 함수를 참조하는 변수 작성
위에서 생성한 nativeCalc
를 로드하여 addPointer()
를 참조하는 변수를 만들도록 하자. ffi를 사용하는 코드를 별도로 관리하기 위해, native_calc.dart
파일을 lib에 새로 생성한 뒤, 아래와 같이 작성해줬다.
import 'dart:ffi';
import 'dart:io';
// 1
final DynamicLibrary nativeCalcLib = Platform.isAndroid
? DynamicLibrary.open("libnativeCalc.so")
: DynamicLibrary.open("ffi_allocate_test.framework/nativeCalc");
// 2
final void Function(Pointer<Int32>) nativeAddPointer = nativeCalcLib
.lookup<NativeFunction<Void Function(Pointer<Int32>)>>("addPointer")
.asFunction();
- 플랫폼에 따라 참조해야 할 라이브러리가 다른다, 안드로이드는 CMakeLists.txt에서 지정한 nativeCalc라는 이름 앞에 접두어 lib이 붙은 so파일을 참조한다.
- addPointer의 반환형은 void이고, 파라미터로는 Int32 타입의 포인터를 전달받는다. 1에서 선언한 다이나믹 라이브러리에서 'addPointer'라는 이름을 가진,
Pointer<Int32>
를 받고 반환형은 Void인 값을 nativeAddPointer라는 변수에 함수로 할당하겠다는 의미이다.
이걸로 CPP 파일의 생성과 연결이 끝났다. 남은 것은 플로팅 함수의 로직을 수정하여, 포인터를 생성하고 메모리를 할당한 뒤 addPointer에 넘겨주는 것 뿐이다.
nativeAddPointer의 호출
main.dart의 상단에 dart:ffi 라이브러리와 ffi 패키지를 각각 import해주도록 하자.
import 'dart:ffi';
import 'package:ffi/ffi.dart';
다음은 _counter 상태값을 증가시키는 _incrementCounter 함수의 내용을 다음과 같이 수정해주자.
void _incrementCounter() {
setState(() {
// Int32 타입 포인터의 생성 및 메모리 할당
Pointer<Int32> pointer = allocate(count: 1);
// 값 복사
pointer.value = _counter;
nativeAddPointer(pointer);
// 포인터의 값을 _counter로 전달.
_counter = pointer.value;
});
}
_counter값을 포인터로 치환하면 코드가 좀 더 단순해지겠지만, 설명을 간단하게 하기 위해 _incrementCounter 함수의 내부에서 allocate()를 사용하여 메모리를 할당하고, _counter의 값을 복사한 뒤 연산하는 방식으로 작성했다.
예제를 실행해보면, 이전과 동일하게 플로팅 버튼을 터치할 때마다 카운트가 1씩 증가하는 것을 볼 수 있다.
Visual Studio Code로 실행했을 때는 cpp파일 내에 브레이크 포인트를 걸어도, 디버깅이 되지 않는다. Android Studio로 android 폴더를 열어서 빌드하면,
app/cpp/native_calc.cpp
파일이 생성된 것을 볼 수 있다. 이 파일에 브레이크 포인트를 걸고 디버깅 모드로 실행하면 디버깅이 가능하다. :)