Vinculación Dinámica o Estática

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

Vamos a utilizar este repositorio.

GitHub - flutterwtf/Flutter-FFI-Demo
Contribute to flutterwtf/Flutter-FFI-Demo development by creating an account on GitHub.

Los símbolos de una biblioteca enlazada estáticamente pueden cargarse utilizando 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. En Android, una biblioteca vinculada dinámicamente se distribuye como un conjunto de archivos  .so  (ELF), uno para cada arquitectura. En iOS, la biblioteca vinculada dinámicamente se distribuye como una carpeta  .framework .

Una biblioteca vinculada dinámicamente se puede cargar en Dart a través de DynamicLibrary.open.

Añadir Fuentes C/C++

Dart:ffi sólo puede construir fuentes C. Por eso necesitas marcar los símbolos C++ con "extern C".

Con el fin de enlazar a código nativo, debe asegurarse de que el código nativo se carga, y sus símbolos son visibles para Dart.

Aquí está el código para operaciones matemáticas complejas (como el cálculo de pi o el cálculo del área bajo la sinusoide).

Añade tus fuentes .c . Por ejemplo:

example.c

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include "example.h"

double calc_pi(int32_t precision) {
    double pi = 0;
    int8_t sign = 1;

    for (int32_t i = 1; i < precision * 2; i+=2) {
        pi = pi + sign * 4.0/i;
        sign *= -1;
    }

    return pi;
}

double calc_area_under_sin(int32_t precision, int32_t pi_precision)
{
    double sum = 0;
    double pi = calc_pi(pi_precision);
    double rectArea = pi * 1.0;

    srand(time(NULL));
    for (int i = 0; i < precision; ++i) {
        double x = ((float)rand())/RAND_MAX * pi;
        double y = ((float)rand())/RAND_MAX * 1.0;

        if (y < sin(x)) {
            sum++;
        }
    }
    return sum / precision * rectArea;
}

Luego tienes que crear un archivo de cabecera que declare prototipos de funciones y marque fuentes externas.

Marque los archivos que desea que sean visibles en Dart usando:

extern "C" __attribute__((visibility("default"))) __attribute__((used))

example.h

#ifndef FFI_TEST_EXAMPLE_H
#define FFI_TEST_EXAMPLE_H

extern "C" __attribute__((visibility("default"))) __attribute__((used))
double calc_pi(int32_t precision);

extern "C" __attribute__((visibility("default"))) __attribute__((used))
double calc_area_under_sin(int32_t precision, int32_t pi_precision);

#endif //FFI_TEST_EXAMPLE_H

Después de añadir el código fuente a su proyecto, debe informar al sistema de compilación de Android sobre el código.

Android

Crear un archivo llamado  CMakeLists.txt bajo  android/app/ ruta.

A continuación, rellene este archivo con los datos. Por ejemplo

cmake_minimum_required(VERSION 3.4.1)  # for example

add_library( example_lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        example.cpp)

Luego actualiza tu  android/app/build.gradle:

android {
  // ...
  externalNativeBuild {
    // Encapsulates your CMake build configurations.
    cmake {
      // Provides a relative path to your CMake build script.
      path "CMakeLists.txt"
    }
  }
  // ...
}

IOS

En iOS, tienes que decirle a Xcode que enlace estáticamente el archivo:

  • En Xcode, abra Runner.xcworkspace
  • Añade los archivos fuente C/C++/Objective-C/Swift al proyecto Xcode.

Cargar el código

Para cargar código en C, primero necesitas cargar una librería. Siga el siguiente ejemplo para hacerlo. Recuerda que el nombre del archivo .so es 'lib' + el nombre de la librería que has puesto en el archivo CMakeLists.txt. En nuestro caso, tenemos 'libexample_lib.so'.

import 'dart:ffi'; 
import 'dart:io'; 

final DynamicLibrary nativeAddLib = Platform.isAndroid
    ? DynamicLibrary.open('libexample_lib.so')
    : DynamicLibrary.process();

Después de tratar con la biblioteca, puede obtener sus funciones nativas de aquí.

typedef CalcPiC = Double Function(Int32 precision);

CalcPiDart calcPi =  nativeAddLib.lookup<NativeFunction<CalcPiC>>('calc_pi').asFunction();

A continuación, puede llamar a la función:

calcPi(10000); // returns 3.14....

Comparando el Rendimiento de las Llamadas Dart y Native

Veamos el rendimiento de las llamadas Dart y Native en el cálculo del área de formas complejas con el método de puntos aleatorios.

Calcularemos el área bajo la función sin(x) donde x pertenece a [0, pi].
Comparing Dart and Native Call Performance

