Saltar al contenido principal

Vincular con código nativo usando el template de plugin FFI heredado

Usa el template de plugin FFI heredado y dart:ffi para vincular con código C nativo en tu plugin o aplicación Flutter.

Las aplicaciones móviles y de escritorio de Flutter pueden usar la biblioteca dart:ffi para llamar a las API nativas de C. FFI significa foreign function interface. Otros términos para una funcionalidad similar incluyen interfaz nativa y bindings de lenguaje.

Antes de que tu biblioteca o programa pueda usar la biblioteca FFI para vincularse con código nativo, debes asegurarte de que el código nativo esté cargado y sus símbolos sean visibles para Dart. Esta página se centra en compilar, empaquetar, y cargar código nativo dentro de un plugin o aplicación Flutter.

Este tutorial demuestra cómo empaquetar fuentes de C/C++ en un plugin de Flutter y vincularlos usando la biblioteca FFI de Dart. En este tutorial paso a paso, crearás una función de C que implementa la suma de 32 bits y luego la expone a través de un plugin de Dart llamado native_add.

Enlace dinámico versus estático

#

Una biblioteca nativa se puede vincular a una aplicación de forma dinámica o estática. Una biblioteca vinculada estáticamente se incrusta en la imagen ejecutable de la aplicación, y se carga cuando la aplicación se inicia.

Los símbolos de una biblioteca vinculada estáticamente se pueden cargar usando DynamicLibrary.executable o DynamicLibrary.process.

Una biblioteca vinculada dinámicamente, por el contrario, se distribuye en un archivo o carpeta independiente dentro de la aplicación, y se carga bajo demanda. El formato de distribución depende de la plataforma:

  • En Android, una biblioteca vinculada dinámicamente se distribuye como un conjunto de archivos .so (ELF), uno para cada arquitectura. Solo se admiten bibliotecas dinámicas, porque el ejecutable principal es la JVM, la cual Flutter no vincula estáticamente.
  • En iOS y macOS, la biblioteca vinculada dinámicamente se distribuye como una carpeta .framework.

Una biblioteca vinculada dinámicamente se puede cargar en Dart usando DynamicLibrary.open.

Crear un plugin FFI

#

Para crear un plugin FFI llamado native_add, usa flutter create con el template plugin_ffi:

flutter create --platforms=android,ios,macos,windows,linux --template=plugin_ffi native_add

Esto crea un plugin con fuentes C/C++ en native_add/src. Estos fuentes se compilan mediante los archivos de compilación nativos en las diversas carpetas de compilación del SO.

La biblioteca FFI solo puede vincularse contra símbolos de C, por lo que en C++ estos símbolos se marcan como extern "C".

También debes agregar atributos para indicar que los símbolos están referenciados desde Dart, para evitar que el enlazador descarte los símbolos durante la optimización en tiempo de enlace: __attribute__((visibility("default"))) __attribute__((used)).

El archivo de compilación específico de la plataforma vincula el código:

  • En Android, native_add/android/build.gradle.
  • En iOS, native_add/ios/native_add.podspec.
  • En macOS, native_add/macos/native_add.podspec.
  • En Linux, native_add/linux/CMakeLists.txt.
  • En Windows, native_add/windows/CMakeLists.txt.

El código nativo se invoca desde Dart en lib/native_add_bindings_generated.dart.

Los bindings se generan con package:ffigen.

Otros casos de uso

#

iOS

#

El enlazador dinámico carga automáticamente las bibliotecas vinculadas dinámicamente cuando se inicia la aplicación. Sus símbolos constituyentes pueden resolverse usando DynamicLibrary.process. También puedes obtener una referencia a la biblioteca con DynamicLibrary.open para restringir el alcance de la resolución de símbolos, pero no está claro cómo maneja esto el proceso de revisión de Apple.

Los símbolos vinculados estáticamente en el binario de la aplicación pueden resolverse usando DynamicLibrary.executable o DynamicLibrary.process.

Biblioteca de la plataforma

#

Para vincular con una biblioteca de la plataforma, sigue estas instrucciones:

  1. En Xcode, abre Runner.xcworkspace.
  2. Seleccionar la plataforma de destino.
  3. Haz clic en + en la sección Linked Frameworks and Libraries.
  4. Seleccionar la biblioteca del sistema con la que vincular.

Biblioteca propia (first-party)

#

