Vincular a código nativo usando FFI
Para usar código nativo en tu programa de Flutter, usa la librería dart:ffi con la plantilla package_ffi.
Las aplicaciones de Flutter pueden usar la librería dart:ffi para llamar a APIs nativas. FFI significa foreign function interface (interfaz de funciones extranjeras). Otros términos para una funcionalidad similar incluyen interfaz nativa y vinculaciones de lenguaje.
Desde Flutter 3.38, la forma recomendada de vincular a código nativo es usar el
comando flutter create --template=package_ffi. Esta plantilla utiliza
build hooks para configurar la compilación nativa en un script
build.dart, y ya no requiere archivos de compilación específicos del SO. Este
enfoque funciona tanto para proyectos de Flutter como para proyectos independientes de Dart.
Si necesitas usar la API de plugins de Flutter, o si necesitas configurar un
entorno de ejecución de Google Play services en Android, usa la plantilla de plugin estándar (flutter create --template=plugin).
Crear un paquete FFI
#Para crear un paquete FFI, ejecuta el siguiente comando:
flutter create --template=package_ffi native_add
cd native_add
Esto crea un paquete con el siguiente contenido especializado:
lib/native_add.dart: El código Dart que define la API del paquete.lib/native_add_bindings_generated.dart: Las vinculaciones Dart generadas para el código nativo.src/native_add.c: El código fuente nativo de C.src/native_add.h: El archivo de cabecera de C para el código nativo.hook/build.dart: Un script que es ejecutado por el SDK de Flutter para compilar el código nativo.ffigen.yaml: El archivo de configuración para quepackage:ffigengenere las vinculaciones Dart.pubspec.yaml: La definición del paquete, que habilita el hookbuild.dart.
El código nativo
#El código nativo se encuentra en src/native_add.c y src/native_add.h. La
función de C sum está definida en el archivo .c y su firma está en el archivo
de cabecera. La función está marcada para ser exportada de modo que se pueda llamar desde Dart.
El hook de compilación
#El código nativo se compila y empaqueta con tu aplicación automáticamente. Esto
lo realiza el script hook/build.dart, que es un build hook.
Esto significa que ya no necesitas escribir archivos de compilación específicos del SO (como
CMakeLists.txt para Linux/Windows, .podspec para iOS/macOS, o
build.gradle
para Android) para compilar tu código nativo.
El build hook utiliza package:native_toolchain_c para compilar el código C en una
librería dinámica. Puedes personalizar este archivo para compilar otros lenguajes nativos o
para descargar binarios precompilados.
El código Dart
#El código Dart define la API pública del paquete.
Generar las vinculaciones
#Para vincular al código nativo, la plantilla utiliza package:ffigen
para generar
vinculaciones a partir del archivo de cabecera (src/native_add.h). La generación se configura
en ffigen.yaml.
Esto genera lib/native_add_bindings_generated.dart.
Llamar a la función nativa
#Las vinculaciones generadas en lib/native_add_bindings_generated.dart contienen
funciones @Native() external. Estas funciones se resuelven automáticamente en
tiempo de ejecución en función del asset de código producido por el build hook (que se ejecuta a
nivel de compilación). Esto significa que no se requiere lógica específica del SO para abrir las
librerías dinámicas mediante dlopen, haciendo que el código Dart sea verdaderamente multiplataforma.
El archivo de librería principal lib/native_add.dart expone estas funciones. Tu aplicación
puede entonces llamar a estas funciones importando package:native_add/native_add.dart.
Pruebas
#El paquete generado incluye una prueba unitaria en test/native_add_test.dart que
muestra cómo probar la función nativa.
Otros casos de uso
#Librerías del sistema
#Para vincular contra una librería del sistema, modificas el hook build.dart para especificar
el modo de vinculación. En lugar de compilar código fuente, creas un CodeAsset
y
estableces su linkMode.
Para muchas librerías del sistema en Android, iOS, Linux y macOS, puedes usar
LookupInProcess() para encontrar símbolos en el proceso principal.
Para Windows, a menudo utilizas DynamicLoadingSystem() y proporcionas el nombre de la
DLL.
Aquí tienes un ejemplo de build.dart que se vincula contra librerías del sistema para obtener el
nombre del host:
// hook/build.dart
import 'package:hooks/hooks.dart';
import 'package:code_assets/code_assets.dart';
void main(List<String> args) async {
await build(args, (input, output) async {
final targetOS = input.target.os;
switch (targetOS) {
case OS.android || OS.iOS || OS.linux || OS.macOS:
output.assets.code.add(
CodeAsset(
package: 'host_name',
name: 'src/third_party/unix.dart',
linkMode: LookupInProcess(),
),
);
case OS.windows:
output.assets.code.add(
CodeAsset(
package: 'host_name',
name: 'src/third_party/windows.dart',
linkMode: DynamicLoadingSystem(Uri.file('ws2_32.dll')),
),
);
default:
throw Exception('Unsupported target os: $targetOS');
}
});
}
Los archivos de Dart (unix.dart, windows.dart) contendrían entonces las funciones external
que usan los símbolos de estas librerías del sistema.
Empaquetar libc++_shared.so en Android
#
Aunque libc++_shared.so se distribuye con el NDK de Android,
no es una librería del sistema.
Si tu aplicación o paquete utiliza la librería estándar de C++,
o incluye múltiples librerías compartidas
que dependen de ella,
tu aplicación necesita empaquetar libc++_shared.so.
Para empaquetar la librería en tu aplicación,
añade una dependencia a package:android_libcpp_shared,
que utiliza su propio build hook para empaquetar libc++_shared.so
desde el NDK instalado localmente para cada arquitectura de destino.
Librerías de código cerrado
#También puedes usar build hooks para vincular contra librerías de código cerrado precompiladas. El enfoque recomendado es descargar los binarios precompilados en tiempo de compilación y verificar su integridad con un hash de archivo.
En tu hook build.dart, harías lo siguiente:
- Descarga la librería desde una URL.
- Verifica el hash del archivo descargado.
- Coloca la librería en el directorio de salida de la compilación.
- Crea un
CodeAssetconDynamicLoadingapuntando a la librería.
Aquí tienes un ejemplo simplificado de la creación de CodeAsset:
// hook/build.dart
import 'package:hooks/hooks.dart';
import 'package:code_assets/code_assets.dart';
void main(List<String> args) async {
await build(args, (input, output) async {
// 1. Download the library from a URL.
// 2. Verify the hash of the downloaded file.
// 3. Place the library in the build output directory.
output.assets.code.add(
CodeAsset(
package: input.packageName,
name: 'src/my_lib.dart', // Dart file with bindings
linkMode: DynamicLoadingBundled(),
file: input.outputDirectory.resolve('my_lib.so'),
),
);
});
}
Necesitarías manejar diferentes arquitecturas y plataformas teniendo diferentes versiones de tu librería precompilada.
Para ver más ejemplos, consulta los ejemplos del paquete code_assets.
Pautas de nomenclatura de librerías dinámicas
#Al implementar hooks build.dart para paquetes que empaquetan assets de código, es
fundamental asegurar una nomenclatura consistente de tus librerías dinámicas en todas las
arquitecturas y SDKs de destino.
En las plataformas de Apple (iOS y macOS), las librerías dinámicas se empaquetan en frameworks. El sistema de compilación de Flutter se basa en estos nombres para generar metadatos y empaquetar formatos distribuibles como XCFrameworks.
Consistencia entre arquitecturas
#Para un ID de asset dado, tu hook se invocará varias veces, una por
arquitectura. Tu hook debe producir el mismo nombre de archivo independientemente de la
arquitectura de destino (por ejemplo, arm64 vs. x64).
- ¿Por qué? Dentro de una sola compilación de SDK, Flutter combina binarios específicos de
arquitectura en un único binario universal (fat) usando
lipo. Si las arquitecturas tienen nombres de archivo diferentes, la herramienta elegirá uno de manera no determinante y emitirá una advertencia. Además, los mensajes de error en tiempo de ejecución serán confusos para tus usuarios si las librerías dinámicas cambian de nombre. - Acción recomendada: Evita añadir sufijos de arquitectura a tus
nombres de archivo (por ejemplo, usa
libsqlite3.dyliben lugar delibsqlite3_arm64.dylib). En su lugar, escribe el archivo eninput.outputDirectory(que es único por arquitectura) o en un subdirectorio específico de la arquitectura deinput.outputDirectoryShared(por ejemplo,input.outputDirectoryShared.resolve('$architecture/')).
Consistencia entre SDKs (iOS)
#Al compilar para iOS, tu hook se invocará múltiples veces con diferentes
valores para el SDK y la arquitectura. Tanto las invocaciones para dispositivo físico (iphoneos) como para
simulador (iphonesimulator) deben producir el mismo nombre de framework
para el mismo ID de asset.
- ¿Por qué? Flutter utiliza
xcodebuild -create-xcframeworkpara combinar estas salidas. Xcode requiere que todos los fragmentos (slices) de la plataforma dentro de un XCFramework compartan el mismo nombre de framework para permitir una vinculación fluida. Si los nombres de archivo difieren, la herramienta Flutter no puede crear un XCFramework correcto y los comandos comoflutter build ios-frameworkfallarán. - Acción recomendada: No uses sufijos como
_simo_simulatorpara la compilación del simulador. La estructura de XCFramework ya maneja la separación de plataformas internamente (por ejemplo,MyLib.xcframework/ios-arm64_x86_64-simulator/MyLib.framework). En su lugar, escribe el archivo eninput.outputDirectory(que es único por SDK) o en un subdirectorio específico del SDK deinput.outputDirectoryShared.
Consistencia en el conjunto de assets
#Tu hook debe producir el mismo conjunto de IDs de assets en todos los SDKs para una plataforma de destino determinada.
- ¿Por qué? El sistema de compilación de Apple y la validación de la App Store requieren que todos los
frameworks incluidos en una aplicación sean compatibles con el dispositivo de
destino. Si produces un asset para el simulador (
iphonesimulator) pero no para el dispositivo físico (iphoneos), el XCFramework resultante contendrá un fragmento (slice) que no tiene contraparte para el dispositivo. Esto puede provocar fallos en la compilación o que Apple rechace la aplicación por incluir binarios exclusivos del simulador en una compilación para dispositivos. - Acción recomendada: Asegúrate de que la lógica de tu hook
build.dartmaneje todos los SDKs soportados de manera consistente. Si produces un asset para un SDK, debes producir el correspondiente asset para todos los demás SDKs para esa plataforma. Para código específico de un SDK, puedes usar implementaciones de stubs para otros SDKs.
A menos que se indique lo contrario, la documentación de este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-06-08. Ver código fuente oreportar un problema.