Escojamos un rectángulo que tenga altura 1 y anchura pi y que contenga nuestra parte de la curva sen(x). Sabemos que el área de este rectángulo es 1 * pi. El método de los puntos aleatorios nos dice que si colocamos un punto aleatorio en nuestro rectángulo y luego contamos el número de puntos en el área que queremos calcular y luego dividimos este número por la cantidad de puntos y lo multiplicamos por el área del rectángulo, obtendremos un área aproximada de nuestra forma. Cuanto mayor sea el número de puntos, más exacta será el área.

En nuestro ejemplo calculamos pi primero usando series infinitas y luego calculamos el área de nuestra figura.

Vamos a elegir precisiones bajas para calcular pi y el área al principio. Eso significa que tendremos un pequeño cálculo.

Como puedes ver la diferencia no es tan grande y C es incluso más lento en el cálculo de pi. Eso es debido al tiempo de llamada a la función nativa. Pero cuando tenemos muchos más cálculos, las cosas se ponen diferentes:

Como puedes ver aquí, C es casi 2 veces más rápido que dart para calcular el área aproximada de nuestra forma.
⚠️ Pero recuerda llamar a  nativeLib.lookup<NativeFunction<CalcPiC>>('calc_pi').asFunction(); ; sólo una vez cuando inicialices y almacenes la función en tu programa, porque llamarla muchas veces puede producir un GRAN problema de rendimiento.

0:00
/

Añadir Librerías de Terceros

Android

  • Busque la biblioteca deseada en la lista de Android NDK Native APIs en los documentos de Android.
  • Añade la librería a tu CMakeLists.txt
  • Importe su librería
find_library( # Defines the name of the path variable that stores the
        # location of the NDK library.
        log

        # Specifies the name of the NDK library that
        # CMake needs to locate.
        log )
DynamicLibrary.open('liblog.so');

IOS

  • In Xcode, open Runner.xcworkspace.
  • Add the C/C++/Objective-C/Swift source files to the Xcode project.
  • Add the following prefix to the exported symbol declarations to ensure they are visible to Dart:

IOS

  • En Xcode, abra Runner.xcworkspace.
  • Añade los archivos fuente C/C++/Objective-C/Swift al proyecto Xcode.
  • Añade el siguiente prefijo a las declaraciones de los símbolos exportados para asegurarte de que son visibles para Dart:
extern "C" /* <= C++ only */ __attribute__((visibility("default"))) __attribute__((used))

Trabajar con Estructuras y Punteros

Definición de una estructura en struct_example.h:

extern"C"
struct __attribute__((visibility("default"))) Person
{
	int age;
	char*name;
};

Y en Dart:

class Person extends Struct {
  @Int32()
	external int age;

	external Pointer<Utf8> name;
}

Vamos a definir algunas funciones para init Person struct. Una con int y char*, y la segunda con int* y char*. Entonces:

final createPerson = nativeLib.lookupFunction<CreatePersonC, CreatePersonDart>('create_person');
Person p1 = createPerson(18, 'John'.toNativeUtf8());

Para trabajar con punteros, primero tenemos que asignar memoria e init un valor. A continuación, ¡siéntete libre de usarlo!

final agePtr = calloc<Int32>();
agePtr.value = 22;
Person p2 = createPersonPointer(agePtr, 'Mike'.toNativeUtf8());

Para obtener el valor diferido, basta con utilizar el campo de  value  del puntero.

⚠️ No olvide liberar memoria
calloc.free(agePtr);

☝Recuerda que puedes acceder a la memoria nativa asignada. Esto, por ejemplo, se puede utilizar para trabajar con imágenes.


Dart:ffigen

Si tienes mucho código nativo para usar en Dart, puedes usar ffigen para generar archivos dart.

  • Añade ffigen bajo dev_dependencies en su pubspec.yaml(ejecuta dart pub add -d ffigen)
  • Añada package:ffi bajo dependencies en su pubspec.yaml (ejecuta dart pub add ffi).
  • Instale LLVM (consulte Installing LLVM).
  • Las configuraciones deben proporcionarse en pubspec.yaml.
  • Ejecute la herramienta - dart run ffigen.

Proporcione todas las configuraciones a  pubspec.yamlbajo la etiqueta  ffigen :

ffigen:
	name: ExampleLibGen
	description: Bindings to structs_example.
	output:'lib/example_gen.dart'
	headers:
		entry-points:
      -'android/app/calc_example/example.cpp'
	llvm-path:
    -'D:\\LLVM'

Entonces, ¡no dude en utilizar los archivos generados! Para más información, visite Dart:ffigen.

GitHub - flutterwtf/Flutter-FFI-Demo
Contribute to flutterwtf/Flutter-FFI-Demo development by creating an account on GitHub.
Share this post