Una biblioteca nativa propia (first-party) se puede incluir ya sea como código fuente o como un archivo .framework (firmado). Probablemente sea posible incluir también archivos de almacenamiento vinculados estáticamente, pero requiere pruebas.

Código fuente

#

Para vincular directamente al código fuente, sigue estas instrucciones:

  1. En Xcode, abre Runner.xcworkspace.

  2. Añade los archivos de código fuente de C/C++/Objective-C/Swift al proyecto de Xcode.

  3. Añade el siguiente prefijo a las declaraciones de símbolos exportados para asegurarte de que sean visibles para Dart:

    C/C++/Objective-C:

    objc
    extern "C" /* <= C++ only */ __attribute__((visibility("default"))) __attribute__((used))
    

    Swift:

    swift
    @_cdecl("myFunctionName")
    

Biblioteca compilada (dinámica)

#

Para vincular a una biblioteca dinámica compilada, sigue estas instrucciones:

  1. Si se encuentra presente un archivo Framework correctamente firmado, abre Runner.xcworkspace.
  2. Añade el archivo de framework a la sección Frameworks, Libraries, and Embedded Content del target en Xcode.
  3. Bajo la columna Embed, selecciona Embed & Sign.

Biblioteca de terceros de código abierto (open-source)

#

Para crear un plugin de Flutter que incluya tanto código de C/C++/Objective-C como código de Dart, sigue estas instrucciones:

  1. En el proyecto de tu plugin, abre ios/<myproject>.podspec.
  2. Añade el código nativo al campo source_files.

El código nativo se vinculará entonces estáticamente en el binario de la aplicación de cualquier aplicación que use este plugin.

Biblioteca de terceros de código cerrado (closed-source)

#

Para crear un plugin de Flutter que incluya código fuente de Dart, pero distribuya la biblioteca C/C++ en formato binario, sigue estas instrucciones:

  1. En el proyecto de tu plugin, abre ios/<myproject>.podspec.
  2. Añade un campo vendored_frameworks. Consulta el ejemplo de CocoaPods.

Eliminación de símbolos (stripping)

#

Al crear una compilación de lanzamiento (release), Xcode elimina los símbolos.

  1. En Xcode, selecciona el target Runner, luego ve a Build Settings > Strip Style.
  2. Cambia de All Symbols a Non-Global Symbols.

macOS

#

El enlazador dinámico carga automáticamente las bibliotecas vinculadas dinámicamente cuando se inicia la aplicación. Sus símbolos constituyentes pueden resolverse usando DynamicLibrary.process. También puedes obtener una referencia a la biblioteca con DynamicLibrary.open para restringir el alcance de la resolución de símbolos, pero no está claro cómo maneja esto el proceso de revisión de Apple.

Los símbolos vinculados estáticamente en el binario de la aplicación pueden resolverse usando DynamicLibrary.executable o DynamicLibrary.process.

Biblioteca de la plataforma

#

Para vincular con una biblioteca de la plataforma, sigue estas instrucciones:

  1. En Xcode, abre Runner.xcworkspace.
  2. Seleccionar la plataforma de destino.
  3. Haz clic en + en la sección Linked Frameworks and Libraries.
  4. Seleccionar la biblioteca del sistema con la que vincular.

Biblioteca propia (first-party)

#

Una biblioteca nativa propia (first-party) se puede incluir ya sea como código fuente o como un archivo .framework (firmado). Probablemente sea posible incluir también archivos de almacenamiento vinculados estáticamente, pero requiere pruebas.

Código fuente

#

Para vincular directamente al código fuente, sigue estas instrucciones:

  1. En Xcode, abre Runner.xcworkspace.

  2. Añade los archivos de código fuente de C/C++/Objective-C/Swift al proyecto de Xcode.

  3. Añade el siguiente prefijo a las declaraciones de símbolos exportados para asegurarte de que sean visibles para Dart:

    C/C++/Objective-C:

    objc
    extern "C" /* <= C++ only */ __attribute__((visibility("default"))) __attribute__((used))
    

    Swift:

    swift
    @_cdecl("myFunctionName")
    

Biblioteca compilada (dinámica)

#

Para vincular a una biblioteca dinámica compilada, sigue estas instrucciones:

  1. Si se encuentra presente un archivo Framework correctamente firmado, abre Runner.xcworkspace.
  2. Añade el archivo de framework a la sección Frameworks, Libraries, and Embedded Content del target en Xcode.
  3. Bajo la columna Embed, selecciona Embed & Sign.

Biblioteca compilada (dinámica), de código cerrado

#

Para añadir una biblioteca de código cerrado a una aplicación de Flutter macOS Desktop, sigue estas instrucciones:

  1. Sigue las instrucciones para Flutter desktop para crear una aplicación Flutter desktop.
  2. Open the yourapp/macos/Runner.xcworkspace in Xcode.
    1. Arrastra tu biblioteca precompilada (libyourlibrary.dylib) a Runner/Frameworks.
    2. Click Runner and go to the Build Phases tab.
      1. Arrastra libyourlibrary.dylib a la lista Copy Bundle Resources.
      2. Bajo Embed Libraries, marca Code Sign on Copy.
      3. Bajo Link Binary With Libraries, establece el estado en Optional. (Usamos enlace dinámico, no es necesario vincular estáticamente).
    3. Click Runner and go to the General tab.
      1. Arrastra libyourlibrary.dylib a la lista Frameworks, Libraries, and Embedded Content.
      2. Selecciona Embed & Sign.
    4. Click Runner and go to the Build Settings tab.
      1. En la sección Search Paths configura los Library Search Paths para incluir la ruta donde se encuentra libyourlibrary.dylib.
  3. Edit lib/main.dart.
    1. Usa DynamicLibrary.open('libyourlibrary.dylib') para vincular dinámicamente los símbolos.
    2. Llama a tu función nativa en algún lugar de un Widget.
  4. Ejecuta flutter run y comprueba que se llame a tu función nativa.
  5. Ejecuta flutter build macos para compilar una versión de lanzamiento autónoma de tu aplicación.

Eliminación de símbolos (stripping)

#

Al crear una compilación de lanzamiento (release), Xcode elimina los símbolos.

  1. En Xcode, selecciona el target Runner, luego ve a Build Settings > Strip Style.
  2. Cambia de All Symbols a Non-Global Symbols.

Android

#

Biblioteca de la plataforma

#

Para vincular con una biblioteca de la plataforma, sigue estas instrucciones:

  1. Busca la biblioteca deseada en la lista Android NDK Native APIs en la documentación de Android. Esta lista contiene API nativas estables.

  2. Carga la biblioteca usando DynamicLibrary.open. Por ejemplo, para cargar OpenGL ES (v3):

    dart
    DynamicLibrary.open('libGLES_v3.so');
    

Es posible que necesites actualizar el archivo del manifiesto de Android de la aplicación o plugin si así lo indica la documentación.

Biblioteca propia (first-party)

#

El proceso para incluir código nativo en formato de código fuente o binario es el mismo para una aplicación o un plugin.

Biblioteca de terceros de código abierto (open-source)

#

Sigue las instrucciones de Añadir código C y C++ a tu proyecto en la documentación de Android para añadir código nativo y soporte para la cadena de herramientas de código nativo (ya sea CMake o ndk-build).

Biblioteca de terceros de código cerrado (closed-source)

#

Para crear un plugin de Flutter que incluya código fuente de Dart, pero distribuya la biblioteca C/C++ en formato binario, sigue estas instrucciones:

  1. Abre el archivo android/build.gradle de tu proyecto.
  2. Añade el artefacto AAR como una dependencia. No incluyas el artefacto en tu paquete Flutter. En su lugar, debe descargarse desde un repositorio, como Maven Central.

Tamaño del APK de Android (compresión de objetos compartidos)

#

Las directrices de Android en general recomiendan distribuir objetos compartidos nativos sin comprimir porque eso en realidad ahorra espacio en el dispositivo. Los objetos compartidos se pueden cargar directamente desde el APK en lugar de desempaquetarlos en el dispositivo en una ubicación temporal y luego cargarlos. Además, los APK se empaquetan en tránsito; es por eso que deberías fijarte en el tamaño de descarga.

De forma predeterminada, los APK de Flutter comprimen libflutter.so y libapp.so, lo que resulta en un tamaño de APK más pequeño pero un mayor tamaño ocupado en el dispositivo. Para controlar si las bibliotecas nativas se almacenan comprimidas y se extraen al instalar, establece la opción useLegacyPackaging del plugin de Android Gradle. Para ver las recomendaciones actuales, consulta las directrices de Android.

Otros recursos

#

Para obtener más información sobre la interoperabilidad de C, echa un vistazo a estos videos: